1import{r as n,s,j as t,P as l,b as d}from"./index-Ba4BgsO5.js";function c(){return n.useEffect(()=>{s(d.ODDS_CALCULATOR)},[]),n.useEffect(()=>{const r=`<!DOCTYPE html> 2<html lang="en"> 3<head> 4 <meta charset="UTF-8"> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 <title>NFL Point Spread Calculator | WQI Power Rankings Odds | NFL Draft HQ</title> 7 <meta name="description" content="Calculate NFL point spreads using our proprietary WQI (Win Quality Index) system. Drag-and-drop power rankings to predict matchup odds. AI-powered odds analyzer for NFL games."> 8 <meta name="keywords" content="NFL point spread calculator, NFL odds calculator, power rankings, WQI calculator, NFL matchup predictor, Vegas odds comparison, NFL predictions, point spread odds"> 9 <meta name="robots" content="index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1"> 10 <meta name="language" content="English"> 11 <meta name="revisit-after" content="7 days"> 12 <link rel="canonical" href="https://nfldrafthq.com/nfl-odds-calculator"> 13 <meta name="theme-color" content="#2563eb"> 14 <link rel="apple-touch-icon" href="https://nfldrafthq.com/public/favicon-64.png"> 15 16 <!-- Open Graph Tags for Social Sharing --> 17 <meta property="og:title" content="NFL Point Spread Calculator | WQI Power Rankings | NFL Draft HQ"> 18 <meta property="og:description" content="Calculate NFL point spreads using our proprietary WQI (Win Quality Index) system. Drag-and-drop power rankings to predict matchup odds. AI-powered odds analyzer for NFL games."> 19 <meta property="og:url" content="https://nfldrafthq.com/nfl-odds-calculator"> 20 <meta property="og:type" content="website"> 21 <meta property="og:image" content="https://nfldrafthq.com/public/og-image-nfl-calculator.png"> 22 <meta property="og:image:width" content="1200"> 23 <meta property="og:image:height" content="630"> 24 <meta property="og:site_name" content="NFL Draft HQ"> 25 <meta property="og:locale" content="en_US"> 26 27 <!-- Twitter Card Tags --> 28 <meta name="twitter:card" content="summary_large_image"> 29 <meta name="twitter:title" content="NFL Point Spread Calculator | WQI Power Rankings"> 30 <meta name="twitter:description" content="Calculate NFL point spreads using our proprietary WQI system. Drag-and-drop power rankings to predict odds."> 31 <meta name="twitter:image" content="https://nfldrafthq.com/public/og-image-nfl-calculator.png"> 32 <meta name="twitter:creator" content="@NFLDraftHQ"> 33 <meta name="twitter:site" content="@NFLDraftHQ"> 34 35 <!-- JSON-LD Tool Schema --> 36 <script type="application/ld+json"> 37 { 38 "@context": "https://schema.org", 39 "@type": "Tool", 40 "name": "NFL Point Spread Calculator", 41 "description": "Calculate NFL point spreads using our proprietary WQI (Win Quality Index) system. Drag-and-drop power rankings to predict matchup odds.", 42 "url": "https://nfldrafthq.com/nfl-odds-calculator", 43 "applicationCategory": "UtilityApplication", 44 "offers": { 45 "@type": "Offer", 46 "price": "0", 47 "priceCurrency": "USD" 48 }, 49 "publisher": { 50 "@type": "Organization", 51 "name": "NFL Draft HQ", 52 "url": "https://nfldrafthq.com" 53 } 54 } 55 <\/script> 56 57 <!-- JSON-LD FAQPage Schema --> 58 <script type="application/ld+json"> 59 { 60 "@context": "https://schema.org", 61 "@type": "FAQPage", 62 "mainEntity": [ 63 { 64 "@type": "Question", 65 "name": "How does the NFL Point Spread Calculator work?", 66 "acceptedAnswer": { 67 "@type": "Answer", 68 "text": "Our calculator uses the Win Quality Index (WQI), a proprietary system that ranks NFL teams based on performance. You drag-and-drop teams to create custom power rankings, select an away and home team, and our algorithm calculates the predicted point spread for that matchup." 69 } 70 }, 71 { 72 "@type": "Question", 73 "name": "What is the WQI (Win Quality Index)?", 74 "acceptedAnswer": { 75 "@type": "Answer", 76 "text": "The Win Quality Index (WQI) is our proprietary AI-powered metric that evaluates NFL team performance by analyzing the strength and quality of wins and losses throughout the season, providing more accurate power rankings than traditional metrics." 77 } 78 }, 79 { 80 "@type": "Question", 81 "name": "Can I create custom power rankings?",
82 "acceptedAnswer": { 83 "@type": "Answer", 84 "text": "Yes! You have full control to customize power rankings by dragging and dropping team cards into your preferred order. Your custom arrangement will be used to calculate point spreads for any matchup." 85 } 86 }, 87 { 88 "@type": "Question", 89 "name": "How does the neutral site option work?", 90 "acceptedAnswer": { 91 "@type": "Answer", 92 "text": "When you toggle the 'Neutral Site' option, the calculator removes the home-field advantage bonus from the point spread calculation, giving you odds as if the game were played at a neutral location." 93 } 94 }, 95 { 96 "@type": "Question", 97 "name": "Are these spreads accurate predictions of Vegas odds?", 98 "acceptedAnswer": { 99 "@type": "Answer", 100 "text": "Our spreads are for entertainment purposes only and are generated by our proprietary WQI algorithm. They provide an alternative perspective to Vegas spreads but should not be used as betting advice." 101 } 102 } 103 ] 104 } 105 <\/script> 106 107 <!-- JSON-LD BreadcrumbList Schema --> 108 <script type="application/ld+json"> 109 { 110 "@context": "https://schema.org", 111 "@type": "BreadcrumbList", 112 "itemListElement": [ 113 { 114 "@type": "ListItem", 115 "position": 1, 116 "name": "Home", 117 "item": "https://nfldrafthq.com/" 118 }, 119 { 120 "@type": "ListItem", 121 "position": 2, 122 "name": "NFL Odds Calculator", 123 "item": "https://nfldrafthq.com/nfl-odds-calculator" 124 } 125 ] 126 } 127 <\/script> 128 129 <style> 130 /* Hide embedded navigation - use PageHeader component instead */ 131 .main-header { 132 display: none !important; 133 } 134 body { 135 font-family: Arial, sans-serif; 136 padding: 0; 137 margin: 0; 138 background: #f0f0f0; 139 -webkit-font-smoothing: antialiased; 140 -moz-osx-font-smoothing: grayscale; 141 } 142 h1, h2 { 143 text-align: center; 144 color: #333; 145 text-rendering: optimizeLegibility; 146 } 147 .content { 148 max-width: 1200px; 149 margin: auto; 150 } 151 .container { 152 display: flex; 153 gap: 20px; 154 flex-wrap: wrap; 155 justify-content: center; 156 align-items: center; 157 } 158 .grid { 159 display: grid; 160 grid-template-columns: repeat(8, 1fr); 161 gap: 10px; 162 width: 100%; 163 max-width: 800px; 164 margin-top: 20px; 165 } 166 @media (max-width: 768px) { 167 body { 168 overflow-x: hidden; 169 overflow-y: visible !important; 170 height: auto !important; 171 min-height: auto !important; 172 padding-bottom: 0 !important; 173 } 174 html { 175 overflow-y: visible !important; 176 height: auto !important; 177 min-height: auto !important; 178 } 179 .content { 180 padding: 10px !important; 181 } 182 .grid { 183 grid-template-columns: repeat(4, 1fr); 184 gap: 5px; 185 margin-top: -65px !important; 186 width: calc(100vw - 20px); 187 max-width: calc(100vw - 20px); 188 } 189 .container { 190 flex-direction: column-reverse !important; 191 gap: 3px; 192 overflow-y: visible !important; 193 } 194 .content { 195 height: auto !important; 196 overflow: visible !important; 197 } 198 #wqi-description { 199 margin: 5px auto 5px !important; 200 padding-bottom: 0 !important; 201 } 202 .copyright { 203 display: none !important; 204 } 205 /* Hide the footer inside the iframe on mobile */ 206 footer, .footer { 207 display: none !important; 208 } 209 .controls { 210 width: calc(100vw - 24px); 211 max-width: calc(100vw - 24px); 212 margin-bottom: 0px; 213 padding: 12px; 214 box-sizing: border-box; 215 position: relative !important; 216 top: 0px !important; 217 transform: scale(0.8); 218 transform-origin: top center; 219 } 220 .controls button { 221 transform: scale(0.8); 222 } 223 .mobile-header-container { 224 border-bottom: none !important; 225 } 226 .team-btn, .selected-team { 227 min-height: 45px; 228 min-width: 45px; 229 padding: 4px; 230 font-size: 11px; 231 } 232 .selected-team { 233 width: 81px !important; 234 height: 81px !important; 235 min-height: 81px !important; 236 min-width: 81px !important; 237 flex: 1 !important; 238 padding: 4px !important; 239 display: flex !important; 240 align-items: center !important; 241 justify-content: center !important; 242 } 243 .team-container { 244 gap: 10px; 245 width: 100%; 246 } 247 .at-sign { 248 font-size: 16px; 249 } 250 .team-btn small { 251 font-size: 9px !important;
252 margin-top: 1px; 253 } 254 h2 { 255 font-size: 14px !important; 256 margin: 4px 0 !important; 257 white-space: nowrap; 258 } 259 .neutral-check { 260 flex-direction: column; 261 } 262 .how-it-works-btn { 263 margin-left: 0; 264 margin-top: 8px; 265 width: 100%; 266 } 267 } 268 .team-btn, .selected-team { 269 padding: 10px; 270 border: 1px solid #1d4ed8; 271 border-radius: 5px; 272 cursor: move; 273 font-weight: bold; 274 text-align: center; 275 transition: transform 0.2s; 276 min-height: 80px; 277 min-width: 80px; 278 display: flex; 279 flex-direction: column; 280 justify-content: center; 281 box-sizing: border-box; 282 text-rendering: geometricPrecision; 283 background: #2563eb; 284 color: white; 285 box-shadow: 6px 6px 12px rgba(59, 130, 246, 0.5), inset -3px -3px 6px rgba(59, 130, 246, 0.4), inset 3px 3px 6px rgba(255, 255, 255, 0.8); 286 text-shadow: 2px 2px 4px rgba(0,0,0,0.3); 287 } 288 .team-btn small { 289 margin-top: 4px; 290 display: block; 291 font-weight: normal; 292 font-size: 13px !important; 293 } 294 .rank-separator { 295 width: 50%; 296 border: 0.5px solid rgba(255,255,255,0.3); 297 margin: 4px auto; 298 } 299 .selected-team { 300 background: #666; 301 border: 1px solid #1d4ed8; 302 min-height: 40px; 303 min-width: 40px; 304 height: 60px; 305 flex: 1; 306 color: white; 307 box-shadow: 6px 6px 12px rgba(59, 130, 246, 0.5), inset -3px -3px 6px rgba(59, 130, 246, 0.4), inset 3px 3px 6px rgba(255, 255, 255, 0.8); 308 text-shadow: 2px 2px 4px rgba(0,0,0,0.3); 309 } 310 .controls { 311 width: 100%; 312 max-width: 300px; 313 background: white; 314 padding: 20px; 315 border-radius: 10px; 316 box-shadow: 0 2px 5px rgba(0,0,0,0.1); 317 border: 2px solid #000; 318 position: relative; 319 top: -30px; 320 } 321 button { 322 padding: 10px 20px; 323 margin: 5px; 324 cursor: pointer; 325 background: #2563eb; 326 color: white; 327 border: 1px solid #1d4ed8; 328 border-radius: 5px; 329 box-shadow: 6px 6px 12px rgba(59, 130, 246, 0.5), inset -3px -3px 6px rgba(59, 130, 246, 0.4), inset 3px 3px 6px rgba(255, 255, 255, 0.8); 330 text-shadow: 2px 2px 4px rgba(0,0,0,0.3); 331 font-weight: bold; 332 transition: transform 0.2s; 333 } 334 button:hover { 335 background: #1d4ed8; 336 transform: scale(1.05); 337 } 338 button:disabled { 339 background: #6c757d; 340 cursor: not-allowed; 341 opacity: 0.65; 342 box-shadow: none; 343 } 344 .how-it-works-btn { 345 padding: 8px 16px; 346 background: #2563eb; 347 border: 1px solid #1d4ed8; 348 color: white; 349 border-radius: 5px; 350 cursor: pointer; 351 font-size: 14px; 352 margin-left: 10px; 353 box-shadow: 6px 6px 12px rgba(59, 130, 246, 0.5), inset -3px -3px 6px rgba(59, 130, 246, 0.4), inset 3px 3px 6px rgba(255, 255, 255, 0.8); 354 text-shadow: 2px 2px 4px rgba(0,0,0,0.3); 355 font-weight: bold; 356 } 357 .how-it-works-btn:hover { 358 background: #1d4ed8; 359 transform: scale(1.05); 360 } 361 .modal { 362 display: none; 363 position: fixed; 364 z-index: 1000; 365 left: 0; 366 top: 0; 367 width: 100%; 368 height: 100%; 369 background-color: rgba(0,0,0,0.5); 370 }
371 .modal-content { 372 background-color: white; 373 margin: 3% auto; 374 padding: 30px; 375 padding-right: 50px; 376 border-radius: 10px; 377 max-width: 500px; 378 max-height: 80vh; 379 overflow-y: auto; 380 box-shadow: 0 4px 20px rgba(0,0,0,0.3); 381 position: relative; 382 } 383 .close-modal { 384 float: right; 385 font-size: 28px; 386 font-weight: bold; 387 cursor: pointer; 388 color: #666; 389 } 390 .close-modal:hover { 391 color: #000; 392 } 393 .modal h2 { 394 margin-top: 0; 395 color: #007bff; 396 } 397 .modal p { 398 line-height: 1.6; 399 color: #333; 400 margin: 15px 0; 401 } 402 .modal ul { 403 line-height: 1.8; 404 color: #333; 405 } 406 #result { 407 margin-top: 20px; 408 font-size: 1.2em; 409 padding: 10px; 410 border-radius: 5px; 411 min-height: 40px; 412 } 413 .neutral-check, .injury-check { 414 margin: 10px 0; 415 } 416 #clearBtn { 417 background: #dc3545; 418 } 419 #clearBtn:disabled { 420 background: #6c757d; 421 cursor: not-allowed; 422 opacity: 0.65; 423 } 424 .team-display { 425 pointer-events: none; 426 height: 100%; 427 display: flex; 428 align-items: center; 429 justify-content: center; 430 } 431 .team-container { 432 display: flex; 433 align-items: center; 434 gap: 10px; 435 margin: 15px 0; 436 } 437 .at-sign { 438 font-size: 20px; 439 font-weight: bold; 440 } 441 .copyright { 442 text-align: center; 443 margin-top: 25px; 444 color: #666; 445 font-size: 0.9em; 446 } 447 @keyframes neon-flash { 448 0%, 100% { box-shadow: 0 0 5px #ff0000, 0 0 10px #ff0000, 0 0 15px #ff0000; } 449 50% { box-shadow: 0 0 10px #ff0000, 0 0 20px #ff0000, 0 0 30px #ff0000; } 450 } 451 .flash-border { 452 animation: neon-flash 3s ease-in-out infinite; 453 border-radius: 8px; 454 padding: 8px; 455 } 456 .instruction-text { 457 font-size: 14px; 458 margin: 4px 0; 459 color: #0066cc; 460 font-weight: 600; 461 } 462 .scroll-elevator { 463 position: fixed; 464 right: 20px; 465 display: flex; 466 flex-direction: column; 467 gap: 5px; 468 z-index: 1001; 469 } 470 .scroll-btn { 471 width: 40px; 472 height: 40px; 473 background: #2563eb; 474 border: 1px solid #1d4ed8; 475 border-radius: 5px; 476 color: white; 477 cursor: pointer; 478 display: flex; 479 align-items: center; 480 justify-content: center; 481 font-size: 20px; 482 box-shadow: 0 2px 5px rgba(0,0,0,0.3); 483 transition: all 0.2s; 484 } 485 .scroll-btn:hover { 486 background: #1d4ed8; 487 transform: scale(1.1); 488 } 489 .scroll-btn:active { 490 transform: scale(0.95); 491 } 492 </style> 493</head> 494<body> 495 <!-- Combined Header with Logo and Navigation --> 496 <nav class="main-header"> 497 <div class="header-wrapper"> 498 <!-- Desktop Layout: Logo left, centered nav --> 499 <div class="header-row-desktop"> 500 <a href="/" class="logo-link"> 501 <div class="logo-section"> 502 <img src="/hq-logo.png" alt="HQ Logo" class="header-logo" style="height: 52px; width: auto;"> 503 <span class="header-brand">NFL Draft HQ</span> 504 </div> 505 </a> 506 507 <div class="nav-center"> 508 <div class="nav-dropdown" data-testid="nav-draft-dropdown"> 509 <button class="nav-link dropdown-trigger" data-testid="nav-draft-btn"> 510 Draft <span class="dropdown-arrow">â¼</span> 511 </button> 512 <div class="dropdown-menu" data-testid="dropdown-menu-draft"> 513 <a href="/current-mock" class="dropdown-item" target="_blank" rel="noopener noreferrer" onclick="new Audio('/nfl-draft-chime.mp3').play().catch(() => {})">HQ's 2027 Mock Draft</a> 514 <a href="/draft-sim-classic" class="dropdown-item" target="_blank" rel="noopener noreferrer">
5143-Round Classic Simulator</a> 515 <a href="/draft-sim-custom" class="dropdown-item" onclick="window.top.location.href='/draft-sim-custom'; return false;">7-Round Customizable Sim</a> 516 <a href="/Prospect-Rankings" class="dropdown-item" onclick="window.top.location.href='/Prospect-Rankings'; return false;">Prospect Rankings</a> 517 <a href="/team-needs" class="dropdown-item" onclick="window.top.location.href='/team-needs'; return false;">Team Needs</a> 518 <a href="/full-draft-order" class="dropdown-item" onclick="window.top.location.href='/full-draft-order'; return false;">Full Draft Order</a> 519 </div> 520 </div> 521 <div class="nav-dropdown" data-testid="nav-odds-dropdown"> 522 <button class="nav-link dropdown-trigger" data-testid="nav-odds-btn"> 523 Odds/Bets <span class="dropdown-arrow">â¼</span> 524 </button> 525 <div class="dropdown-menu" data-testid="dropdown-menu-odds"> 526 <a href="/nfl-od
526ds-calculator" class="dropdown-item" onclick="window.top.location.href='/nfl-odds-calculator'; return false;">Point Spread Calculator</a> 527 </div> 528 </div> 529 <a href="/nfl-report" class="nav-link" data-testid="nav-nfl-news" target="_blank" style="font-weight: bold;">NFL/Draft/Fantasy News</a> 530 <div class="nav-dropdown" data-testid="nav-more-dropdown"> 531 <button class="nav-link dropdown-trigger" data-testid="nav-more-btn"> 532 More <span class="dropdown-arrow">â¼</span> 533 </button> 534 <div class="dropdown-menu" data-testid="dropdown-menu-more"> 535 <a href="/team-picker-wheel.html" class="dropdown-item" target="_blank" rel="noopener noreferrer">Team Picker Wheel</a> 536 <a href="/ai-game" class="dropdown-item" onclick="window.top.location.href='/ai-game'; return false;">AI Game Simulator</a> 537 <a href="https://www.nfltriviablitz.com/" class="dropdown-item" target="_blank">2000's NFL Trivia</a> 538 <a href="#contact" class="dropdown-item">Contact Us</a> 539 </div> 540 </div> 541 </div> 542 </div> 543 544 <!-- Mobile Layout: Logo | Draft | Odds & Bets | More --> 545 <div class="header-row-mobile"> 546 <a href="/" class="mobile-logo-link" data-testid="mobile-logo-link"> 547 <img src="/hq-logo.png" alt="" class="mobile-header-logo" style="display: block; width: auto;"> 548 </a> 549 <div class="mobile-nav-dropdown" data-testid="mobile-draft-dropdown"> 550 <button class="mobile-nav-link mobile-dropdown-trigger" data-testid="mobile-draft-btn"> 551 Draft <span class="mobile-dropdown-arrow">â¼</span> 552 </button> 553 <div class="mobile-dropdown-menu" data-testid="mobile-dropdown-menu-draft"> 554 <a href="/current-mock" class="mobile-dropdown-item" target="_blank" rel="noopener noreferrer" onclick="new Audio('/nfl-draft-chime.mp3').play().catch(() => {})">HQ's 2027 Mock Draft</a> 555 <a href="/draft-sim-custom" class="mobile-dropdown-item" onclick="window.top.location.href='/draft-sim-custom'; return false;">7-Round Customizable Sim</a> 556 <a href="/draft-sim-classic" class="mobile-dropdown-item" target="_blank" rel="noopener noreferrer">3-Round Classic Simulator</a> 557 <a href="/Prospect-Rankings" class="mobile-dropdown-item" onclick="window.top.location.href='/Prospect-Rankings'; return false;">Prospect Rankings</a> 558 <a href="/team-needs" class="mobile-dropdown-item" onclick="window.top.location.href='/team-needs'; return false;">Team Needs</a> 559 <a href="/full-draft-order" class="mobile-dropdown-item" onclick="window.top.location.href='/full-draft-order'; return false;">Full Draft Order</a> 560 <a href="https://spinwheelai.com" class="mobile-dropdown-item" target="_blank">
560Picker Wheel Random Draft</a> 561 </div> 562 </div> 563 <div class="mobile-nav-dropdown" data-testid="mobile-odds-dropdown"> 564 <button class="mobile-nav-link mobile-dropdown-trigger" data-testid="mobile-odds-btn"> 565 Odds/Bets <span class="mobile-dropdown-arrow">â¼</span> 566 </button> 567 <div class="mobile-dropdown-menu" data-testid="mobile-dropdown-menu-odds"> 568 <a href="/nfl-odds-calculator" class="mobile-dropdown-item" onclick="window.top.location.href='/nfl-odds-calculator'; return false;">Point Spread Calculator</a> 569 </div> 570 </div> 571 <a href="/nfl-report" class="mobile-nav-link" data-testid="mobile-nfl-news" style="font-weight: bold;">NFL News</a> 572 <div class="mobile-nav-dropdown" data-testid="mobile-more-dropdown"> 573 <button class="mobile-nav-link mobile-dropdown-trigger" data-testid="mobile-more-btn"> 574 More <span class="mobile-dropdown-arrow">â¼</span> 575 </button> 576 <div class="mobile-dropdown-menu" data-testid="mobile-dropdown-menu-more"> 577 <a href="/team-picker-wheel.html" class="mobile-dropdown-item" target="_blank" rel="noopener noreferrer">Team Picker Wheel</a> 578 <a href="/ai-game" class="mobile-dropdown-item" onclick="window.top.location.href='/ai-game'; return false;">AI Game Simulator</a> 579 <a href="https://www.nfltriviablitz.com/" class="mobile-dropdown-item" target="_blank">2000's NFL Trivia</a> 580 <a href="#contact" class="mobile-dropdown-item">Contact Us</a> 581 </div> 582 </div> 583 </div> 584 </div> 585 586 <style> 587 .main-header { 588 background: white; 589 border-bottom: 3px solid #dc2626; 590 position: sticky; 591 top: 0; 592 z-index: 1000; 593 box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); 594 } 595 596 .header-wrapper { 597 max-width: 1400px; 598 margin: 0 auto; 599 padding: 0 1rem; 600 } 601 602 /* Desktop Layout */ 603 .header-row-desktop { 604 display: flex; 605 align-items: center; 606 padding: 0.75rem 0; 607 position: relative; 608 } 609 610 .logo-link { 611 display: flex; 612 align-items: center; 613 text-decoration: none; 614 flex-shrink: 0; 615 } 616 617 .logo-section { 618 display: flex; 619 align-items: center; 620 gap: 0.5rem; 621 flex-shrink: 0; 622 } 623 624 .header-logo { 625 height: 52px; 626 width: auto; 627 display: block; 628 } 629 630 .header-brand { 631 font-family: 'Courier New', Courier, monospace; 632 font-size: 16px; 633 font-weight: 900; 634 background: linear-gradient(135deg, #dc2626 0%, #991b1b 50%, #7f1d1d 100%); 635 -webkit-background-clip: text; 636 -webkit-text-fill-color: transparent; 637 background-clip: text; 638 white-space: nowrap; 639 letter-spacing: 0.08em; 640 text-transform: uppercase; 641 text-shadow: 2px 2px 0px rgba(220, 38, 38, 0.2); 642 filter: drop-shadow(1px 1px 2px rgba(0, 0, 0, 0.1)); 643 } 644 645 /* Centered Navigation */ 646 .nav-center { 647 position: absolute; 648 left: 50%; 649 transform: translateX(-50%); 650 display: flex; 651 align-items: center; 652 gap: 1.5rem; 653 } 654 655 .nav-link { 656 font-family: 'Comic Sans MS', 'Comic Sans', cursive; 657 font-size: 14px; 658 color: #374151; 659 text-decoration: none; 660 letter-spacing: 0.08em; 661 padding: 0.3rem 0; 662 transition: all 0.2s ease; 663 white-space: nowrap; 664 border: none; 665 background: none !important; 666 cursor: pointer; 667 position: relative; 668 box-shadow: none !important; 669 text-shadow: none !important; 670 } 671 672 .nav-link:hover { 673 color: #dc2626; 674 background: none !important; 675 box-shadow: none !important; 676 } 677 678 .nav-link.active { 679 color: #dc2626; 680 background: none !important; 681 box-shadow: none !important; 682 } 683 684 .nav-link.active::after { 685 content: ''; 686 position: absolute; 687 bottom: 0; 688 left: 0; 689 right: 0; 690 height: 2px; 691 background-color: #dc2626; 692 } 693 694 /* Dropdown */ 695 .nav-dropdown { 696 position: relative; 697 } 698 699 .dropdown-trigger { 700 display: flex; 701 align-items: center; 702 gap: 0.3rem; 703 } 704 705 .dropdown-arrow { 706 font-size: 8px; 707 color: #000; 708 } 709 710 .dropdown-menu { 711 display: none; 712 position: absolute; 713 top: 100%; 714 left: 50%; 715 transform: translateX(-50%); 716 background: white; 717 border: 1px solid #e5e7eb; 718 border-radius: 6px; 719 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); 720 min-width: 220px; 721 padding: 0.5rem 0; 722 z-index: 1001; 723 margin-top: 0; 724 pointer-events: auto; 725 } 726 727 .nav-dropdown:hover .dropdown-menu, 728 .nav-dropdown .dropdown-menu.show { 729 display: block; 730 } 731 732 .dropdown-item { 733 display: block; 734 font-family: 'Comic Sans MS', 'Comic Sans', cursive; 735 font-size: 14px; 736 color: #374151; 737 text-decoration: none; 738 padding: 0.6rem 1rem; 739 letter-spacing: 0.08em; 740 transition: all 0.2s ease; 741 border-bottom: 1px solid #e5e7eb; 742 } 743 744 .dropdown-item:last-child { 745 border-bottom: none; 746 } 747 748 .dropdown-item:hover { 749 background-color: #f9fafb; 750 color: #dc2626; 751 } 752 753 .dropdown-item.active { 754 color: #dc2626; 755 } 756 757 /* Mobile Layout */ 758 .header-row-mobile { 759 display: none; 760 flex-direction: row; 761 align-items: center; 762 justify-content: space-between; 763 gap: 0.5rem; 764 padding: 1.5rem 1rem; 765 } 766 767 .mobile-logo-link { 768 display: flex; 769 align-items: center; 770 justify-content: center; 771 text-decoration: none; 772 cursor: pointer; 773 flex-shrink: 0; 774 } 775 776 .mobile-header-logo { 777 height: 64px; 778 width: auto; 779 display: block; 780 } 781 782 .mobile-nav-link { 783 font-family: 'Comic Sans MS', 'Comic Sans', cursive; 784 font-size: 22px; 785 color: #374151; 786 text-decoration: none; 787 letter-spacing: 0.05em; 788 padding: 0.5rem 0; 789 transition: all 0.2s ease; 790 white-space: nowrap; 791 border: none !important; 792 background: none !important; 793 background-color: transparent !important; 794 box-shadow: none !important; 795 outline: none !important; 796 cursor: pointer; 797 position: relative; 798 -webkit-appearance: none; 799 -moz-appearance: none; 800 appearance: none; 801 } 802 803 .mobile-nav-link:hover { 804 color: #dc2626; 805 background: none !important; 806 background-color: transparent !important; 807 } 808 809 .mobile-nav-link:focus { 810 background: none !important; 811 background-color: transparent !important; 812 box-shadow: none !important; 813 outline: none !important; 814 } 815 816 .mobile-nav-link.active { 817 color: #dc2626; 818 text-decoration: underline; 819 text-underline-offset: 3px; 820 } 821 822 /* Mobile Dropdown */ 823 .mobile-nav-dropdown { 824 position: relative; 825 } 826 827 .mobile-dropdown-trigger { 828 display: flex; 829 align-items: center; 830 gap: 0.2rem; 831 } 832 833 .mobile-dropdown-arrow { 834 font-size: 14px; 835 color: #00338D; 836 } 837 838 .mobile-dropdown-menu { 839 display: none; 840 position: absolute; 841 top: 100%; 842 left: 0; 843 background: white; 844 border: 1px solid #e5e7eb; 845 border-radius: 6px; 846 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); 847 min-width: 240px; 848 max-width: none; 849 width: auto; 850 padding: 0.5rem 0; 851 z-index: 1001; 852 margin-top: 0; 853 pointer-events: auto; 854 } 855 856 .mobile-nav-dropdown:last-child .mobile-dropdown-menu { 857 left: auto; 858 right: 0; 859 } 860 861 .mobile-nav-dropdown:hover .mobile-dropdown-menu, 862 .mobile-nav-dropdown:focus-within .mobile-dropdown-menu { 863 display: block; 864 } 865 866 .mobile-dropdown-item { 867 display: block; 868 font-family: 'Comic Sans MS', 'Comic Sans', cursive; 869 font-size: 15px; 870 color: #374151; 871 text-decoration: none; 872 padding: 0.75rem 1.25rem; 873 letter-spacing: 0.05em; 874 transition: all 0.2s ease; 875 border-bottom: 1px solid #e5e7eb; 876 white-space: nowrap; 877 } 878 879 .mobile-dropdown-item:last-child { 880 border-bottom: none; 881 } 882 883 .mobile-dropdown-item:hover { 884 background-color: #f9fafb; 885 color: #dc2626; 886 } 887 888 .mobile-dropdown-menu .mobile-dropdown-item { 889 color: #00338D; 890 } 891 892 .mobile-nav-dropdown[data-testid="mobile-odds-dropdown"] .mobile-dropdown-item:first-child { 893 color: #dc2626; 894 font-weight: bold; 895 } 896 897 .mobile-dropdown-divider-thick { 898 height: 3px; 899 background-color: #000; 900 margin: 0; 901 } 902 903 /* Tablet and Mobile */ 904 @media (max-width: 1024px) { 905 .header-logo { 906 height: 32px; 907 } 908 909 .header-brand { 910 font-size: 13px; 911 } 912 913 .nav-link { 914 font-size: 12px; 915 } 916
917 .nav-center { 918 gap: 1rem; 919 } 920 } 921 922 @media (max-width: 768px) { 923 .header-row-desktop { 924 display: none; 925 } 926 927 .header-row-mobile { 928 display: flex; 929 gap: 0.5rem; 930 padding: 1.25rem 0.75rem; 931 justify-content: space-between; 932 } 933 934 .mobile-header-logo { 935 height: 48px; 936 } 937 938 .mobile-nav-link { 939 font-size: 18px; 940 } 941 } 942 943 @media (max-width: 480px) { 944 .header-wrapper { 945 padding: 0 0.5rem; 946 } 947 948 .header-row-mobile { 949 gap: 0.4rem; 950 padding: 1rem 0.5rem; 951 justify-content: space-between; 952 } 953 954 .mobile-header-logo { 955 height: 40px; 956 } 957 958 .mobile-nav-link { 959 font-size: 14px; 960 letter-spacing: 0.02em; 961 } 962 963 .mobile-dropdown-arrow { 964 font-size: 10px; 965 } 966 967 .mobile-dropdown-item { 968 font-size: 14px; 969 padding: 0.7rem 1.1rem; 970 white-space: nowrap; 971 } 972 } 973 </style> 974 </nav> 975 976 <script> 977 // Dropdown behavior: stay open on hover, close on mouseleave 978 document.querySelectorAll('.nav-dropdown').forEach(dropdown => { 979 const trigger = dropdown.querySelector('.dropdown-trigger'); 980 const menu = dropdown.querySelector('.dropdown-menu'); 981 982 if (!menu) return; 983 984 // Show menu on hover over dropdown 985 dropdown.addEventListener('mouseenter', () => { 986 menu.style.display = 'block'; 987 }); 988 989 // Hide menu when mouse leaves dropdown 990 dropdown.addEventListener('mouseleave', () => { 991 menu.style.display = 'none'; 992 }); 993 }); 994 995 // Mobile dropdown behavior 996 document.querySelectorAll('.mobile-nav-dropdown').forEach(dropdown => { 997 const trigger = dropdown.querySelector('.mobile-dropdown-trigger'); 998 const menu = dropdown.querySelector('.mobile-dropdown-menu'); 999 1000 if (!menu) return; 1001 1002 dropdown.addEventListener('mouseenter', () => { 1003 menu.style.display = 'block'; 1004 }); 1005 1006 dropdown.addEventListener('mouseleave', () => { 1007 menu.style.display = 'none'; 1008 }); 1009 }); 1010 <\/script> 1011 1012 <style> 1013 /* Additional page styles follow */ 1014 1015 .header-link-mobile.active { 1016 color: #dc2626; 1017 font-weight: 600; 1018 } 1019 1020 .pipe-divider-mobile { 1021 color: #d1d5db; 1022 font-weight: 300; 1023 margin: 0 0.15rem; 1024 flex-shrink: 0; 1025 } 1026 1027 /* Tablet and Mobile */ 1028 @media (max-width: 1024px) { 1029 .header-logo { 1030 height: 32px; 1031 } 1032 1033 .header-brand { 1034 font-size: 13px; 1035 } 1036 1037 .header-link { 1038 font-size: 11px; 1039 } 1040 1041 .pipe-divider { 1042 margin: 0 0.15rem; 1043 } 1044 } 1045 1046 @media (max-width: 768px) { 1047 .header-row-desktop { 1048 display: none; 1049 } 1050 1051 .header-row-mobile { 1052 display: flex; 1053 } 1054 1055 .mobile-logo-section { 1056 display: flex; 1057 } 1058 1059 .mobile-header-logo { 1060 height: 28px; 1061 } 1062 1063 .mobile-header-brand { 1064 font-size: 12px; 1065 font-weight: 900; 1066 letter-spacing: 0.08em; 1067 } 1068 1069 .header-link-mobile { 1070 font-size: 11px; 1071 } 1072 1073 .pipe-divider-mobile { 1074 margin: 0 0.1rem; 1075 } 1076 } 1077 1078 @media (max-width: 480px) { 1079 .header-wrapper { 1080 padding: 0 0.5rem; 1081 } 1082 1083 .mobile-header-logo { 1084 height: 24px; 1085 } 1086 1087 .mobile-header-brand { 1088 font-size: 11px; 1089 font-weight: 900; 1090 letter-spacing: 0.08em; 1091 } 1092 1093 .header-link-mobile { 1094 font-size: 10px; 1095 } 1096 1097 .pipe-divider-mobile { 1098 font-size: 10px; 1099 margin: 0 0.08rem; 1100 } 1101 1102 .mobile-line { 1103 gap: 0.2rem; 1104 } 1105 } 1106 </style> 1107 </nav> 1108 1109 <!-- Current NFL Power Rankings - January 17, 2027 --> 1110 <div aria-hidden="true" style="display: none;"> 1111 <h2>Current NFL Power Rankings 2025</h2> 1112 <p>This page contains the current NFL power rankings updated weekly using our proprietary Win Quality Index (WQI) system. Teams are ranked 1-32 based on performance and strength of schedule.</p> 1113 <ul> 1114 <li>1. Seahawks</li> 1115 <li>2. Jaguars</li> 1116 <li>3. Texans</li> 1117 <li>4. Rams</li> 1118 <li>5. Broncos</li> 1119 <li>6. Patriots</li> 1120 <li>7. Bills</li> 1121 <li>8. 49ers</li> 1122 <li>9. Eagles</li> 1123 <li>10. Steelers</li> 1124 <li>11. Vikings</li> 1125 <li>12. Chargers</li> 1126 <li>13. Panthers</li> 1127 <li>14. Buccaneers</li> 1128 <li>15. Bears</li> 1129 <li>16. Colts</li> 1130 <li>17. Falcons</li> 1131 <li>18. Lions</li> 1132 <li>19. Packers</li> 1133 <li>20. Ravens</li> 1134 <li>21. Saints</li> 1135 <li>22. Dolphins</li> 1136 <li>23. Cowboys</li> 1137 <li>24. Bengals</li> 1138 <li>25. Titans</li> 1139 <li>26. Chiefs</li> 1140 <li>27. Browns</li> 1141 <li>28. Giants</li> 1142 <li>29. Cardinals</li> 1143 <li>30. Raiders</li> 1144 <li>31. Jets</li> 1145 <li>32. Commanders</li> 1146 </ul> 1147 </div> 1148 <!-- END: Current NFL Power Rankings --> 1149 1150 <div class="content" style="padding: 0; margin-top: 0;"> 1151 <div class="container"> 1152 <div style="width: 100%; max-width: 800px;"> 1153 <div class="grid" id="teamGrid"></div> 1154 <div id="wqi-description" style="text-align: center; max-width: 800px; margin: 20px auto 20px; font-size: 0.88rem; color: #555; line-height: 1.6; padding: 0 20px;"> 1155 <p>Our proprietary <strong>Win Quality Index (WQI)</strong> calculates NFL point spreads and powers our WQI analysis. Drag teams to customize rankings & calculate spreads for any matchup. Rankings are updated 12 hours before game time.</p> 1156 </div> 1157 <div class="copyright" style="margin-top: 20px; padding-top: 20px;
1157 border-top: 4px solid #000;"> 1158 © 2025 <a href="https://nfldrafthq.com" target="_blank">NFLDraftHQ.com</a> · Point spreads are for entertainment purposes only 1159 </div> 1160 </div> 1161 <div class="controls"> 1162 <div id="instructionContainer" style="text-align: center; margin-bottom: 20px;"> 1163 <h3 id="instructionText" class="instruction-text flash-border">Click/Drag or Double Click<br>the teams to calculate odds</h3> 1164 </div> 1165 <div class="team-container"> 1166 <div class="selected-team" id="awayDrop"> 1167 <div class="default-text"><strong>Away</strong></div> 1168 </div> 1169 <div class="at-sign">@</div> 1170 <div class="selected-team" id="homeDrop"> 1171 <div class="default-text"><strong>Home</strong></div> 1172 </div> 1173 </div> 1174 <div style="display: flex; gap: 10px; margin-bottom: 15px;"> 1175 <button id="calculateBtn" onclick="calculateOdds()" disabled style="flex: 1; padding: 8px 16px; font-size: 0.8em;">Calculate Spread</button> 1176 <button id="clearBtn" onclick="clearTeams()" disabled style="flex: 1; padding: 8px 16px; font-size: 0.8em;">Clear Teams</button> 1177 </div> 1178 <div id="result" style="margin-bottom: 15px; min-height: 30px;"></div> 1179 <div class="neutral-check" style="display: flex; align-items: center; justify-content: space-between;"> 1180 <label><input type="checkbox" id="neutralSite"> Neutral Site</label> 1181 <button class="how-it-works-btn" onclick="showHowItWorks()">How it Works</button> 1182 </div> 1183 <!-- Commented out for potential future restoration 1184 <div class="injury-check"> 1185 <label id="homeInjuryLabel"><input type="checkbox" id="homeInjury"> Home team injury concerns</label> 1186 </div> 1187 <div class="injury-check"> 1188 <label id="awayInjuryLabel"><input type="checkbox" id="awayInjury"> Away team injury concerns</label> 1189 </div> 1190 <div class="injury-check"> 1191 <label id="positiveHomeMomentumLabel"><input type="checkbox" id="positiveHomeMomentum"> Positive Home team momentum</label> 1192 </div> 1193 <div class="injury-check"> 1194 <label id="positiveAwayMomentumLabel"><input type="checkbox" id="positiveAwayMomentum"> Positive Away team momentum</label> 1195 </div> 1196 <div class="injury-check"> 1197 <label id="negativeHomeMomentumLabel"><input type="checkbox" id="negativeHomeMomentum"> Negative Home team momentum</label> 1198 </div> 1199 <div class="injury-check"> 1200 <label id="negativeAwayMomentumLabel"><input type="checkbox" id="negativeAwayMomentum"> Negative Away team momentum</label> 1201 </div> 1202 --> 1203 </div> 1204 </div> 1205 </div> 1206 1207 <!-- How it Works Modal --> 1208 <div id="howItWorksModal" class="modal"> 1209 <div class="modal-content" id="modalContent"> 1210 <span class="close-modal" onclick="closeHowItWorks()">×</span> 1211 <div class="scroll-elevator" id="scrollElevator" style="display: none;"> 1212 <button class="scroll-btn" onclick="scrollModalUp()" title="Scroll Up">â²</button> 1213 <button class="scroll-btn" onclick="scrollModalDown()" title="Scroll Down">â¼</button> 1214 </div> 1215 <h2>How the NFL Odds Calculator Works</h2> 1216 1217 <p><strong>Power Rankings System:</strong></p> 1218 <ul> 1219 <li><strong>HQ Power Rankings:</strong> Our default rankings are dynamically updated based on the Win Quality Index (WQI), a proprietary metric that evaluates team performance by analyzing the strength and quality of wins/losses throughout the season.</li> 1220 <li><strong>Custom Rankings:</strong> You have full control to create your own power rankings by simply dragging and dropping team cards into your preferred order. Your custom arrangement will be used to calculate point spreads.</li> 1221 </ul> 1222 1223 <p><strong>Calculating Odds:</strong></p> 1224 <p>To generate a point spread prediction:</p> 1225 <ul> 1226 <li>Select an away team and a home team by dragging or double-clicking the team cards</li> 1227 <li>Click "Calculate Spread" to view the predicted point spread</li> 1228 <li>Toggle "Neutral Site" if the game is played at a neutral location</li> 1229 </ul> 1230 1231 <p><strong>Win Quality Index (WQI):</strong></p> 1232 <p>The WQI is a sophisticated algorithm that weights wins/losses based on opponent strength. HQ rankings are automatically refreshed to reflect the most current team performance.</p> 1233 1234 <p style="font-style: italic; color: #666; margin-top: 20px;">Note: Point spreads are for entertainment purposes only.</p> 1235 </div> 1236 </div> 1237 1238 <script> 1239 function showHowItWorks() { 1240 const modal = document.getElementById('howItWorksModal'); 1241 const modalContent = document.getElementById('modalContent'); 1242 const scrollElevator = document.getElementById('scrollElevator'); 1243 1244 modal.style.display = 'block'; 1245 1246 // Check if modal content is scrollable and show/hide elevator 1247 setTimeout(() => { 1248 if (modalContent.scrollHeight > modalContent.clientHeight) { 1249 scrollElevator.style.display = 'flex'; 1250 updateScrollElevatorPosition(); 1251 } else { 1252 scrollElevator.style.display = 'none'; 1253 } 1254 }, 100); 1255 1256 // Add scroll listener to update elevator position 1257 modalContent.addEventListener('scroll', updateScrollElevatorPosition); 1258 } 1259 1260 function closeHowItWorks() { 1261 const modal = document.getElementById('howItWorksModal'); 1262 const modalContent = document.getElementById('modalContent'); 1263 const scrollElevator = document.getElementById('scrollElevator'); 1264 1265 modal.style.display = 'none'; 1266 scrollElevator.style.display = 'none'; 1267 modalContent.scrollTop = 0; 1268 modalContent.removeEventListener('scroll', updateScrollElevatorPosition); 1269 } 1270 1271 function scrollModalUp() { 1272 const modalContent = document.getElementById('modalContent'); 1273 modalContent.scrollBy({ top: -100, behavior: 'smooth' }); 1274 } 1275 1276 function scrollModalDown() { 1277 const modalContent = document.getElementById('modalContent'); 1278 modalContent.scrollBy({ top: 100, behavior: 'smooth' }); 1279 } 1280 1281 function updateScrollElevatorPosition() { 1282 const modalContent = document.getElementById('modalContent'); 1283 const scrollElevator = document.getElementById('scrollElevator'); 1284 const modalRect = modalContent.getBoundingClientRect(); 1285 1286 // Position the elevator on the right side of the modal, vertically centered 1287 const centerY = modalRect.top + (modalRect.height / 2) - 50; 1288 scrollElevator.style.top = centerY + 'px'; 1289 } 1290 1291 // Close modal when clicking outside of it 1292 window.onclick = function(event) { 1293 const modal = document.getElementById('howItWorksModal'); 1294 if (event.target === modal) { 1295 closeHowItWorks(); 1296 } 1297 } 1298 const teams = [ 1299 { name: 'Seahawks', primary: '#002244', secondary: '#69BE28' }, 1300 { name: 'Jaguars', primary: '#006778', secondary: '#D7A22A' }, 1301 { name: 'Texans', primary: '#03202F', secondary: '#A71930' }, 1302 { name: 'Rams', primary: '#003594', secondary: '#FFA300' }, 1303 { name: 'Broncos', primary: '#FB4F14', secondary: '#002244' }, 1304 { name: 'Patriots', primary: '#002244', secondary: '#C60C30' }, 1305 { name: 'Bills', primary: '#00338D', secondary: '#C60C30' }, 1306 { name: '49ers', primary: '#AA0000', secondary: '#B3995D' }, 1307 { name: 'Eagles', primary: '#004C54', secondary: '#A5ACAF' }, 1308 { name: 'Steelers', primary: '#FFB612', secondary: '#000000' }, 1309 { name: 'Vikings', primary: '#4F2683', secondary: '#FFC62F' }, 1310 { name: 'Chargers', primary: '#0080C6', secondary: '#FFC20E' }, 1311 { name: 'Panthers', primary: '#0085CA', secondary: '#101820' }, 1312 { name: 'Buccaneers', primary: '#D50A0A', secondary: '#FF7900' }, 1313 { name: 'Bears', primary: '#0B162A', secondary: '#C83803' }, 1314 { name: 'Colts', primary: '#002C5F', secondary: '#A2AAAD' }, 1315 { name: 'Falcons', primary: '#A71930', secondary: '#000000' }, 1316 { name: 'Lions', primary: '#0076B6', secondary: '#B0B7BC' }, 1317 { name: 'Packers', primary: '#203731', secondary: '#FFB612' }, 1318 { name: 'Ravens', primary: '#241773', secondary: '#9E7C0C' }, 1319 { name: 'Saints', primary: '#D3BC8D', secondary: '#101820' }, 1320 { name: 'Dolphins', primary: '#008E97', secondary: '#FC4C02' }, 1321 { name: 'Cowboys', primary: '#041E42', secondary: '#869397' }, 1322 { name: 'Bengals', primary: '#FB4F14', secondary: '#000000' }, 1323 { name: 'Titans', primary: '#0C2340', secondary: '#4B92DB' }, 1324 { name: 'Chiefs', primary: '#E31837', secondary: '#FFB81C' }, 1325 { name: 'Browns', primary: '#311D00', secondary: '#FF3C00' }, 1326 { name: 'Giants', primary: '#0B2265', secondary: '#A71930' }, 1327 { name: 'Cardinals', primary: '#97233F', secondary: '#000000' }, 1328 { name: 'Raiders', primary: '#000000', secondary: '#A5ACAF' }, 1329 { name: 'Jets', primary: '#125740', secondary: '#000000' }, 1330 { name: 'Commanders', primary: '#5A1414', secondary: '#FFB612' }, 1331 ]; 1332 1333 let draggedItem = null; 1334 let awayTeam = null; 1335 let homeTeam = null; 1336 let offenseRanks = {}; 1337 let defenseRanks = {}; 1338 const darkColors = ['#000000', '#041E42', '#002244', '#101820', '#03202F', '#0B162A', '#0C2340', '#311D00', '#002C5F', '#125740', '#4F2683', '#241773', '#203731', '#006778', '#004C54']; 1339 1340 // Load live power rankings from Google Sheets API 1341 async function loadLivePowerRankings() { 1342 // Show loading state 1343 const grid = document.getElementById('teamGrid'); 1344 grid.innerHTML = '<div style="text-align: center; padding: 40px; color: #666; font-size: 18px;">Loading Power Rankings...</div>'; 1345 1346 try { 1347 // Get the base URL for the parent window 1348 const baseUrl = window.parent.location.origin; 1349 1350 // Fetch live power rankings from our API with cache busting 1351 const response = await fetch(\`\${baseUrl}/api/power-rankings?t=\${Date.now()}\`, { 1352 cache: 'no-store', 1353 headers: { 1354 'Cache-Control': 'no-cache', 1355 'Pragma': 'no-cache' 1356 } 1357 }); 1358 1359 if (!response.ok) { 1360 throw new Error(\`Failed to fetch power rankings: \${response.status}\`); 1361 } 1362 1363 const powerRankings = await response.json(); 1364 1365 console.log('Live power rankings fetched:', powerRankings.length, 'teams'); 1366 1367 if (powerRankings && powerRankings.length > 0) { 1368 // Update team order based on live power rankings 1369 updateTeamOrderFromPowerRankings(powerRankings); 1370 // Also load offense/defense rankings separately for odds calculations 1371 loadOffenseDefenseRankings(); 1372 } else { 1373 throw new Error('No power rankings data received'); 1374 } 1375 } catch (error) { 1376 console.error('Error loading live power rankings:', error); 1377 // Show error message instead of fallback grid 1378 grid.innerHTML = \` 1379 <div style="text-align: center; padding: 40px; color: #d32f2f;"> 1380 <div style="font-size: 18px; margin-bottom: 10px;">â ï¸ Failed to load Power Rankings</div> 1381 <div style="font-size: 14px; margin-bottom: 20px;">Unable to fetch live data from Google Sheets</div> 1382 <button onclick="location.reload()" style="padding: 10px 20px; background: #007bff; color: white; border: none; border-radius: 5px; cursor: pointer;"> 1383 Retry 1384 </button> 1385 </div> 1386 \`; 1387 // Still attempt to load offense/defense rankings for calculations 1388 loadOffenseDefenseRankings(); 1389 } 1390 } 1391 1392 // Update team order based on power rankings 1393 function updateTeamOrderFromPowerRankings(powerRankings) { 1394 // Create a mapping from team name to rank 1395 const rankMap = {};
1396 powerRankings.forEach(ranking => { 1397 // API already returns short names, use them directly 1398 rankMap[ranking.team] = ranking.rank; 1399 }); 1400 1401 console.log('Rank mapping:', rankMap); 1402 1403 // Sort teams array based on power rankings 1404 teams.sort((a, b) => { 1405 const rankA = rankMap[a.name] || 99; 1406 const rankB = rankMap[b.name] || 99; 1407 return rankA - rankB; 1408 }); 1409 1410 console.log('Teams reordered by power rankings'); 1411 // Refresh the grid display 1412 initializeGrid(); 1413 } 1414 1415 // Convert full team names to short names used in the interface 1416 function getShortTeamName(fullName) { 1417 const nameMap = { 1418 'Philadelphia Eagles': 'Eagles', 1419 'Kansas City Chiefs': 'Chiefs', 1420 'Baltimore Ravens': 'Ravens', 1421 'Buffalo Bills': 'Bills', 1422 'Detroit Lions': 'Lions', 1423 'Washington Commanders': 'Commanders', 1424 'Los Angeles Rams': 'Rams', 1425 'San Francisco 49ers': '49ers', 1426 'Green Bay Packers': 'Packers', 1427 'Minnesota Vikings': 'Vikings', 1428 'Los Angeles Chargers': 'Chargers', 1429 'Cincinnati Bengals': 'Bengals', 1430 'Tampa Bay Buccaneers': 'Buccaneers', 1431 'Denver Broncos': 'Broncos', 1432 'Dallas Cowboys': 'Cowboys', 1433 'Houston Texans': 'Texans', 1434 'Chicago Bears': 'Bears', 1435 'Pittsburgh Steelers': 'Steelers', 1436 'Seattle Seahawks': 'Seahawks', 1437 'Arizona Cardinals': 'Cardinals', 1438 'Miami Dolphins': 'Dolphins', 1439 'New Orleans Saints': 'Saints', 1440 'Tennessee Titans': 'Titans', 1441 'New York Jets': 'Jets', 1442 'New England Patriots': 'Patriots', 1443 'Indianapolis Colts': 'Colts', 1444 'Jacksonville Jaguars': 'Jaguars', 1445 'Las Vegas Raiders': 'Raiders', 1446 'Atlanta Falcons': 'Falcons', 1447 'Cleveland Browns': 'Browns', 1448 'Carolina Panthers': 'Panthers', 1449 'New York Giants': 'Giants' 1450 }; 1451 return nameMap[fullName] || null; 1452 } 1453 1454 // Load team offense and defense rankings for odds calculations 1455 async function loadOffenseDefenseRankings() { 1456 try { 1457 // Get the base URL for the parent window 1458 const baseUrl = window.parent.location.origin; 1459 1460 // Load offense rankings from CSV (keep this for now) 1461 const offenseResponse = await fetch(\`\${baseUrl}/team-offense-ranks.csv\`); 1462 const offenseText = await offenseResponse.text(); 1463 const offenseLines = offenseText.split('\\n').slice(1); // Skip header 1464 offenseLines.forEach(line => { 1465 const [rank, team] = line.split(','); 1466 if (rank && team) { 1467 offenseRanks[team.trim()] = parseInt(rank); 1468 } 1469 }); 1470 1471 // Load defense rankings from CSV (keep this for now) 1472 const defenseResponse = await fetch(\`\${baseUrl}/team-defense-ranks.csv\`); 1473 const defenseText = await defenseResponse.text(); 1474 const defenseLines = defenseText.split('\\n').slice(1); // Skip header 1475 defenseLines.forEach(line => { 1476 const [rank, team] = line.split(','); 1477 if (rank && team) { 1478 defenseRanks[team.trim()] = parseInt(rank); 1479 } 1480 }); 1481 1482 console.log('Offense rankings loaded:', Object.keys(offenseRanks).length, 'teams'); 1483 console.log('Defense rankings loaded:', Object.keys(defenseRanks).length, 'teams'); 1484 } catch (error) { 1485 console.error('Error loading offense/defense rankings:', error); 1486 console.log('Failed to load offense/defense rankings - will use empty data until available'); 1487 // Removed hardcoded fallback - only use Google Sheets data 1488 } 1489 } 1490 1491 1492 1493 function validateSelections() { 1494 const calculateBtn = document.getElementById('calculateBtn'); 1495 calculateBtn.disabled = !(awayTeam && homeTeam && awayTeam !== homeTeam); 1496 } 1497 1498 function updateCheckboxColors() { 1499 const instructionText = document.getElementById('instructionText'); 1500 1501 // Home team label IDs 1502 const homeLabels = ['homeInjuryLabel', 'positiveHomeMomentumLabel', 'negativeHomeMomentumLabel']; 1503 1504 // Update home team label colors using primary color 1505 if (homeTeam) { 1506 const homeTeamData = teams.find(t => t.name === homeTeam); 1507 if (homeTeamData) {
1508 homeLabels.forEach(labelId => { 1509 const label = document.getElementById(labelId); 1510 if (label) { 1511 label.style.color = homeTeamData.primary; 1512 label.style.fontWeight = 'bold'; 1513 } 1514 }); 1515 } 1516 } else { 1517 homeLabels.forEach(labelId => { 1518 const label = document.getElementById(labelId); 1519 if (label) { 1520 label.style.color = ''; 1521 label.style.fontWeight = ''; 1522 } 1523 }); 1524 } 1525 1526 // Away team label IDs 1527 const awayLabels = ['awayInjuryLabel', 'positiveAwayMomentumLabel', 'negativeAwayMomentumLabel']; 1528 1529 // Update away team label colors using primary color 1530 if (awayTeam) { 1531 const awayTeamData = teams.find(t => t.name === awayTeam); 1532 if (awayTeamData) {
1533 awayLabels.forEach(labelId => { 1534 const label = document.getElementById(labelId); 1535 if (label) { 1536 label.style.color = awayTeamData.primary; 1537 label.style.fontWeight = 'bold'; 1538 } 1539 }); 1540 } 1541 } else { 1542 awayLabels.forEach(labelId => { 1543 const label = document.getElementById(labelId); 1544 if (label) { 1545 label.style.color = ''; 1546 label.style.fontWeight = ''; 1547 } 1548 }); 1549 } 1550 1551 // Keep the flashing border always visible on instruction text 1552 instructionText.classList.add('flash-border'); 1553 } 1554 1555 function disableTeamButton(teamName) { 1556 const buttons = document.querySelectorAll('.team-btn'); 1557 buttons.forEach(btn => { 1558 const teamData = JSON.parse(btn.dataset.team); 1559 if (teamData.name === teamName) { 1560 // Get the text color of the team card 1561 const textColor = btn.style.color; 1562 btn.style.border = '4px solid ' + textColor; 1563 btn.style.pointerEvents = 'none'; 1564 btn.style.cursor = 'not-allowed'; 1565 } 1566 }); 1567 } 1568 1569 function enableAllTeamButtons() { 1570 const buttons = document.querySelectorAll('.team-btn'); 1571 buttons.forEach(btn => { 1572 const teamData = JSON.parse(btn.dataset.team); 1573 btn.style.border = '1px solid #1d4ed8'; 1574 btn.style.pointerEvents = 'auto'; 1575 btn.style.cursor = 'grab'; 1576 }); 1577 } 1578 1579 function highlightTeamButton(teamName) { 1580 const buttons = document.querySelectorAll('.team-btn'); 1581 buttons.forEach(btn => { 1582 const teamData = JSON.parse(btn.dataset.team); 1583 if (teamData.name === teamName) { 1584 const textColor = btn.style.color; 1585 btn.style.border = '4px solid ' + textColor; 1586 } 1587 }); 1588 } 1589 1590 function initializeGrid() { 1591 console.log('Initializing grid with', teams.length, 'teams'); 1592 const grid = document.getElementById('teamGrid'); 1593 grid.innerHTML = ''; 1594 teams.forEach((team, index) => { 1595 const btn = document.createElement('div'); 1596 btn.className = 'team-btn'; 1597 btn.draggable = true; 1598 btn.innerHTML = \` 1599 \${team.name} 1600 <hr class="rank-separator"> 1601 <small>Rank: \${index + 1}</small> 1602 \`; 1603 btn.style.backgroundColor = team.primary; 1604 btn.style.color = darkColors.includes(team.secondary) ? '#FFFFFF' : team.secondary; 1605 btn.style.borderColor = team.secondary; 1606 1607 if(team.name === 'Commanders') { 1608 btn.style.justifyContent = 'space-evenly'; 1609 } 1610 1611 const nameLength = team.name.length; 1612 btn.style.fontSize = nameLength <= 4 ? '16px' : 1613 nameLength <= 8 ? '14px' : '12px'; 1614 1615 btn.dataset.team = JSON.stringify(team); 1616 1617 btn.addEventListener('dragstart', handleDragStart); 1618 btn.addEventListener('dragover', handleDragOver); 1619 btn.addEventListener('drop', handleGridDrop); 1620 btn.addEventListener('dblclick', () => { 1621 // Check if button is disabled 1622 if (btn.style.pointerEvents === 'none') { 1623 return; // Don't allow selection if disabled 1624 } 1625 1626 const teamData = JSON.parse(btn.dataset.team); 1627 let dropZone = null; 1628 if (!awayTeam) { 1629 dropZone = document.getElementById('awayDrop'); 1630 awayTeam = teamData.name; 1631 // Disable this team button and add border 1632 disableTeamButton(teamData.name); 1633 } else if (!homeTeam) { 1634 dropZone = document.getElementById('homeDrop'); 1635 homeTeam = teamData.name; 1636 // Highlight home team button with border 1637 highlightTeamButton(teamData.name); 1638 } 1639 if (dropZone) { 1640 dropZone.style.backgroundColor = teamData.primary; 1641 dropZone.style.color = darkColors.includes(teamData.secondary) ? '#FFFFFF' : teamData.secondary; 1642 dropZone.innerHTML = \` 1643 <div class="team-display" 1644 style="width: 100%; 1645 height: 100%; 1646 display: flex; 1647 align-items: center; 1648 justify-content: center;"> 1649 \${teamData.name} 1650 </div> 1651 \`; 1652 document.getElementById('result').textContent = ''; 1653 document.getElementById('clearBtn').disabled = false;
1654 validateSelections(); 1655 updateCheckboxColors(); 1656 } 1657 }); 1658 grid.appendChild(btn); 1659 }); 1660 1661 document.querySelectorAll('.selected-team').forEach(zone => { 1662 zone.dataset.original = zone.innerHTML; 1663 zone.addEventListener('dragover', handleDragOver); 1664 zone.addEventListener('drop', handleTeamDrop); 1665 }); 1666 } 1667 1668 function handleDragStart(e) { 1669 draggedItem = this; 1670 e.dataTransfer.setData('text/plain', this.dataset.team); 1671 } 1672 1673 function handleDragOver(e) { 1674 e.preventDefault(); 1675 } 1676 1677 function handleGridDrop(e) { 1678 e.preventDefault(); 1679 if (draggedItem !== this) { 1680 const grid = this.parentNode; 1681 const items = Array.from(grid.children); 1682 const oldIndex = items.indexOf(draggedItem); 1683 const newIndex = items.indexOf(this); 1684 1685 if (oldIndex < newIndex) { 1686 grid.insertBefore(draggedItem, this.nextSibling); 1687 } else { 1688 grid.insertBefore(draggedItem, this); 1689 } 1690 updateRanks(); 1691 } 1692 } 1693 1694 function handleTeamDrop(e) { 1695 e.preventDefault(); 1696 const team = JSON.parse(e.dataTransfer.getData('text/plain')); 1697 const dropZone = e.target.closest('.selected-team'); 1698 1699 dropZone.style.backgroundColor = team.primary; 1700 dropZone.style.color = darkColors.includes(team.secondary) ? '#FFFFFF' : team.secondary; 1701 1702 dropZone.innerHTML = \` 1703 <div class="team-display" 1704 style="width: 100%; 1705 height: 100%; 1706 display: flex; 1707 align-items: center; 1708 justify-content: center;"> 1709 \${team.name} 1710 </div> 1711 \`; 1712 1713 if (dropZone.id === 'awayDrop') { 1714 awayTeam = team.name; 1715 // Disable this team button and add border 1716 disableTeamButton(team.name); 1717 } 1718 if (dropZone.id === 'homeDrop') { 1719 homeTeam = team.name; 1720 // Highlight home team button with border 1721 highlightTeamButton(team.name); 1722 } 1723 1724 document.getElementById('result').textContent = ''; 1725 document.getElementById('clearBtn').disabled = false; 1726 validateSelections(); 1727 updateCheckboxColors(); 1728 } 1729 1730 function updateRanks() { 1731 document.querySelectorAll('.team-btn').forEach((btn, index) => { 1732 btn.querySelector('small').textContent = \`Rank: \${index + 1}\`; 1733 }); 1734 } 1735 1736 function clearTeams() { 1737 awayTeam = null; 1738 homeTeam = null; 1739 document.querySelectorAll('.selected-team').forEach(zone => { 1740 zone.style.backgroundColor = '#666'; 1741 zone.style.color = 'white'; 1742 zone.innerHTML = zone.dataset.original; 1743 }); 1744 document.getElementById('result').textContent = ''; 1745 document.getElementById('neutralSite').checked = false; 1746 // Commented out checkboxes - keep for potential future restoration 1747 // document.getElementById('homeInjury').checked = false; 1748 // document.getElementById('awayInjury').checked = false; 1749 // document.getElementById('positiveHomeMomentum').checked = false; 1750 // document.getElementById('negativeHomeMomentum').checked = false; 1751 // document.getElementById('positiveAwayMomentum').checked = false; 1752 // document.getElementById('negativeAwayMomentum').checked = false; 1753 document.getElementById('clearBtn').disabled = true; 1754 // Re-enable all team buttons 1755 enableAllTeamButtons(); 1756 validateSelections(); 1757 updateCheckboxColors(); 1758 } 1759 1760 async function calculateOdds() { 1761 if (!awayTeam || !homeTeam || awayTeam === homeTeam) return; 1762 1763 const resultDiv = document.getElementById('result'); 1764 resultDiv.textContent = 'Calculating...'; 1765 1766 // Wait 2 seconds before displaying the spread 1767 await new Promise(resolve => setTimeout(resolve, 2000)); 1768 1769 const awayRank = getCurrentRank(awayTeam); 1770 const homeRank = getCurrentRank(homeTeam); 1771 const rankDiff = awayRank - homeRank;
1772 const k = 0.5; 1773 const BASE_HOME_ADV = 2.0; 1774 const ADJUSTMENT = 0; 1775 const MAX_SPREAD = 18.5; 1776 const ABSOLUTE_MAX = 20.5; 1777 const INJURY_AWAY = 1.4; 1778 const INJURY_HOME = 0.91; 1779 1780 let baseSpread = k * rankDiff; 1781 let spread = baseSpread + BASE_HOME_ADV; 1782 1783 // NEW RULE: When away team is 1 rank higher than home team, home team favored by 3 1784 // This overrides all other conditions except injury and neutral site 1785 if (awayRank === homeRank - 1) { 1786 spread = 3.0; // Home team favored by 3 when they're ranked 1 position lower 1787 } else { 1788 // Apply rank 1 adjustment 1789 if (homeRank === 1) { 1790 spread += 1.5; // Makes spread more positive, favoring home rank 1 1791 } else if (awayRank === 1) { 1792 spread -= 1.5; // Makes spread more negative, favoring away rank 1 1793 } 1794 1795 // Apply specific matchup overrides 1796 if (awayRank === 2 && homeRank === 1) { 1797 spread = 4.0; // Rank 1 favored by 4.0 1798 } else if (awayRank === 3 && homeRank === 1) { 1799 spread = 4.5; // Rank 1 favored by 4.5 1800 } else if (awayRank === 1 && homeRank === 2) { 1801 spread = 1.5; // Rank 2 favored by 1.5 1802 } 1803 } 1804 1805 // Apply offense/defense ranking adjustments 1806 const awayOffenseRank = getTeamOffenseRank(awayTeam); 1807 const awayDefenseRank = getTeamDefenseRank(awayTeam); 1808 const homeOffenseRank = getTeamOffenseRank(homeTeam); 1809 const homeDefenseRank = getTeamDefenseRank(homeTeam); 1810 1811 const offenseDefenseAdjustment = calculateOffenseDefenseAdjustment( 1812 awayOffenseRank, awayDefenseRank, homeOffenseRank, homeDefenseRank, Math.abs(spread) 1813 ); 1814 spread += offenseDefenseAdjustment; 1815 1816 // Apply injury adjustments - Commented out for potential future restoration 1817 // if (document.getElementById('awayInjury').checked) spread += INJURY_AWAY; 1818 // if (document.getElementById('homeInjury').checked) spread -= INJURY_HOME; 1819 1820 // Apply momentum adjustments with non-whole number logic 1821 // Helper function to determine adjustment that makes spread non-whole 1822 const getMomentumAdjustment = (baseAdjustment, alternateAdjustment) => { 1823 // Check if current spread is whole or half 1824 const isWhole = Math.abs(spread % 1) < 0.01; 1825 const isHalf = Math.abs((spread % 1) - 0.5) < 0.01; 1826 1827 // If spread is currently whole, use the half adjustment; if half, use the whole adjustment 1828 if (isWhole) { 1829 return alternateAdjustment; // This will make it non-whole 1830 } else if (isHalf) { 1831 return baseAdjustment; // This will make it whole, so switch 1832 } else { 1833 return baseAdjustment; // Use default 1834 } 1835 }; 1836 1837 // Commented out momentum adjustments for potential future restoration 1838 // Top 23 teams + positive home momentum: add 2 or 2.5 to make non-whole 1839 // if (document.getElementById('positiveHomeMomentum').checked && homeRank <= 23) { 1840 // const adjustment = getMomentumAdjustment(2.0, 2.5); 1841 // spread += adjustment; 1842 // } 1843 1844 // Top 23 teams + positive away momentum: subtract 1.5 or 2 to make non-whole 1845 // if (document.getElementById('positiveAwayMomentum').checked && awayRank <= 23) { 1846 // const adjustment = getMomentumAdjustment(2.0, 1.5); 1847 // spread -= adjustment; 1848 // } 1849 1850 // Top 23 teams + negative home momentum: subtract 1.5 or 2 to make non-whole 1851 // if (document.getElementById('negativeHomeMomentum').checked && homeRank <= 23) { 1852 // const adjustment = getMomentumAdjustment(2.0, 1.5); 1853 // spread -= adjustment; 1854 // } 1855 1856 // Teams 24+ + negative home momentum: subtract 1.5 or 2 to make non-whole 1857 // if (document.getElementById('negativeHomeMomentum').checked && homeRank >= 24) { 1858 // const adjustment = getMomentumAdjustment(2.0, 1.5); 1859 // spread -= adjustment; 1860 // } 1861 1862 // Teams 24+ + negative away momentum: add 2 or 2.5 to make non-whole 1863 // if (document.getElementById('negativeAwayMomentum').checked && awayRank >= 24) { 1864 // const adjustment = getMomentumAdjustment(2.0, 2.5); 1865 // spread += adjustment; 1866 // } 1867 1868 // Apply neutral site adjustment last 1869 if (document.getElementById('neutralSite').checked) spread -= BASE_HOME_ADV; 1870 1871 // Cap the spread 1872 spread = Math.min(Math.max(spread, -ABSOLUTE_MAX), ABSOLUTE_MAX); 1873 spread = Math.min(Math.max(spread, -MAX_SPREAD), MAX_SPREAD); 1874 1875 let rounded; 1876 const absSpread = Math.abs(spread); 1877 if (absSpread < 0.7) { 1878 rounded = 0; 1879 } else if (absSpread < 1.25) { 1880 rounded = Math.sign(spread) * 1.0; 1881 } else { 1882 rounded = Math.round(spread * 2) / 2; 1883 } 1884 1885 const formatted = rounded % 1 === 0 ? Math.abs(rounded).toFixed(0) : Math.abs(rounded).toFixed(1); 1886 const favoredTeam = rounded >= 0 ? homeTeam : awayTeam; 1887 1888 // Get favored team's color 1889 const favoredTeamData = teams.find(t => t.name === favoredTeam); 1890 const favoredTeamColor = favoredTeamData ? favoredTeamData.primary : '#000000'; 1891 1892 resultDiv.innerHTML = rounded !== 0 1893 ? \`<strong style="color: \${favoredTeamColor};">Spread: \${favoredTeam} -\${formatted}</strong>\` 1894 : \`<strong>Even matchup</strong>\`; 1895 } 1896 1897 function getCurrentRank(teamName) { 1898 const buttons = document.querySelectorAll('.team-btn'); 1899 for (let i = 0; i < buttons.length; i++) { 1900 if (buttons[i].textContent.includes(teamName)) { 1901 return i + 1; 1902 } 1903 } 1904 return 32; 1905 } 1906 1907 function getTeamOffenseRank(teamName) { 1908 // Convert short name to full name for lookup 1909 const fullName = getFullTeamName(teamName); 1910 return offenseRanks[fullName] || offenseRanks[teamName] || 16; // Default to middle rank if not found 1911 } 1912 1913 function getTeamDefenseRank(teamName) { 1914 // Convert short name to full name for lookup 1915 const fullName = getFullTeamName(teamName); 1916 return defenseRanks[fullName] || defenseRanks[teamName] || 16; // Default to middle rank if not found 1917 } 1918 1919 function getFullTeamName(shortName) { 1920 const teamMap = { 1921 'Eagles': 'Philadelphia Eagles', 1922 'Chiefs': 'Kansas City Chiefs', 1923 'Ravens': 'Baltimore Ravens', 1924 'Bills': 'Buffalo Bills', 1925 'Lions': 'Detroit Lions', 1926 'Commanders': 'Washington Commanders', 1927 'Rams': 'Los Angeles Rams', 1928 '49ers': 'San Francisco 49ers', 1929 'Packers': 'Green Bay Packers', 1930 'Vikings': 'Minnesota Vikings', 1931 'Chargers': 'Los Angeles Chargers', 1932 'Bengals': 'Cincinnati Bengals', 1933 'Buccaneers': 'Tampa Bay Buccaneers', 1934 'Broncos': 'Denver Broncos', 1935 'Cowboys': 'Dallas Cowboys', 1936 'Texans': 'Houston Texans', 1937 'Bears': 'Chicago Bears', 1938 'Steelers': 'Pittsburgh Steelers', 1939 'Seahawks': 'Seattle Seahawks', 1940 'Cardinals': 'Arizona Cardinals', 1941 'Dolphins': 'Miami Dolphins', 1942 'Saints': 'New Orleans Saints', 1943 'Titans': 'Tennessee Titans', 1944 'Jets': 'New York Jets', 1945 'Patriots': 'New England Patriots', 1946 'Colts': 'Indianapolis Colts', 1947 'Jaguars': 'Jacksonville Jaguars', 1948 'Raiders': 'Las Vegas Raiders', 1949 'Falcons': 'Atlanta Falcons', 1950 'Browns': 'Cleveland Browns', 1951 'Panthers': 'Carolina Panthers', 1952 'Giants': 'New York Giants' 1953 };
1954 return teamMap[shortName] || shortName; 1955 } 1956 1957 function calculateOffenseDefenseAdjustment(awayOffenseRank, awayDefenseRank, homeOffenseRank, homeDefenseRank, currentSpread) { 1958 // Check if a team is highly ranked in both offense and defense (top 10 in both) 1959 // or lowly ranked in both (bottom 10 in both) 1960 const awayHighInBoth = awayOffenseRank <= 10 && awayDefenseRank <= 10; 1961 const awayLowInBoth = awayOffenseRank >= 23 && awayDefenseRank >= 23; 1962 const homeHighInBoth = homeOffenseRank <= 10 && homeDefenseRank <= 10; 1963 const homeLowInBoth = homeOffenseRank >= 23 && homeDefenseRank >= 23; 1964 1965 let adjustment = 0; 1966 1967 // If current spread is 8 or less 1968 if (currentSpread <= 8) { 1969 if (homeHighInBoth && !awayHighInBoth) { 1970 adjustment = 0.5; // Favor home team by extra 0.5 points 1971 } else if (awayHighInBoth && !homeHighInBoth) { 1972 adjustment = -0.5; // Favor away team by extra 0.5 points 1973 } else if (homeLowInBoth && !awayLowInBoth) { 1974 adjustment = -0.5; // Penalize home team by 0.5 points 1975 } else if (awayLowInBoth && !homeLowInBoth) { 1976 adjustment = 0.5; // Penalize away team by 0.5 points 1977 } 1978 } 1979 // If current spread is 8.5 or more 1980 else if (currentSpread >= 8.5) { 1981 if (homeHighInBoth && !awayHighInBoth) { 1982 adjustment = Math.random() < 0.5 ? 0.5 : 1.0; // Random 0.5 or 1.0 points 1983 } else if (awayHighInBoth && !homeHighInBoth) { 1984 adjustment = Math.random() < 0.5 ? -0.5 : -1.0; // Random -0.5 or -1.0 points 1985 } else if (homeLowInBoth && !awayLowInBoth) { 1986 adjustment = Math.random() < 0.5 ? -0.5 : -1.0; // Penalize home team 1987 } else if (awayLowInBoth && !homeLowInBoth) { 1988 adjustment = Math.random() < 0.5 ? 0.5 : 1.0; // Penalize away team 1989 } 1990 } 1991 1992 console.log(\`Offense/Defense Adjustment: Away O:\${awayOffenseRank} D:\${awayDefenseRank}, Home O:\${homeOffenseRank} D:\${homeDefenseRank}, Spread:\${currentSpread}, Adjustment:\${adjustment}\`); 1993 return adjustment; 1994 } 1995 1996 window.addEventListener('load', () => { 1997 console.log('Window loaded, loading power rankings first'); 1998 // Set logo src to use parent window origin for proper image loading in iframe 1999 const baseUrl = window.parent.location.origin; 2000 const headerLogo = document.getElementById('headerLogo'); 2001 const mobileHeaderLogo = document.getElementById('mobileHeaderLogo'); 2002 if (headerLogo) headerLogo.src = \`\${baseUrl}/public/hq-logo.png\`; 2003 if (mobileHeaderLogo) mobileHeaderLogo.src = \`\${baseUrl}/public/hq-logo.png\`; 2004 loadLivePowerRankings(); // Use live power rankings from Google Sheets API first, then initialize grid 2005 }); 2006 <\/script> 2007</body> 2008</html>`,i=new Blob([r],{type:"text/html"}),o=URL.createObjectURL(i),e=document.createElement("iframe");e.src=o,e.className="odds-calculator-iframe",e.style.width="100%",e.style.border="none",e.style.overflow="hidden",e.scrolling="no";const a=document.getElementById("calculator-content");return a&&(a.innerHTML="",a.appendChild(e)),()=>{URL.revokeObjectURL(o)}},[]),t.jsxs("div",{style:{width:"100%",minHeight:"100vh",display:"flex",flexDirection:"column"},children:[t.jsx("style",{children:` 2009 .odds-calculator-iframe { 2010 height: 100vh; 2011 } 2012 @media (max-width: 768px) { 2013 .odds-calculator-iframe { 2014 height: 1450px; /* Reduced fixed height for mobile to condense blank space */ 2015 } 2016 } 2017 `}),t.jsx(l,{showDesktopNavigation:!1}),t.jsx("div",{id:"calculator-content",style:{width:"100%",flex:1}})]})}export{c as default};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.