1/* nav-build:259459737353629 */ 2/* 3 * site-nav.js - Site-wide hamburger navigation. 4 * 5 * Builds a top-right hamburger drawer below 900px, leaves the existing 6 * horizontal nav alone above 900px. The drawer's menu structure is 7 * hardcoded in NAV_STRUCTURE below so every section (FA Cup, Premier 8 * League, World Cup, Betting) gets its own expandable submenu no matter 9 * which page the visitor is currently on. 10 * 11 * Open / close animation timing (.22s ease, height-from-scrollHeight 12 * sub-toggle pattern) mirrors the twinshair.com hamburger we copied as a 13 * reference - keeps the polished feel without rolling our own physics. 14 * 15 * Drawer styling lives in /css/style.css under the "Hamburger nav" heading 16 * so DevTools can attribute every rule to a real stylesheet. 17 */ 18(function(){ 19 if (window.__faSiteNavInstalled) return; 20 window.__faSiteNavInstalled = true; 21 22 // The hamburger kicks in at 900px and below; full horizontal nav above. 23 var MOBILE_BREAKPOINT = 900; 24 25 // ---------- Hardcoded menu structure ---------- 26 // Each top-level entry is an object; if it has `items`, those are sub-links 27 // that appear under a +/- toggle in the drawer. 28 // Each entry can have `items` of its own â recursive so we can mirror the 29 // desktop sub-nav's two-level "Matches > X / Odds > Y" grouping. An entry 30 // with `items` gets a +/- toggle and its children render beneath. Children 31 // may themselves have `items` (third level â used for Matches/Odds groups). 32 var NAV_STRUCTURE = [ 33 { 34 "label": "Home", 35 "href": "https://www.fa-cupfinals.co.uk/" 36 }, 37 { 38 "label": "FA Cup", 39 "href": "/fixtures/facup/", 40 "items": [ 41 { 42 "label": "Winners", 43 "href": "/results.html" 44 }, 45 { 46 "label": "Leaderboard", 47 "href": "/leaderboard.html" 48 }, 49 { 50 "label": "Matches", 51 "href": "/fixtures/facup/", 52 "items": [ 53 { 54 "label": "2nd Qualifying Round", 55 "href": "/fixtures/facup/2nd-qualifying-round.html" 56 }, 57 { 58 "label": "3rd Qualifying Round", 59 "href": "/fixtures/facup/3rd-qualifying-round.html" 60 } 61 ] 62 }, 63 { 64 "label": "Odds", 65 "href": "/odds.html", 66 "items": [ 67 { 68 "label": "Outright", 69 "href": "/odds/facup/2027.html" 70 }, 71 { 72 "label": "Round 1", 73 "href": "/odds/facup/1st-round.html" 74 }, 75 { 76 "label": "Round 2", 77 "href": "/odds/facup/2nd-round.html" 78 } 79 ] 80 }, 81 { 82 "label": "History", 83 "href": "/history/" 84 }, 85 { 86 "label": "Tickets", 87 "href": "/faq/tickets.html" 88 }, 89 { 90 "label": "Dates and Times", 91 "href": "/faq/" 92 } 93 ] 94 }, 95 { 96 "label": "Premier League", 97 "href": "/fixtures/premierleague/", 98 "items": [ 99 { 100 "label": "Matches", 101 "href": "/fixtures/premierleague/", 102 "items": [ 103 { 104 "label": "Oct", 105 "href": "/fixtures/premierleague/october.html" 106 }, 107 { 108 "label": "Nov", 109 "href": "/fixtures/premierleague/november.html" 110 } 111 ] 112 }, 113 { 114 "label": "Table", 115 "href": "/fixtures/premierleague/table.html" 116 }, 117 { 118 "label": "Teams", 119 "href": "/fixtures/teams/" 120 }, 121 { 122 "label": "Odds", 123 "href": "/odds/premierleague/", 124 "items": [ 125 { 126 "label": "Oct", 127 "href": "/odds/premierleague/october.html" 128 }, 129 { 130 "label": "Nov", 131 "href": "/odds/premierleague/november.html" 132 }, 133 { 134 "label": "Outright", 135 "href": "/odds/premierleague/outright.html" 136 }, 137 { 138 "label": "Hull", 139 "href": "/odds/teams/hull-city-outright.html" 140 }, 141 { 142 "label": "Brighton", 143 "href": "/odds/teams/brighton-outright.html" 144 }, 145 { 146 "label": "Top Scorer", 147 "href": "/odds/premierleague/top-scorer.html" 148 }, 149 { 150 "label": "Anytime Scorers", 151 "href": "/odds/premierleague/anytime-scorer.html" 152 }, 153 { 154 "label": "Teams", 155 "href": "/odds/teams/" 156 } 157 ] 158 } 159 ] 160 }, 161 { 162 "label": "English Leagues", 163 "href": "/fixtures/championship/", 164 "items": [ 165 { 166 "label": "Championship", 167 "href": "/fixtures/championship/", 168 "items": [ 169 { 170 "label": "Matches", 171 "href": "/fixtures/championship/", 172 "items": [ 173 { 174 "label": "Oct", 175 "href": "/fixtures/championship/october.html" 176 }, 177 { 178 "label": "Teams", 179 "href": "/fixtures/championship/teams/" 180 } 181 ] 182 }, 183 { 184 "label": "Odds", 185 "href": "/odds/championship/", 186 "items": [ 187 { 188 "label": "Oct", 189 "href": "/odds/championship/october.html" 190 }, 191 { 192 "label": "Outright", 193 "href": "/odds/championship/outright.html" 194 }, 195 { 196 "label": "Top Scorer", 197 "href": "/odds/championship/top-scorer.html" 198 }, 199 { 200 "label": "Anytime Scorers", 201 "href": "/odds/championship/anytime-scorer.html" 202 }, 203 { 204 "label": "Teams", 205 "href": "/odds/championship/teams/" 206 } 207 ] 208 } 209 ] 210 }, 211 { 212 "label": "Division 1", 213 "href": "/fixtures/division-1/", 214 "items": [ 215 { 216 "label": "Matches", 217 "href": "/fixtures/division-1/", 218 "items": [ 219 { 220 "label": "Oct", 221 "href": "/fixtures/division-1/october.html" 222 }, 223 { 224 "label": "Teams", 225 "href": "/fixtures/division-1/teams/" 226 } 227 ] 228 }, 229 { 230 "label": "Odds", 231 "href": "/odds/division-1/", 232 "items": [ 233 { 234 "label": "Oct", 235 "href": "/odds/division-1/october.html" 236 }, 237 { 238 "label": "Outright", 239 "href": "/odds/division-1/outright.html" 240 }, 241 { 242 "label": "Top Scorer", 243 "href": "/odds/division-1/top-scorer.html" 244 }, 245 { 246 "label": "Anytime Scorers", 247 "href": "/odds/division-1/anytime-scorer.html" 248 }, 249 { 250 "label": "Teams", 251 "href": "/odds/division-1/teams/" 252 } 253 ] 254 } 255 ] 256 }, 257 { 258 "label": "Division 2", 259 "href": "/fixtures/division-2/", 260 "items": [ 261 { 262 "label": "Matches", 263 "href": "/fixtures/division-2/", 264 "items": [ 265 { 266 "label": "Oct", 267 "href": "/fixtures/division-2/october.html" 268 }, 269 { 270 "label": "Teams", 271 "href": "/fixtures/division-2/teams/" 272 } 273 ] 274 }, 275 { 276 "label": "Odds", 277 "href": "/odds/division-2/", 278 "items": [ 279 { 280 "label": "Oct", 281 "href": "/odds/division-2/october.html" 282 }, 283 { 284 "label": "Outright", 285 "href": "/odds/division-2/outright.html" 286 }, 287 { 288 "label": "Top Scorer", 289 "href": "/odds/division-2/top-scorer.html" 290 }, 291 { 292 "label": "Anytime Scorers", 293 "href": "/odds/division-2/anytime-scorer.html" 294 }, 295 { 296 "label": "Teams", 297 "href": "/odds/division-2/teams/" 298 } 299 ] 300 } 301 ] 302 } 303 ] 304 }, 305 { 306 "label": "More Comps", 307 "href": "/fixtures/worldcup/", 308 "items": [ 309 { 310 "label": "Euro 2028", 311 "href": "/fixtures/euro-2028/", 312 "items": [ 313 { 314 "label": "Matches", 315 "href": "/fixtures/euro-2028/" 316 }, 317 { 318 "label": "Odds", 319 "href": "/odds/euro-2028/" 320 } 321 ] 322 }, 323 { 324 "label": "Champions League", 325 "href": "/fixtures/championsleague/", 326 "items": [ 327 { 328 "label": "Matches", 329 "href": "/fixtures/championsleague/", 330 "items": [ 331 { 332 "label": "League Phase", 333 "href": "/fixtures/championsleague/league-phase.html" 334 }, 335 { 336 "label": "Final", 337 "href": "/fixtures/championsleague/final.html" 338 } 339 ] 340 }, 341 { 342 "label": "Odds", 343 "href": "/odds/championsleague/", 344 "items": [ 345 { 346 "label": "League Phase", 347 "href": "/odds/championsleague/league-phase.html" 348 }, 349 { 350 "label": "Outright", 351 "href": "/odds/championsleague/outright.html" 352 }, 353 { 354 "label": "Top Scorer", 355 "href": "/odds/championsleague/top-scorer.html" 356 } 357 ] 358 } 359 ] 360 }, 361 { 362 "label": "World Cup", 363 "href": "/fixtures/worldcup/", 364 "items": [ 365 { 366 "label": "Matches", 367 "href": "/fixtures/worldcup/", 368 "items": [ 369 { 370 "label": "W1", 371 "href": "/fixtures/worldcup/week-1.html" 372 }, 373 { 374 "label": "W2", 375 "href": "/fixtures/worldcup/week-2.html" 376 }, 377 { 378 "label": "W3", 379 "href": "/fixtures/worldcup/week-3.html" 380 }, 381 { 382 "label": "Groups", 383 "href": "/fixtures/worldcup/groups.html" 384 } 385 ] 386 }, 387 { 388 "label": "Odds", 389 "href": "/odds/worldcup/", 390 "items": [ 391 { 392 "label": "Outright", 393 "href": "/odds/worldcup/outright.html" 394 }, 395 { 396 "label": "Top Scorer", 397 "href": "/odds/worldcup/top-scorer.html" 398 }, 399 { 400 "label": "Teams", 401 "href": "/odds/teams/worldcup.html" 402 } 403 ] 404 }, 405 { 406 "label": "FAQ", 407 "href": "/faq/worldcup/" 408 } 409 ] 410 }, 411 { 412 "label": "La Liga - Odds", 413 "href": "/odds/la-liga/", 414 "items": [ 415 { 416 "label": "Outright", 417 "href": "/odds/la-liga/outright.html" 418 }, 419 { 420 "label": "Top Scorer", 421 "href": "/odds/la-liga/top-scorer.html" 422 } 423 ] 424 }, 425 { 426 "label": "Serie A - Odds", 427 "href": "/odds/serie-a/", 428 "items": [ 429 { 430 "label": "Outright", 431 "href": "/odds/serie-a/outright.html" 432 }, 433 { 434 "label": "Top Scorer", 435 "href": "/odds/serie-a/top-scorer.html" 436 } 437 ] 438 }, 439 { 440 "label": "Bundesliga - Odds", 441 "href": "/odds/bundesliga/", 442 "items": [ 443 { 444 "label": "Outright", 445 "href": "/odds/bundesliga/outright.html" 446 }, 447 { 448 "label": "Top Scorer", 449 "href": "/odds/bundesliga/top-scorer.html" 450 } 451 ] 452 }, 453 { 454 "label": "Ligue 1 - Odds", 455 "href": "/odds/ligue-1/", 456 "items": [ 457 { 458 "label": "Outright", 459 "href": "/odds/ligue-1/outright.html" 460 }, 461 { 462 "label": "Top Scorer", 463 "href": "/odds/ligue-1/top-scorer.html" 464 } 465 ] 466 } 467 ] 468 }, 469 { 470 "label": "Tips", 471 "href": "/betting/" 472 }, 473 { 474 "label": "Games", 475 "href": "/games/" 476 }, 477 { 478 "label": "Contact Us", 479 "href": "/contact/" 480 } 481 ]; 482 var DESKTOP_STRUCTURE = [ 483 { 484 "label": "Home", 485 "href": "https://www.fa-cupfinals.co.uk/", 486 "class": "home" 487 }, 488 { 489 "label": "FA Cup", 490 "href": "/fixtures/facup/", 491 "class": "facupoddslink", 492 "items": [ 493 { 494 "label": "Winners", 495 "href": "/results.html" 496 }, 497 { 498 "label": "Leaderboard", 499 "href": "/leaderboard.html" 500 }, 501 { 502 "label": "Matches", 503 "href": "/fixtures/facup/", 504 "items": [ 505 { 506 "label": "2nd Qualifying Round", 507 "href": "/fixtures/facup/2nd-qualifying-round.html" 508 }, 509 { 510 "label": "3rd Qualifying Round", 511 "href": "/fixtures/facup/3rd-qualifying-round.html" 512 } 513 ] 514 }, 515 { 516 "label": "Odds", 517 "href": "/odds.html", 518 "items": [ 519 { 520 "label": "Outright", 521 "href": "/odds.html" 522 }, 523 { 524 "label": "Hull", 525 "href": "/odds/teams/hull-facup.html", 526 "class": "nav-subitem" 527 }, 528 { 529 "label": "Brighton", 530 "href": "/odds/teams/brighton-facup.html", 531 "class": "nav-subitem" 532 }, 533 { 534 "label": "Leeds", 535 "href": "/odds/teams/leeds-facup.html", 536 "class": "nav-subitem" 537 }, 538 { 539 "label": "Round 1", 540 "href": "/odds/facup/1st-round.html" 541 }, 542 { 543 "label": "Round 2", 544 "href": "/odds/facup/2nd-round.html" 545 } 546 ] 547 }, 548 { 549 "label": "History", 550 "href": "/history/" 551 }, 552 { 553 "label": "Tickets", 554 "href": "/faq/tickets.html" 555 }, 556 { 557 "label": "Dates & Times", 558 "href": "/faq/" 559 } 560 ] 561 }, 562 { 563 "label": "Premier League", 564 "href": "/fixtures/premierleague/", 565 "class": "premierleaguelink", 566 "items": [ 567 { 568 "label": "Matches", 569 "href": "/fixtures/premierleague/", 570 "items": [ 571 { 572 "label": "Oct", 573 "href": "/fixtures/premierleague/october.html" 574 }, 575 { 576 "label": "Nov", 577 "href": "/fixtures/premierleague/november.html" 578 } 579 ] 580 }, 581 { 582 "label": "Table", 583 "href": "/fixtures/premierleague/table.html" 584 }, 585 { 586 "label": "Teams", 587 "href": "/fixtures/teams/" 588 }, 589 { 590 "label": "Odds", 591 "href": "/odds/premierl
591eague/", 592 "items": [ 593 { 594 "label": "Oct", 595 "href": "/odds/premierleague/october.html" 596 }, 597 { 598 "label": "Nov", 599 "href": "/odds/premierleague/november.html" 600 }, 601 { 602 "label": "Outright", 603 "href": "/odds/premierleague/outright.html" 604 }, 605 { 606 "label": "Hull", 607 "href": "/odds/teams/hull-city-outright.html", 608 "class": "nav-subitem" 609 }, 610 { 611 "label": "Brighton", 612 "href": "/odds/teams/brighton-outright.html", 613 "class": "nav-subitem" 614 }, 615 { 616 "label": "Leeds", 617 "href": "/odds/teams/leeds-outright.html", 618 "class": "nav-subitem" 619 }, 620 { 621 "label": "Top Scorer", 622 "href": "/odds/premierleague/top-scorer.html" 623 }, 624 { 625 "label": "Anytime Scorers", 626 "href": "/odds/premierleague/anytime-scorer.html" 627 }, 628 { 629 "label": "Teams", 630 "href": "/odds/teams/" 631 } 632 ] 633 } 634 ] 635 }, 636 { 637 "label": "English Leagues", 638 "href": "/fixtures/championship/", 639 "class": "championshiplink", 640 "items": [ 641 { 642 "label": "Championship", 643 "href": "/fixtures/championship/", 644 "items": [ 645 { 646 "label": "Matches", 647 "href": "/fixtures/championship/", 648 "items": [ 649 { 650 "label": "Oct", 651 "href": "/fixtures/championship/october.html" 652 }, 653 { 654 "label": "Teams", 655 "href": "/fixtures/championship/teams/" 656 } 657 ] 658 }, 659 { 660 "label": "Odds", 661 "href": "/odds/championship/", 662 "items": [ 663 { 664 "label": "Oct", 665 "href": "/odds/championship/october.html" 666 }, 667 { 668 "label": "Outright", 669 "href": "/odds/championship/outright.html" 670 }, 671 { 672 "label": "Top Scorer", 673 "href": "/odds/championship/top-scorer.html" 674 }, 675 { 676 "label": "Anytime Scorers", 677 "href": "/odds/championship/anytime-scorer.html" 678 }, 679 { 680 "label": "Teams", 681 "href": "/odds/championship/teams/" 682 } 683 ] 684 } 685 ] 686 }, 687 { 688 "label": "Division 1", 689 "href": "/fixtures/division-1/", 690 "items": [ 691 { 692 "label": "Matches", 693 "href": "/fixtures/division-1/", 694 "items": [ 695 { 696 "label": "Oct", 697 "href": "/fixtures/division-1/october.html" 698 }, 699 { 700 "label": "Teams", 701 "href": "/fixtures/division-1/teams/" 702 } 703 ] 704 }, 705 { 706 "label": "Odds", 707 "href": "/odds/division-1/", 708 "items": [ 709 { 710 "label": "Oct", 711 "href": "/odds/division-1/october.html" 712 }, 713 { 714 "label": "Outright", 715 "href": "/odds/division-1/outright.html" 716 }, 717 { 718 "label": "Top Scorer", 719 "href": "/odds/division-1/top-scorer.html" 720 }, 721 { 722 "label": "Anytime Scorers", 723 "href": "/odds/division-1/anytime-scorer.html" 724 }, 725 { 726 "label": "Teams", 727 "href": "/odds/division-1/teams/" 728 } 729 ] 730 } 731 ] 732 }, 733 { 734 "label": "Division 2", 735 "href": "/fixtures/division-2/", 736 "items": [ 737 { 738 "label": "Matches", 739 "href": "/fixtures/division-2/", 740 "items": [ 741 { 742 "label": "Oct", 743 "href": "/fixtures/division-2/october.html" 744 }, 745 { 746 "label": "Teams", 747 "href": "/fixtures/division-2/teams/" 748 } 749 ] 750 }, 751 { 752 "label": "Odds", 753 "href": "/odds/division-2/", 754 "items": [ 755 { 756 "label": "Oct", 757 "href": "/odds/division-2/october.html" 758 }, 759 { 760 "label": "Outright", 761 "href": "/odds/division-2/outright.html" 762 }, 763 { 764 "label": "Top Scorer", 765 "href": "/odds/division-2/top-scorer.html" 766 }, 767 { 768 "label": "Anytime Scorers", 769 "href": "/odds/division-2/anytime-scorer.html" 770 }, 771 { 772 "label": "Teams", 773 "href": "/odds/division-2/teams/" 774 } 775 ] 776 } 777 ] 778 } 779 ] 780 }, 781 { 782 "label": "More Comps", 783 "href": "/fixtures/worldcup/", 784 "class": "worldcuplink", 785 "items": [ 786 { 787 "label": "Euro 2028", 788 "href": "/fixtures/euro-2028/", 789 "items": [ 790 { 791 "label": "Matches", 792 "href": "/fixtures/euro-2028/" 793 }, 794 { 795 "label": "Odds", 796 "href": "/odds/euro-2028/" 797 } 798 ] 799 }, 800 { 801 "label": "Champions League", 802 "href": "/fixtures/championsleague/", 803 "class": "championsleaguelink", 804 "items": [ 805 { 806 "label": "Matches", 807 "href": "/fixtures/championsleague/", 808 "items": [ 809 { 810 "label": "League Phase", 811 "href": "/fixtures/championsleague/league-phase.html" 812 }, 813 { 814 "label": "Final", 815 "href": "/fixtures/championsleague/final.html" 816 } 817 ] 818 }, 819 { 820 "label": "Odds", 821 "href": "/odds/championsleague/", 822 "items": [ 823 { 824 "label": "League Phase", 825 "href": "/odds/championsleague/league-phase.html" 826 }, 827 { 828 "label": "Outright", 829 "href": "/odds/championsleague/outright.html" 830 }, 831 { 832 "label": "Top Scorer", 833 "href": "/odds/championsleague/top-scorer.html" 834 } 835 ] 836 } 837 ] 838 }, 839 { 840 "label": "World Cup", 841 "href": "/fixtures/worldcup/", 842 "items": [ 843 { 844 "label": "Matches", 845 "href": "/fixtures/worldcup/", 846 "items": [ 847 { 848 "label": "W1", 849 "href": "/fixtures/worldcup/week-1.html" 850 }, 851 { 852 "label": "W2", 853 "href": "/fixtures/worldcup/week-2.html" 854 }, 855 { 856 "label": "W3", 857 "href": "/fixtures/worldcup/week-3.html" 858 }, 859 { 860 "label": "Groups", 861 "href": "/fixtures/worldcup/groups.html" 862 } 863 ] 864 }, 865 { 866 "label": "Odds", 867 "href": "/odds/worldcup/", 868 "items": [ 869 { 870 "label": "Outright", 871 "href": "/odds/worldcup/outright.html" 872 }, 873 { 874 "label": "Top Scorer", 875 "href": "/odds/worldcup/top-scorer.html" 876 }, 877 { 878 "label": "Teams", 879 "href": "/odds/teams/worldcup.html" 880 } 881 ] 882 }, 883 { 884 "label": "Dates and Times", 885 "href": "/faq/worldcup/" 886 } 887 ] 888 }, 889 { 890 "label": "La Liga - Odds", 891 "href": "/odds/la-liga/", 892 "items": [ 893 { 894 "label": "Outright", 895 "href": "/odds/la-liga/outright.html" 896 }, 897 { 898 "label": "Top Scorer", 899 "href": "/odds/la-liga/top-scorer.html" 900 } 901 ] 902 }, 903 { 904 "label": "Serie A - Odds", 905 "href": "/odds/serie-a/", 906 "items": [ 907 { 908 "label": "Outright", 909 "href": "/odds/serie-a/outright.html" 910 }, 911 { 912 "label": "Top Scorer", 913 "href": "/odds/serie-a/top-scorer.html" 914 } 915 ] 916 }, 917 { 918 "label": "Bundesliga - Odds", 919 "href": "/odds/bundesliga/", 920 "items": [ 921 { 922 "label": "Outright", 923 "href": "/odds/bundesliga/outright.html" 924 }, 925 { 926 "label": "Top Scorer", 927 "href": "/odds/bundesliga/top-scorer.html" 928 } 929 ] 930 }, 931 { 932 "label": "Ligue 1 - Odds", 933 "href": "/odds/ligue-1/", 934 "items": [ 935 { 936 "label": "Outright", 937 "href": "/odds/ligue-1/outright.html" 938 }, 939 { 940 "label": "Top Scorer", 941 "href": "/odds/ligue-1/top-scorer.html" 942 } 943 ] 944 } 945 ] 946 }, 947 { 948 "label": "Tips", 949 "href": "/betting/", 950 "class": "bettinglink" 951 }, 952 { 953 "label": "Games", 954 "href": "/games/", 955 "class": "gameslink" 956 }, 957 { 958 "label": "Contact Us", 959 "href": "/contact/", 960 "class": "postid-contact" 961 } 962 ]; 963 var EXCLUSIONS = []; 964 965 // ---------- PWA-only nav (focused horse-racing index) ---------- 966 // When the visitor has installed the app to their home screen and 967 // launches it from the shortcut, body.pwa-mode is added by 968 // /betting/templegate-app/pwa.js. We want the hamburger drawer in 969 // that mode to show a focused horse-racing index instead of the 970 // whole-site NAV_STRUCTURE â football links etc. aren't relevant 971 // when the installed app is "Racing Tips Tracker". 972 var PWA_NAV_STRUCTURE = [ 973 { label: "Today's Tips", 974 href: '/betting/racing-tips.html' }, 975 { label: "Yesterday's Tips vs Results", 976 href: '/betting/racing-tips-yesterday.html' }, 977 { label: 'Tipster Tracker', href: '/betting/racing-tips.html', items: [ 978 { label: 'Templegate', href: '/betting/templegate.html' }, 979 { label: 'Racing Post', href: '/betting/racing-tips-tipster-racingpost.html' } 980 ]}, 981 { label: 'Big Event Tips', href: '/betting/racing-tips.html', items: [ 982 { label: 'Grand National', href: '/betting/racing-tips-grand-national.html' }, 983 { label: 'Cheltenham Festival', href: '/betting/racing-tips-cheltenham-festival.html' }, 984 { label: 'Royal Ascot', href: '/betting/racing-tips-royal-ascot.html' }, 985 { label: 'Epsom Derby', href: '/betting/racing-tips-the-derby.html' }, 986 { label: 'King George Chase', href: '/betting/racing-tips-king-george-stakes.html' } 987 ]} 988 ]; 989 990 // ---------- Padel-section nav (mobile hamburger only) ---------- 991 // When the visitor is on a padel page (body.padel-page), the football 992 // sections (FA Cup, Premier League, World Cup, Betting, More Sports) 993 // are irrelevant to the journey. The hamburger drawer focuses on the
994 // 3 padel destinations - same pattern as PWA_NAV_STRUCTURE above. 995 // Desktop horizontal nav stays untouched (still shows the whole-site 996 // football items) because site-nav.js only builds the MOBILE drawer. 997 var PADEL_NAV_STRUCTURE = [ 998 { label: 'Live Padel Rankings', href: '/padel/rankings.html' }, 999 /* User asked 2026-06-01: Tournament Schedule link in hamburger after 1000 Rankings, before Padel Travel/Courts. Travel link will come back 1001 once we have more articles in that section. */ 1002 { label: 'Tournament Schedule', href: '/padel/tournaments.html' }, 1003 { label: 'Padel Travel', href: '/padel/travel.html' }, 1004 { label: 'Padel Courts', href: '/padel/courts.html' }, 1005 /* User asked 2026-06-01: contact link at the bottom of the padel 1006 hamburger menu so visitors can reach /padel/contact/ from any 1007 padel page without needing the now-hidden subnav. */ 1008 { label: 'Contact Us', href: '/padel/contact/' } 1009 ]; 1010 1011 // Padel-page detection - mirrors the PWA detection pattern below. 1012 // body.padel-page is hard-coded on every padel HTML page. 1013 function isPadelPage() { 1014 try { 1015 return document.body && document.body.classList.contains('padel-page'); 1016 } catch (e) { return false; } 1017 } 1018 1019 // Cheap PWA detection - matches the criteria pwa.js uses to add 1020 // body.pwa-mode (display-mode standalone, iOS navigator.standalone, 1021 // ?pwa=1 cold-launch URL, or our cached _tgPWA flag). Duplicating 1022 // the check here so we don't depend on pwa.js having run first 1023 // (race condition: both scripts wire at DOMContentLoaded). 1024 function isPwaStandalone() { 1025 try { 1026 if (window._isTemplegatePWA === true) return true; 1027 if (window.navigator && window.navigator.standalone === true) return true; 1028 if (window.matchMedia && window.matchMedia('(display-mode: standalone)').matches) return true; 1029 if (window.location && window.location.search && window.location.search.indexOf('pwa=1') !== -1) return true; 1030 if (window.localStorage && window.localStorage.getItem('_tgPWA') === '1') return true; 1031 } catch (e) {} 1032 return false; 1033 } 1034 1035 // ---------- CSS lives in /css/style.css (search for "Hamburger nav") 1036 // Nothing to inject from here - this script only builds the 1037 // drawer markup and wires events. ---------- 1038 1039 // ---------- Build the drawer from NAV_STRUCTURE ---------- 1040 function init() { 1041 var header = document.querySelector('header.site-header'); 1042 if (!header) return; 1043 // Rebuild even if a previous version of the script ran - the new schema 1044 // (hardcoded NAV_STRUCTURE) supersedes any DOM-derived attempt. 1045 var existingToggle = header.querySelector('.fa-nav-toggle'); 1046 var existingDrawer = header.querySelector('.fa-nav-drawer'); 1047 if (existingToggle) existingToggle.remove(); 1048 if (existingDrawer) existingDrawer.remove(); 1049 1050 var toggle = document.createElement('button'); 1051 toggle.type = 'button'; 1052 toggle.className = 'fa-nav-toggle'; 1053 toggle.setAttribute('aria-label', 'Open navigation menu'); 1054 toggle.setAttribute('aria-expanded', 'false'); 1055 toggle.setAttribute('aria-controls', 'fa-nav-drawer'); 1056 toggle.innerHTML = '<span></span><span></span><span></span>'; 1057 1058 var drawer = document.createElement('ul'); 1059 drawer.className = 'fa-nav-drawer'; 1060 drawer.id = 'fa-nav-drawer'; 1061 1062 var currentPath = location.pathname; 1063 1064 // Recursive builder so any entry with `items` can carry its own +/- and 1065 // a nested sub-list. Depth controls which class set we attach so the CSS 1066 // can style level-1 (parent) vs level-2 (nested under a sub-parent). 1067 function buildEntry(entry, depth) { 1068 var li = document.createElement('li'); 1069 var a = document.createElement('a'); 1070 a.href = entry.href; 1071 a.textContent = entry.label; 1072 li.appendChild(a); 1073 var _ecls = entry['class'] || entry.cls; if (_ecls) li.className = (li.className ? li.className + ' ' : '') + _ecls; 1074 if (depth === 0 && sectionMatches(currentPath, entry)) { 1075 li.classList.add('fa-nav-current'); 1076 } 1077 if (Array.isArray(entry.items) && entry.items.length) { 1078 li.classList.add('fa-nav-has-sub'); 1079 var subToggle = document.createElement('button'); 1080 subToggle.type = 'button'; 1081 subToggle.className = 'fa-nav-sub-toggle'; 1082 subToggle.setAttribute('aria-label', 'Toggle ' + entry.label + ' submenu'); 1083 subToggle.setAttribute('aria-expanded', 'false'); 1084 subToggle.innerHTML = '<span class="fa-nav-sub-icon" aria-hidden="true"></span>'; 1085 li.appendChild(subToggle); 1086 var subUl = document.createElement('ul'); 1087 subUl.className = 'fa-nav-sub' + (depth >= 1 ? ' fa-nav-sub-deep' : '');
1088 entry.items.forEach(function(child) { 1089 subUl.appendChild(buildEntry(child, depth + 1)); 1090 }); 1091 li.appendChild(subUl); 1092 } 1093 return li; 1094 } 1095 // Pick the right nav for context: 1096 // Padel page -> padel links only (PADEL_NAV_STRUCTURE) 1097 // Wins over PWA detection because the padel PWA 1098 // runs in standalone too and would otherwise 1099 // inherit the horse-racing PWA_NAV_STRUCTURE. 1100 // PWA mode -> focused horse-racing index (PWA_NAV_STRUCTURE) 1101 // anything else -> whole-site default (NAV_STRUCTURE) 1102 var navData = isPadelPage() 1103 ? PADEL_NAV_STRUCTURE 1104 : (isPwaStandalone() ? PWA_NAV_STRUCTURE : NAV_STRUCTURE); 1105 navData.forEach(function(entry) { 1106 if (_navExcluded(entry.label)) return; 1107 drawer.appendChild(buildEntry(entry, 0)); 1108 }); 1109 1110 header.appendChild(toggle); 1111 header.appendChild(drawer); 1112 1113 // ----- Open/close handlers ----- 1114 function setOpen(open) { 1115 drawer.classList.toggle('open', open); 1116 toggle.classList.toggle('open', open); 1117 toggle.setAttribute('aria-expanded', open ? 'true' : 'false'); 1118 toggle.setAttribute('aria-label', open ? 'Close navigation menu' : 'Open navigation menu'); 1119 } 1120 toggle.addEventListener('click', function(e){ 1121 e.stopPropagation(); 1122 setOpen(!drawer.classList.contains('open')); 1123 }); 1124 // Close when tapping a destination link (NOT the +/- sub-toggle). 1125 drawer.addEventListener('click', function(e){ 1126 if (e.target.closest('.fa-nav-sub-toggle')) return; 1127 if (e.target.closest('a')) setOpen(false); 1128 }); 1129 // +/- sub-toggle: animate height from 0 -> scrollHeight (and back). 1130 // Accordion behaviour: opening a parent first triggers the collapse of 1131 // any sibling parents already open at the same level, then opens the 1132 // new one once the close animation has played out so the user sees the 1133 // transition rather than a snap-swap. 1134 var SUB_TRANSITION_MS = 220; 1135 function closeLi(targetLi){ 1136 var sub = targetLi.querySelector(':scope > .fa-nav-sub'); 1137 var btn = targetLi.querySelector(':scope > .fa-nav-sub-toggle'); 1138 if (btn) btn.setAttribute('aria-expanded', 'false'); 1139 if (!sub) { targetLi.classList.remove('open'); return; } 1140 sub.style.height = sub.scrollHeight + 'px'; 1141 sub.offsetHeight; // force layout so transition has a numeric start 1142 requestAnimationFrame(function(){ 1143 targetLi.classList.remove('open'); 1144 sub.style.height = '0'; 1145 }); 1146 } 1147 function openLi(targetLi){ 1148 var sub = targetLi.querySelector(':scope > .fa-nav-sub'); 1149 var btn = targetLi.querySelector(':scope > .fa-nav-sub-toggle'); 1150 if (btn) btn.setAttribute('aria-expanded', 'true'); 1151 if (!sub) { targetLi.classList.add('open'); return; } 1152 targetLi.classList.add('open'); 1153 sub.style.height = sub.scrollHeight + 'px'; 1154 var onEnd = function(ev){ 1155 if (ev.propertyName !== 'height') return; 1156 sub.style.height = 'auto'; 1157 sub.removeEventListener('transitionend', onEnd); 1158 }; 1159 sub.addEventListener('transitionend', onEnd); 1160 } 1161 drawer.addEventListener('click', function(e){ 1162 var btn = e.target.closest('.fa-nav-sub-toggle'); 1163 if (!btn) return; 1164 e.preventDefault(); 1165 e.stopPropagation(); 1166 var li = btn.closest('.fa-nav-has-sub'); 1167 if (!li) return; 1168 var willOpen = !li.classList.contains('open'); 1169 if (!willOpen) { 1170 closeLi(li); 1171 return; 1172 } 1173 // Close any open siblings at this level first, then open the new one 1174 // after the collapse transition has had time to play. 1175 var parentList = li.parentElement; 1176 var siblings = parentList ? Array.from(parentList.children).filter(function(c){ 1177 return c !== li && c.classList && c.classList.contains('fa-nav-has-sub') && c.classList.contains('open'); 1178 }) : []; 1179 siblings.forEach(closeLi); 1180 if (siblings.length) { 1181 setTimeout(function(){ openLi(li); }, SUB_TRANSITION_MS); 1182 } else { 1183 openLi(li); 1184 } 1185 }); 1186 document.addEventListener('keydown', function(e){ 1187 if (e.key === 'Escape' && drawer.classList.contains('open')) setOpen(false); 1188 }); 1189 document.addEventListener('click', function(e){ 1190 if (!drawer.classList.contains('open')) return; 1191 if (e.target.closest('.site-header')) return; 1192 setOpen(false); 1193 }); 1194 // Close drawer if viewport grows past the breakpoint (CSS hides it but 1195 // we want the state clean for when the user resizes back). 1196 window.addEventListener('resize', function(){ 1197 if (window.innerWidth > MOBILE_BREAKPOINT && drawer.classList.contains('open')) { 1198 setOpen(false); 1199 } 1200 }); 1201 } 1202 1203 // ---------- Desktop horizontal nav: ensure a Champions League link ---------- 1204 // The desktop nav (>900px) is hardcoded as <ul> markup inside each page's
1205 // header, NOT built by this script. Most pages were authored before the 1206 // Champions League section existed, so their .newmenu list is missing it. 1207 // Rather than edit dozens of templates + static pages, inject the link 1208 // here so it appears site-wide. The Champions League section pages already 1209 // ship a hardcoded `.championsleaguelink` <li>, so the guard below skips 1210 // them to avoid a duplicate. Inserted right after Premier League to match 1211 // the order the CL section pages already use (FA Cup, PL, CL, World Cup). 1212 function ensureChampionsLeagueDesktopLink() { 1213 var ul = document.querySelector('.newmenu > ul'); 1214 if (!ul) return; 1215 if (ul.querySelector('.championsleaguelink')) return; // already present 1216 var li = document.createElement('li'); 1217 li.className = 'championsleaguelink'; 1218 var a = document.createElement('a'); 1219 a.href = '/fixtures/championsleague/'; 1220 a.textContent = 'Champions League'; 1221 li.appendChild(a); 1222 var pl = ul.querySelector('.premierleaguelink'); 1223 if (pl) { 1224 ul.insertBefore(li, pl.nextSibling); 1225 } else { 1226 var wc = ul.querySelector('.worldcuplink'); 1227 if (wc) ul.insertBefore(li, wc); 1228 else ul.appendChild(li); 1229 } 1230 } 1231 1232 // La Liga desktop link, published site-wide AFTER Champions League. Section pages carry 1233 // body.laligapage; la-liga.css uses it to light this link and cancel the inherited 1234 // Premier League highlight (prempage stays on the body for styling). 1235 function ensureLaLigaDesktopLink() { 1236 var ul = document.querySelector('.newmenu > ul'); 1237 if (!ul) return; 1238 if (ul.querySelector('.laligalink')) return; // already present 1239 var li = document.createElement('li'); 1240 li.className = 'laligalink'; 1241 var a = document.createElement('a'); 1242 a.href = '/fixtures/la-liga/'; 1243 a.textContent = 'La Liga'; 1244 li.appendChild(a); 1245 var cl = ul.querySelector('.championsleaguelink'); 1246 if (cl) { 1247 ul.insertBefore(li, cl.nextSibling); 1248 } else { 1249 var wc = ul.querySelector('.worldcuplink'); 1250 if (wc) ul.insertBefore(li, wc); 1251 else ul.appendChild(li); 1252 } 1253 } 1254 1255 // Bundesliga desktop link, inserted after La Liga. 1256 function ensureBundesligaDesktopLink() { 1257 var ul = document.querySelector('.newmenu > ul'); 1258 if (!ul) return; 1259 if (ul.querySelector('.bundesligalink')) return; 1260 var li = document.createElement('li'); 1261 li.className = 'bundesligalink'; 1262 var a = document.createElement('a'); 1263 a.href = '/fixtures/bundesliga/'; 1264 a.textContent = 'Bundesliga'; 1265 li.appendChild(a); 1266 var la = ul.querySelector('.laligalink'); 1267 if (la) { ul.insertBefore(li, la.nextSibling); } 1268 else { 1269 var wc = ul.querySelector('.worldcuplink'); 1270 if (wc) ul.insertBefore(li, wc); else ul.appendChild(li); 1271 } 1272 } 1273 1274 // Inject the Championship link into the desktop .newmenu site-wide (most 1275 // templates predate the section). Inserted right after Premier League so the 1276 // order reads FA Cup, Premier League, Championship, Champions League, World Cup. 1277 // Runs AFTER ensureChampionsLeagueDesktopLink in boot() so it lands between PL 1278 // and the just-inserted Champions League link. 1279 // Style the English Leagues desktop dropdown: lift it ABOVE the subnav (both were 1280 // positioned with z:auto so the later subnav painted over it), and give it the FA Cup 1281 // draw-table look - red border + alternating row colours, red hover. Injected once. 1282 function ensureDesktopDropdownStyle() { 1283 if (document.getElementById('englishleagues-dd-style')) return; 1284 var st = document.createElement('style'); 1285 st.id = 'englishleagues-dd-style'; 1286 st.textContent = 1287 '.newmenu .championshiplink{z-index:9999 !important;}' + 1288 '.newmenu .championshiplink > ul{z-index:9999 !important;border:2px solid #4d4a4a !important;border-radius:0 0 6px 6px !important;background:#fff !important;
1288min-width:200px !important;box-shadow:0 4px 12px rgba(0,0,0,.18) !important;padding:0 !important;overflow:hidden !important;}' + 1289 '.newmenu .championshiplink > ul > li > a{padding:9px 14px !important;font-size:10pt !important;color:#1b1b1b !important;border-bottom:1px solid #f0e2da !important;}' + 1290 '.newmenu .championshiplink > ul > li:last-child > a{border-bottom:0 !important;}' + 1291 '.newmenu .championshiplink > ul > li:nth-child(odd) > a{background:#fff !important;}' + 1292 '.newmenu .championshiplink > ul > li:nth-child(even) > a{background:#fbf7f1 !important;}' + 1293 '.newmenu .championshiplink > ul > li > a:hover{background:#4d4a4a !important;color:#fff !important;}' + 1294 '.newmenu .championshiplink > ul > li.dd-active > a{background:#4d4a4a !important;color:#fff !important;}'; 1295 (document.head || document.documentElement).appendChild(st); 1296 } 1297 1298 function ensureChampionshipDesktopLink() { 1299 var ul = document.querySelector('.newmenu > ul'); 1300 if (!ul) return; 1301 ensureDesktopDropdownStyle(); 1302 if (ul.querySelector('.championshiplink')) return; // already present 1303 var li = document.createElement('li'); 1304 li.className = 'championshiplink'; 1305 li.style.position = 'relative'; // anchor the absolute dropdown to this li 1306 var a = document.createElement('a'); 1307 a.href = '/fixtures/championship/'; 1308 a.textContent = 'English Leagues'; 1309 li.appendChild(a); 1310 // Desktop hover dropdown (uses the existing .newmenu ul ul CSS): the three 1311 // English league sections - Championship, Division 1, Division 2. 1312 var sub = document.createElement('ul'); 1313 [['Championship', '/fixtures/championship/'], 1314 ['Division 1', '/fixtures/division-1/'], 1315 ['Division 2', '/fixtures/division-2/']].forEach(function (it) { 1316 var sli = document.createElement('li'); 1317 var sa = document.createElement('a'); 1318 sa.href = it[1]; 1319 sa.textContent = it[0]; 1320 var ddKey = it[1].split('/').filter(Boolean)[1]; // championship / division-1 / division-2 1321 if (ddKey && location.pathname.indexOf('/' + ddKey + '/') !== -1) sli.className = 'dd-active'; 1322 sli.appendChild(sa); 1323 sub.appendChild(sli); 1324 }); 1325 li.appendChild(sub); 1326 var pl = ul.querySelector('.premierleaguelink'); 1327 if (pl) { ul.insertBefore(li, pl.nextSibling); } 1328 else { ul.appendChild(li); } 1329 } 1330 1331 function sectionMatches(path, entry) { 1332 if (!entry || !entry.href) return false; 1333 var href = entry.href; 1334 // Trim domain prefix if present (Home link). 1335 href = href.replace(/^https?:\/\/[^/]+/, ''); 1336 if (!href || href === '/') return path === '/' || path === '/index.html'; 1337 // For section entries, match the leading path segment. 1338 var key = href.split('/').filter(Boolean)[0]; // 'fixtures' / 'odds' / 'betting' / 'sports' / 'contact' 1339 if (!key) return false; 1340 // The "FA Cup" / "Premier League" / "World Cup" entries point at 1341 // /fixtures/{league}/ but the section also lives at /odds/{league}/. 1342 // Match both by also checking the second path segment. 1343 var subKey = href.split('/').filter(Boolean)[1] || ''; 1344 // "English Leagues" (Championship) also owns Division 1 & 2, which live under 1345 // different path segments (/fixtures/division-1/, /odds/division-2/, etc.). 1346 if (subKey === 'championship' && (path.indexOf('/division-1/') !== -1 || path.indexOf('/division-2/') !== -1)) return true; 1347 if (subKey) { 1348 return path.indexOf('/' + subKey + '/') !== -1; 1349 } 1350 return path.indexOf('/' + key + '/') === 0 || path.indexOf('/' + key) === 0; 1351 } 1352 1353 // Desktop: turn the hardcoded 'World Cup' link into a 'More Comps' parent with a 1354 // hover dropdown (World Cup + Euro 2028). The .worldcuplink class is kept so the 1355 // existing worldcuppage highlight still lights it; euro2028page highlight added below. 1356 function restructureMoreComps() { 1357 var ul = document.querySelector('.newmenu > ul'); 1358 if (!ul) return; 1359 var wc = ul.querySelector('.worldcuplink'); 1360 if (!wc) { 1361 // older nav markup (home/racing/betting/match reports): the World Cup <li> has no 1362 // .worldcuplink class - find it by href or text and tag it so the rename + CSS apply. 1363 var _lis = ul.children; 1364 for (var _i = 0; _i < _lis.length; _i++) { 1365 var _a = _lis[_i].querySelector('a'); 1366 var _h = _a ? (_a.getAttribute('href') || '') : ''; 1367 if (_a && (_h.indexOf('/worldcup/') !== -1 || _a.textContent.trim() === 'World Cup')) { 1368 wc = _lis[_i]; wc.classList.add('worldcuplink'); break; 1369 } 1370 } 1371 } 1372 if (!wc || wc.getAttribute('data-morecomps')) return; 1373 wc.setAttribute('data-morecomps', '1'); 1374 wc.style.position = 'relative'; 1375 var a = wc.querySelector('a'); 1376 if (a) a.textContent = 'More Comps'; 1377 var sub = document.createElement('ul'); 1378 [['World Cup', '/fixtures/worldcup/', 'worldcup'], 1379 ['Euro 2028', '/fixtures/euro-2028/', 'euro-2028'], 1380 ['Serie A', '/fixtures/serie-a/', 'serie-a'], 1381 ['French Ligue 1', '/fixtures/ligue-1/', 'ligue-1']].forEac
1381h(function (it) { 1382 var sli = document.createElement('li'); 1383 var sa = document.createElement('a'); 1384 sa.href = it[1]; sa.textContent = it[0]; 1385 if (location.pathname.indexOf('/' + it[2] + '/') !== -1) sli.className = 'dd-active'; 1386 sli.appendChild(sa); sub.appendChild(sli); 1387 }); 1388 wc.appendChild(sub); 1389 if (!document.getElementById('morecomps-style')) { 1390 var st = document.createElement('style'); st.id = 'morecomps-style'; 1391 st.textContent = 1392 '.newmenu .worldcuplink{position:relative;}' + 1393 '.newmenu .worldcuplink > ul{display:none;position:absolute;left:0;top:100%;margin:0;padding:0;list-style:none;z-index:9999;border:2px solid #4d4a4a;border-radius:0 0 6px 6px;background:#fff;min-width:180px;box-shadow:0 4px 12px rgba(0,0,0,.18);overflow:hidden;}' + 1394 '.newmenu .worldcuplink:hover > ul{display:block;}' + 1395 '.newmenu .worldcuplink > ul > li{float:none;margin:0;}' + 1396 '.newmenu .worldcuplink > ul > li > a{display:block;padding:9px 14px;font-size:10pt;color:#1b1b1b;border-bottom:1px solid #f0e2da;white-space:nowrap;}' + 1397 '.newmenu .worldcuplink > ul > li:last-child > a{border-bottom:0;}' + 1398 '.newmenu .worldcuplink > ul > li:nth-child(even) > a{background:#fbf7f1;}' + 1399 '.newmenu .worldcuplink > ul > li > a:hover{background:#4d4a4a;color:#fff;}' + 1400 '.newmenu .worldcuplink > ul > li.dd-active > a{background:#4d4a4a;color:#fff;}' + 1401 'body.euro2028page .newmenu > ul > li.worldcuplink > a{background-color:#4d4a4a !important;}' + 1402 'body.serieapage .newmenu > ul > li.worldcuplink > a{background-color:#4d4a4a !important;}' + 1403 'body.ligue1page .newmenu > ul > li.worldcuplink > a{background-color:#4d4a4a !important;}' + 1404 'body.laligapage .newmenu > ul > li.worldcuplink > a{background-color:#4d4a4a !important;}' + 1405 'body.bundesligapage .newmenu > ul > li.worldcuplink > a{background-color:#4d4a4a !important;}' + 1406 'body.championsleaguepage .newmenu > ul > li.worldcuplink > a{background-color:#4d4a4a !important;}'; 1407 document.head.appendChild(st); 1408 } 1409 } 1410 1411 // Build desktop hover-dropdowns from NAV_STRUCTURE (the admin-edited nav tree) so grouped items like 1412 // "More Comps" / "English Leagues" show their child leagues on desktop, matching the mobile hamburger. 1413 // Replaces the old hardcoded restructureMoreComps + ensure*DesktopLink injections. (user 2026-08-23) 1414 // Is this top-level nav label excluded on the current page? (data-driven, EXCLUSIONS from admin) (user 2026-08-23) 1415 function _navExcluded(label) { 1416 var p = location.pathname, i, ex; 1417 for (i = 0; i < (EXCLUSIONS || []).length; i++) { 1418 ex = EXCLUSIONS[i]; 1419 if ((ex.labels || []).indexOf(label) === -1) continue; 1420 var hit = false; 1421 try { if (ex.pathRegex && new RegExp(ex.pathRegex).test(p)) hit = true; } catch (e) {} 1422 if (ex.prefix && p.indexOf(ex.prefix) === 0) hit = true; 1423 if (ex.path && p === ex.path) hit = true; 1424 if (ex.bodyClass && document.body && document.body.classList.contains(ex.bodyClass)) hit = true; 1425 if (hit) return true; 1426 } 1427 return false; 1428 } 1429 1430 function _ddAct(u) { if (!u || u === '/') return false; var p = location.pathname; if (p === u) return true; if (u.charAt(u.length - 1) === '/' && (p === u || p === u + 'index.html')) return true; return false; } 1431 // current page competition = the URL section (/odds/<comp>/ or /fixtures/<comp>/). 1432 // Keyed on the URL, NOT the body class: some cloned pages carry more than one comp body 1433 // class (e.g. a Champions League page inheriting worldcuppage for styling), which would 1434 // otherwise light up the wrong More Comps entry. 1435 function _compActive(u) { 1436 var mc = /\/(?:fixtures|odds)\/([^\/]+)\//.exec(u || ''); 1437 if (!mc) return false; 1438 var mp = /\/(?:fixtures|odds)\/([^\/]+)(?:\/|$)/.exec(location.pathname); 1439 return !!(mp && mp[1] === mc[1]); 1440 } 1441 // A flyout link is "in scope" when the current page sits within its section: exact match, 1442 // OR a section landing (href ends in '/') that prefixes the path, OR the section's outright 1443 // market when the page is a team-outright page (e.g. 'Team to win the Champions League'). 1444 function _ddScope(u) { 1445 if (_ddAct(u)) return true; 1446 if (!u || u === '/') return false;
1447 var p = location.pathname; 1448 if (u.charAt(u.length - 1) === '/' && p.indexOf(u) === 0) { 1449 // The shared team-landing dirs (/odds/teams/, /fixtures/teams/) hold team pages from SEVERAL 1450 // competitions, so a foreign-section team page living there (e.g. an FA Cup "-facup" team page, 1451 // body .facuppage) must NOT light up the league Teams link - its own section's exact team link 1452 // highlights instead. (user 2026-09-14) 1453 if (/\/(?:odds|fixtures)\/teams\/$/.test(u) && document.body && 1454 (document.body.classList.contains('facuppage') || document.body.classList.contains('worldcuppage'))) return false; 1455 return true; 1456 } 1457 if (/\/outright\.html$/.test(u)) { 1458 var seg = /\/(?:fixtures|odds)\/([^\/]+)\//.exec(u); 1459 if (seg && document.body && document.body.classList.contains('team-outright-page') && p.indexOf('/odds/' + seg[1] + '/') === 0) return true; 1460 } 1461 return false; 1462 } 1463 function buildDesktopDropdowns() { 1464 var ul = document.querySelector('.newmenu > ul'); 1465 if (!ul || ul.getAttribute('data-dd-built')) return; 1466 ul.setAttribute('data-dd-built', '1'); 1467 var lis = ul.children; 1468 for (var _x = lis.length - 1; _x >= 0; _x--) { var _xa = lis[_x].querySelector('a'); if (_xa && _navExcluded(_xa.textContent.trim())) lis[_x].parentNode.removeChild(lis[_x]); } 1469 function findLi(label) { 1470 for (var i = 0; i < lis.length; i++) { var a = lis[i].querySelector('a'); if (a && a.textContent.trim() === label) return lis[i]; } 1471 return null; 1472 } 1473 (typeof DESKTOP_STRUCTURE !== 'undefined' && DESKTOP_STRUCTURE.length ? DESKTOP_STRUCTURE : (typeof NAV_STRUCTURE !== 'undefined' ? NAV_STRUCTURE : [])).forEach(function (item) { 1474 if (!item || !item.items || !item.items.length) return; 1475 var li = findLi(item.label); 1476 if (!li || li.querySelector('ul')) return; 1477 li.classList.add('has-dd'); li.style.position = 'relative'; 1478 var sub = document.createElement('ul'); sub.className = 'nm-dd'; 1479 var anyActive = false; 1480 // 3rd level: leagues inside 'English Leagues' / 'More Comps' get a Matches/Odds flyout to their right. 1481 var derive = (item.label === 'English Leagues' || item.label === 'More Comps'); 1482 item.items.forEach(function (c) { 1483 if (!c || !c.label) return; 1484 var sli = document.createElement('li'), sa = document.createElement('a'); 1485 sa.href = c.href || '#'; sa.textContent = c.label; 1486 var hp = c.href || ''; 1487 if (_ddAct(hp) || (item.label === 'More Comps' && _compActive(hp))) { sli.className = 'dd-active'; anyActive = true; } 1488 var fly = (c.items && c.items.length) ? c.items : null; 1489 if (!fly && derive) { var mm = /^\/fixtures\/([^\/]+)\/?$/.exec(hp); if (mm) { fly = [{ label: 'Matches', href: hp }, { label: 'Odds', href: '/odds/' + mm[1] + '/' }]; if (['championship', 'division-1', 'division-2'].indexOf(mm[1]) !== -1) fly.push({ label: 'Table', href: '/fixtures/' + mm[1] + '/table.html' }); } } 1490 if (fly && fly.length) { 1491 sli.className = (sli.className ? sli.className + ' ' : '') + 'has-fly'; 1492 var fu = document.createElement('ul'); fu.className = 'nm-fly'; var childActive = false; 1493 fly.forEach(function (g) { if (!g || !g.label) return; var gl = document.createElement('li'), ga = document.createElement('a'); ga.href = g.href || '#'; ga.textContent = g.label; if (_ddScope(g.href)) { gl.className = 'dd-active'; anyActive = true; childActive = true; } gl.appendChild(ga); var _gcls = g['class'] || g.cls; if (_gcls) gl.className = (gl.className ? gl.className + ' ' : '') + _gcls; if (g.items && g.items.length) { gl.className = (gl.className ? gl.className + ' ' : '') + 'has-fly2'; var gu = document.createElement('ul'); gu.className = 'nm-fly2'; g.items.forEach(function (h) { if (!h || !h.label) return; var hl = document.createElement('li'), ha = document.createElement('a'); ha.href = h.href || '#'; ha.textContent = h.label; if (_ddScope(h.href)) { hl.className = 'dd-active'; anyActive = true; childActive = true; } hl.appendChild(ha); var _hcls = h['class'] || h.cls; if (_hcls) hl.className = (hl.className ? hl.className + ' ' : '') + _hcls; gu.appendChild(hl); }); gl.appendChild(gu); } fu.appendChild(gl); }); 1494 if (childActive && sli.className.indexOf('dd-active') === -1) sli.className += ' dd-active'; sli.appendChild(sa); sli.appendChild(fu); 1495 } else { sli.appendChild(sa); } 1496 var _ccls = c['class'] || c.cls; if (_ccls) sli.className = (sli.className ? sli.className + ' ' : '') + _ccls; 1497 sub.appendChild(sli); 1498 }); 1499 if (anyActive) li.classList.add('nm-parent-active'); 1500 li.appendChild(sub); 1501 }); 1502 if (!document.getElementById('nm-dd-style')) { 1503 var st = document.createElement('style'); st.id = 'nm-dd-style'; 1504 st.textContent = 1505 '.newmenu .has-dd{position:relative;}' + 1506 '.newmenu .has-dd > ul.nm-dd{display:none;position:absolute;
1506left:0;top:100%;margin:0;padding:0;list-style:none;z-index:9999;border:2px solid #4d4a4a;border-radius:0 0 6px 6px;background:#fff;min-width:190px;box-shadow:0 4px 12px rgba(0,0,0,.18);overflow:visible;}' + 1507 '.newmenu .has-dd:hover > ul.nm-dd{display:block;}' + 1508 '.newmenu .has-dd > ul.nm-dd > li{float:none;margin:0;}' + 1509 '.newmenu .has-dd > ul.nm-dd > li > a{display:block;padding:9px 14px;font-size:10pt;color:#1b1b1b;border-bottom:1px solid #f0e2da;white-space:nowrap;}' + 1510 '.newmenu .has-dd > ul.nm-dd > li:last-child > a{border-bottom:0;}' + 1511 '.newmenu .has-dd > ul.nm-dd > li:nth-child(even) > a{background:#fbf7f1;}' + 1512 '.newmenu .has-dd > ul.nm-dd > li > a:hover{background:#4d4a4a;color:#fff;}' + 1513 '.newmenu .has-dd > ul.nm-dd > li.dd-active > a{background:#4d4a4a;color:#fff;}' + 1514 '.newmenu > ul > li.has-dd.nm-parent-active > a{background-color:#4d4a4a !important;}' + 1515 '.newmenu .nm-dd > li.has-fly{position:relative;}' + 1516 '.newmenu .nm-dd > li.has-fly > a::after{content:"\\203A";position:absolute;right:11px;opacity:.55;}' + 1517 '.newmenu .nm-dd > li.has-fly > ul.nm-fly{display:none;position:absolute;left:100%;top:0;margin:0;padding:0;list-style:none;z-index:10000;border:2px solid #4d4a4a;border-radius:0 6px 6px 6px;background:#fff;min-width:132px;box-shadow:0 4px 12px rgba(0,0,0,.18);overflow:visible;}' + 1518 '.newmenu .nm-dd > li.has-fly:hover > ul.nm-fly{display:block;}' + 1519 '.newmenu ul.nm-fly > li{float:none;margin:0;}' + 1520 '.newmenu ul.nm-fly > li > a{display:block;padding:8px 16px;font-size:9.5pt;color:#1b1b1b;border-bottom:1px solid #f0e2da;white-space:nowrap;}' + 1521 '.newmenu ul.nm-fly > li:last-child > a{border-bottom:0;}' + 1522 '.newmenu ul.nm-fly > li > a:hover{background:#4d4a4a;color:#fff;}' + 1523 '.newmenu ul.nm-fly > li.dd-active > a{background:#4d4a4a;color:#fff;}' + 1524 '.newmenu ul.nm-fly > li.has-fly2{position:relative;}' + 1525 '.newmenu ul.nm-fly > li.has-fly2 > a::after{content:"\\203A";position:absolute;right:10px;opacity:.55;}' + 1526 '.newmenu ul.nm-fly2{display:none;position:absolute;left:100%;top:0;margin:0;padding:0;list-style:none;z-index:10001;border:2px solid #4d4a4a;border-radius:0 6px 6px 6px;background:#fff;min-width:150px;box-shadow:0 4px 12px rgba(0,0,0,.18);}' + 1527 '.newmenu ul.nm-fly > li.has-fly2:hover > ul.nm-fly2{display:block;}' + 1528 '.newmenu ul.nm-fly2 > li{float:none;margin:0;}' + 1529 '.newmenu ul.nm-fly2 > li > a{display:block;padding:8px 15px;font-size:9.5pt;color:#1b1b1b;border-bottom:1px solid #f0e2da;white-space:nowrap;}' + 1530 '.newmenu ul.nm-fly2 > li:last-child > a{border-bottom:0;}' + 1531 '.newmenu ul.nm-fly2 > li > a:hover{background:#4d4a4a;color:#fff;}' + 1532 '.newmenu ul.nm-fly2 > li.dd-active > a{background:#4d4a4a;color:#fff;}' + 1533 /* indented team sub-links that belong to the Outright item (desktop dropdown) */ 1534 '.newmenu li.nav-subitem > a{padding-left:30px !important;position:relative;}' + 1535 '.newmenu li.nav-subitem > a::before{content:"\\21B3";position:absolute;left:12px;top:50%;transform:translateY(-50%);opacity:.6;font-weight:400;}' + 1536 /* same indented team sub-links in the mobile hamburger drawer */ 1537 '.fa-nav-drawer li.nav-subitem{margin-left:1.5em;}' + 1538 '.fa-nav-drawer li.nav-subitem > a{position:relative;padding-left:1.4em !important;}' + 1539 '.fa-nav-drawer li.nav-subitem > a::before{content:"\\21B3";position:absolute;left:0;opacity:.55;font-weight:400;}'; 1540 document.head.appendChild(st); 1541 } 1542 } 1543 1544 function boot() { 1545 init(); 1546 buildDesktopDropdowns(); 1547 prependSubnavLabels(); 1548 // Section links are now in place - align the subnav at DOMContentLoaded so 1549 // it lands under the right link without waiting for the load event. 1550 alignSectionSubnav(); 1551 } 1552 if (document.readyState === 'loading') { 1553 document.addEventListener('DOMContentLoaded', boot); 1554 } else { 1555 boot(); 1556 } 1557 1558 // ââ Sub-nav alignment (SUBNAV_ALIGN_V2) ââââââââââââââââââââââââââââââ 1559 // CSS sets accurate padding-left defaults per body class so the subnav 1560 // renders in the correct position on first paint with no visible jump.
1561 // This helper is now only useful at resize time + on mobile (where the 1562 // hamburger replaces the nav and the offset should drop to 0). It is 1563 // INTENTIONALLY a no-op on initial desktop render to avoid disturbing 1564 // the CSS-painted position. We do not toggle opacity / .subnav-aligned 1565 // anymore (V4 of the CSS dropped the fade-in that was the user's "jump"). 1566 function prependSubnavLabels() { 1567 var ul = document.querySelector('.subnav > ul'); if (!ul) return; 1568 if (ul.querySelector('.subnav-section-label')) return; 1569 var b = document.body.classList, label = null; 1570 if (b.contains('euro2028page')) label = 'Euro 28'; 1571 else if (b.contains('serieapage')) label = 'Serie A'; 1572 else if (b.contains('ligue1page')) label = 'French L1'; 1573 // Champions League pages ALSO carry `worldcuppage` (so the subnav aligns under the World Cup / 1574 // More Comps main-nav link), so this MUST be checked BEFORE the worldcup case or the label reads 1575 // "World Cup" instead of "Champions League". (fixed 2026-08-28) 1576 else if (b.contains('championsleaguepage')) label = 'Champions League'; 1577 else if (b.contains('worldcuppage') || b.contains('worldcupsection')) label = 'World Cup'; 1578 else if (b.contains('bundesligapage')) label = 'Bundesliga'; 1579 else if (b.contains('laligapage')) label = 'La Liga'; 1580 else if (b.contains('championshippage')) label = 'Championship'; 1581 else if (b.contains('division1page')) label = 'Division 1'; 1582 else if (b.contains('division2page')) label = 'Division 2'; 1583 if (!label) return; 1584 if (!document.getElementById('subnav-label-style')) { 1585 var st = document.createElement('style'); st.id = 'subnav-label-style'; 1586 st.textContent = '.subnav ul li.subnav-section-label{cursor:default;} .subnav ul li.subnav-section-label .ssl-inner{display:block;background:#c12626;color:#fff;font-weight:700;font-size:12px;line-height:normal;padding:9px 10px 10px 12px;white-space:nowrap;text-transform:none;letter-spacing:0;} .subnav ul li.subnav-section-label .ssl-arrow{margin-left:4px;font-weight:400;opacity:.85;}'; 1587 document.head.appendChild(st); 1588 } 1589 var li = document.createElement('li'); li.className = 'subnav-section-label'; 1590 li.innerHTML = '<span class="ssl-inner">' + label + ' <span class="ssl-arrow">\u203A</span></span>'; 1591 ul.insertBefore(li, ul.firstChild); 1592 } 1593 function alignSectionSubnav() { 1594 if (!document.body) return; 1595 if (document.body.classList.contains('padel-page')) return; 1596 var subnavUl = document.querySelector('.subnav > ul'); 1597 if (!subnavUl) return; 1598 var anchorMap = [ 1599 // Champions League pages also carry `facuppage` (for FA-Cup result 1600 // styling), so this MUST be checked first or the subnav would align 1601 // under the FA Cup link instead of the Champions League link. 1602 { body: 'championsleaguepage', anchor: '.newmenu .worldcuplink', center: true }, 1603 { body: 'facuppage', anchor: '.newmenu .facupoddslink' }, 1604 // Championship pages get their own desktop top-menu link (added by 1605 // ensureChampionshipDesktopLink, championship pages only) immediately after 1606 // Premier League, so align the subnav under that "Championship" link. MUST 1607 // be checked before prempage (champ pages also carry prempage). 1608 { body: 'championshippage', anchor: '.newmenu .championshiplink' }, 1609 // Division 1 & 2 sit under the same "English Leagues" dropdown as the 1610 // Championship and ALSO carry prempage, so anchor their subnav to the 1611 // English Leagues link and check BEFORE prempage (else they'd align under 1612 // the Premier League link instead of English Leagues). 1613 { body: 'division1page', anchor: '.newmenu .championshiplink' }, 1614 { body: 'division2page', anchor: '.newmenu .championshiplink' }, 1615 { body: 'worldcupsection', anchor: '.newmenu .worldcuplink', center: true }, 1616 { body: 'worldcuppage', anchor: '.newmenu .worldcuplink', center: true }, 1617 { body: 'euro2028page', anchor: '.newmenu .worldcuplink', center: true }, 1618 // La Liga pages reuse the Premier League body classes for row styling, so they also 1619 // carry `prempage`. MUST be checked BEFORE prempage or the subnav aligns under the 1620 // Premier League link instead of La Liga. 1621 { body: 'bundesligapage', anchor: '.newmenu .worldcuplink', center: true }, 1622 { body: 'laligapage', anchor: '.newmenu .worldcuplink', center: true }, 1623 { body: 'serieapage', anchor: '.newmenu .worldcuplink', center: true }, 1624 { body: 'ligue1page', anchor: '.newmenu .worldcuplink', center: true }, 1625 { body: 'prempage', anchor: '.newmenu .premierleaguelink' }, 1626 // Betting pages: align the subnav under the "Betting" main-nav link. 1627 // Added 2026-07-10 - previously relied on a hardcoded style.css margin-left 1628 // which broke when the English Leagues nav link shifted Betting right. 1629 { body: 'bettingsection', anchor: '.newmenu .bettinglink' }, 1630 ]; 1631 for (var i = 0; i < anchorMap.length; i++) { 1632 var cfg = anchorMap[i]; 1633 if (!document.body.classList.contains(cfg.body)) continue; 1634 var anchor = document.querySelector(cfg.anchor); 1635 if (!anchor || !anchor.offsetParent) { 1636 // Hamburger is active (mobile) - the CSS desktop padding-left would 1637 // push the subnav off-screen. Override to 0. 1638 subnavUl.style.setProperty('padding-left', '0', 'important'); 1639 return; 1640 } 1641 // Desktop - leave the CSS default in place. Only override if the 1642 // measured offset differs meaningfully from current padding (i.e. 1643 // user resized the window and chrome reflowed the nav). NOTE: 1644 // getBoundingClientRect().left for a padded element is the BORDER 1645 // box edge (i.e. includes the padding), so aR.left - sR.left already 1646 // gives the correct padding-left value - do NOT add current. 1647 var aR = anchor.getBoundingClientRect(); 1648 var sR = subnavUl.getBoundingClientRect(); 1649 var current = parseInt(getComputedStyle(subnavUl).paddingLeft, 10) || 0; 1650 var measured; 1651 if (cfg.center) { 1652 var _kk = subnavUl.children, _cf = null, _cl = null, _r; 1653 for (var _a = 0; _a < _kk.length; _a++) { _r = _kk[_a].getBoundingClientRect(); if (_r.width > 0) { _cf = _r; break; } } 1654 for (var _b = _kk.length - 1; _b >= 0; _b--) { _r = _kk[_b].getBoundingClientRect(); if (_r.width > 0) { _cl = _r; break; } } 1655 var _cw = (_cf && _cl) ? (_cl.right - _cf.left) : 0, _ac = aR.left + aR.width / 2; 1656 measured = Math.max(8, Math.round(current + (_ac - _cw / 2 - (_cf ? _cf.left : sR.left)))); 1657 } else { 1658 measured = Math.max(0, Math.round(aR.left - sR.left)); 1659 } 1660 // Don't let the subnav run off the right edge. The World Cup subnav has many 1661 // items AND its menu word sits far right, so aligning under it overflows and 1662 // clips the tail (Teams / FAQ). If aligning under the anch
1662or would push the 1663 // last item past the viewport, shift the whole bar left JUST enough to fit, 1664 // keeping as much indent as the width allows (so it's not jammed fully left). 1665 // Sections that already fit keep their exact under-the-word alignment. 1666 // Clamp so the tail (Teams / FAQ) never runs off the right edge. Measure the 1667 // RIGHTMOST VISIBLE item, NOT subnavUl.scrollWidth: the Matches> / Odds> 1668 // dropdown flyouts are position:absolute and hugely inflate scrollWidth 1669 // (~1233px vs the ~825px of visible links), which made the old clamp think 1670 // the bar was almost viewport-wide and jam the indent to ~0 at 1280px. 1671 var _vw = document.documentElement.clientWidth || window.innerWidth || 0; 1672 var _kids = subnavUl.children, _lastRight = sR.right; 1673 for (var _k = _kids.length - 1; _k >= 0; _k--) { 1674 var _kr = _kids[_k].getBoundingClientRect(); 1675 if (_kr.width > 0 && _kr.height > 0) { _lastRight = _kr.right; break; } 1676 } 1677 if (_vw > 0) { 1678 // Shifting padding-left by (measured - current) moves the last item's 1679 // right edge by the same delta. Keep it inside the viewport (14px gutter). 1680 var _projRight = _lastRight + (measured - current); 1681 var _over = _projRight - (_vw - 14); 1682 if (_over > 0) measured = Math.max(0, measured - _over); 1683 } 1684 if (Math.abs(measured - current) > 3) { 1685 subnavUl.style.setProperty('padding-left', measured + 'px', 'important'); 1686 } 1687 return; 1688 } 1689 } 1690 // Run on resize ONLY. Initial paint relies on the CSS defaults so there 1691 // is zero JS-induced layout shift on page load. 1692 window.addEventListener('resize', alignSectionSubnav); 1693 window.addEventListener('load', alignSectionSubnav); // belt-and-braces if user resized before load fires 1694})();
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.