1 2<!doctype html> 3<html lang="en"> 4 <head> 5 <meta charset="utf-8"> 6 <meta name="viewport" content="width=device-width, initial-scale=1"> 7 <title>Launchpad Rocks</title> 8 <link rel="icon" type="image/png" sizes="32x32" href="/assets/images/favicon-32.png"> 9 <style> 10 :root { 11 --ink: #f8f7f6cf; 12 --paper: #100a00ff; 13 --paper-deep: #efe6d6; 14 --accent: #fbfaf9ff; 15 --muted: #6e655b; 16 --rule: #d6cabb; 17 } 18 19 * { 20 box-sizing: border-box; 21 } 22 23 html, body { 24 margin: 0; 25 color: var(--ink); 26 background: black; 27 font-family: arial, sans-serif; 28 min-height: 100vh; 29 overflow-x: hidden; 30 } 31 32 html.modal-open, 33 body.modal-open { 34 overflow: hidden; 35 overscroll-behavior: none; 36 } 37 38 .launch-shake { 39 animation: launch-shake 2400ms linear 1 both; 40 } 41 42 .launch-shake-violent { 43 animation: launch-shake-violent 120ms linear infinite; 44 } 45 46 @keyframes launch-shake { 47 0% { transform: translate(0, 0); } 48 5% { transform: translate(-6px, 4px); } 49 10% { transform: translate(5px, -6px); } 50 18% { transform: translate(-7px, 3px); } 51 26% { transform: translate(6px, -5px); } 52 34% { transform: translate(-4px, 3px); } 53 45% { transform: translate(4px, -3px); } 54 58% { transform: translate(-3px, 2px); } 55 72% { transform: translate(2px, -2px); } 56 86% { transform: translate(-1px, 1px); } 57 100% { transform: translate(0, 0); } 58 } 59 60 @keyframes launch-shake-violent { 61 0% { transform: translate(0, 0); } 62 20% { transform: translate(-10px, 6px); } 63 40% { transform: translate(8px, -9px); } 64 60% { transform: translate(-9px, 5px); } 65 80% { transform: translate(9px, -6px); } 66 100% { transform: translate(0, 0); } 67 } 68 69 .wrap { 70 margin: 0; 71 padding: 0 10px; 72 } 73 74 .rocket-link { 75 position: fixed; 76 top: 16px; 77 right: 16px; 78 width: 61px; 79 height: 61px; 80 z-index: 10; 81 opacity: 0.8; 82 transition: transform 140ms ease, opacity 140ms ease; 83 } 84 85 .rocket-link:hover { 86 transform: translateY(-2px) scale(1.05); 87 opacity: 1; 88 } 89 90 .rocket-link img { 91 display: block; 92 width: 100%; 93 height: 100%; 94 object-fit: contain; 95 } 96 97 98 h1 { 99 font-size: clamp(2.6rem, 4vw, 4rem); 100 letter-spacing: -0.02em; 101 margin: 0; 102 text-transform: uppercase; 103 } 104 105 .tagline { 106 font-size: 1.05rem; 107 line-height: 1.5; 108 max-width: 520px; 109 margin: 0; 110 color: var(--muted); 111 } 112 113 .event-row { 114 display: flex; 115 gap: 0; 116 align-items: flex-start; 117 text-decoration: none; 118 color: inherit; 119 padding: 3px 0; 120 cursor: pointer; 121 transform-origin: center; 122 transition: transform 500ms ease; 123 will-change: transform; 124 } 125 126 .event-row:hover { 127 color: var(--accent); 128 } 129 130 .event-date { 131 font-size: 0.90rem; 132 letter-spacing: 0.06em; 133 text-transform: uppercase; 134 color: var(--accent); 135 flex: 0 0 45px; 136 } 137 138 .event-title { 139 font-size: 2rem; 140 line-height: 1; 141 text-transform: uppercase; 142 font-family: monospace, sans-serif; 143 text-wrap: nowrap; 144 word-spacing: 5px; 145 overflow: hidden; 146 } 147 148 .event-title span { 149 display: inline-block; 150 padding-right: 32px; 151 } 152 153 .event-title.is-scrolling span { 154 animation: title-scroll var(--scroll-duration, 6s) linear infinite; 155 } 156 157 @keyframes title-scroll { 158 from { transform: translateX(0); } 159 to { transform: translateX(calc(-1 * var(--scroll-distance, 0px))); } 160 } 161 162 .modal { 163 position: fixed; 164 inset: 0; 165 /* background: rgba(0, 0, 0, 0.75); */ 166 display: flex; 167 align-items: center; 168 justify-content: center; 169 padding: 24px; 170 opacity: 0; 171 pointer-events: none; 172 transition: opacity 160ms ease; 173 z-index: 20; 174 } 175 176 .modal.is-open { 177 opacity: 1; 178 pointer-events: auto; 179 } 180
181 .modal-shell { 182 background: #0c0a08; 183 color: var(--ink); 184 max-width: 900px; 185 width: 100%; 186 max-height: 85vh; 187 overflow: auto; 188 border: 1px solid rgba(255, 255, 255, 0.08); 189 box-shadow: 0 20px 60px rgba(0, 0, 0, 0.6); 190 position: relative; 191 padding: 24px; 192 } 193 194 .modal-close { 195 position: absolute; 196 top: 0px; 197 right: 6px; 198 background: transparent; 199 border: none; 200 color: var(--ink); 201 font-size: 2.4rem; 202 cursor: pointer; 203 text-shadow: 2px 2px 2px rgba(0, 0, 0, .8); 204 } 205 206 .modal-body { 207 font-size: 1rem; 208 line-height: 1.5; 209 } 210 211 .modal-body a { 212 color: #ffb48a; 213 } 214 215 .modal-body .flyer { 216 width: 300px; 217 max-width: 300px; 218 margin: 0 auto 16px; 219 } 220
221 .modal-body .flyer img { 222 width: 100%; 223 height: auto; 224 display: block; 225 } 226 227 .modal-body .buy { 228 margin: 12px 0 6px; 229 } 230 231 .modal-body .buy a { 232 display: inline-block; 233 padding: 10px 18px; 234 background: #ff6a00; 235 color: #1b0d00; 236 font-weight: 700; 237 font-size: 1.05rem; 238 text-transform: uppercase; 239 letter-spacing: 0.12em; 240 border: 2px solid #1b0d00; 241 box-shadow: 3px 3px 0 rgba(0, 0, 0, 0.45); 242 text-decoration: none; 243 } 244 245 .empty { 246 font-size: 1.1rem; 247 color: var(--muted); 248 } 249 250 .footer { 251 margin-top: 48px; 252 font-size: 1.6rem; 253 color: var(--muted); 254 position: relative; 255 padding: 20px; 256 transition: box-shadow 300ms ease, color 300ms ease; 257 } 258 259 .footer-nav { 260 margin: 0; 261 padding: 0; 262 list-style: none; 263 display: flex; 264 flex-wrap: wrap; 265 gap: 16px 24px; 266 text-transform: uppercase; 267 letter-spacing: 0.08em; 268 font-size: 1.4rem; 269 } 270 271 .footer-nav a { 272 color: inherit; 273 text-decoration: none; 274 } 275 276 .footer-nav a:hover { 277 color: var(--accent); 278 } 279 280 .footer-glow { 281 color: #ffb48a; 282 box-shadow: 283 0 -20px 60px rgba(255, 120, 40, 0.35), 284 0 -6px 18px rgba(255, 90, 20, 0.6); 285 animation: footer-glow 2400ms ease-out 1 both; 286 } 287 .address { 288 margin-top: 12px; 289 } 290 291 @keyframes footer-glow { 292 0% { 293 box-shadow: 294 0 -28px 80px rgba(255, 120, 40, 0.5), 295 0 -8px 22px rgba(255, 90, 20, 0.7); 296 opacity: 1; 297 } 298 70% { 299 box-shadow: 300 0 -12px 40px rgba(255, 120, 40, 0.3), 301 0 -4px 14px rgba(255, 90, 20, 0.4); 302 opacity: 0.9; 303 } 304 100% { 305 box-shadow: 306 0 -4px 10px rgba(255, 120, 40, 0.1), 307 0 -2px 6px rgba(255, 90, 20, 0.15); 308 opacity: 0.7; 309 } 310 } 311 312 @media (max-width: 640px) { 313 .event-row { 314 padding: 1px 0; 315 } 316 317 .event-date { 318 font-size: 0.75rem; 319 } 320 321 .event-title { 322 font-size: 1.2rem; 323 }
324 .modal-shell { 325 padding: 0px; 326 } 327 328 .modal-body .flyer { 329 width: 100%; 330 max-width: 100%; 331 } 332 333 .modal-body .header-event-box h1 { 334 font-size: 1.35rem; 335 } 336 337 .modal-body .buy { 338 text-align: center; 339 } 340 .modal-close { 341 position: fixed; 342 top: 31px; 343 right: 5px; 344 } 345 } 346 </style> 347 </head> 348 <body> 349 <a class="rocket-link" href="#footer" aria-label="Jump to footer"> 350 <img src="/assets/images/rocket_cropped.webp" alt=""> 351 </a> 352 <main class="wrap"> 353 <section class="events"> 354 355 <div class="event-row" data-event-id="467088"> 356 <div class="event-date"> 357 Sep<br><span class="dom">25</span> 358 </div> 359 <div class="event-title"> 360 <span>"9 2 5" Dolly Parton Tribute Show </span> 361 </div> 362 </div> 363 364 365 <div class="event-row" data-event-id="467087"> 366 <div class="event-date"> 367 Sep<br><span class="dom">26</span> 368 </div> 369 <div class="event-title"> 370 <span>Dead Payphones * Ditch Dog * Anti Gravity Love Machine * Suitor Scenario</span> 371 </div> 372 </div> 373 374 375 <div class="event-row" data-event-id="461621"> 376 <div class="event-date"> 377 Sep<br><span class="dom">27</span> 378 </div> 379 <div class="event-title"> 380 <span>Silly Goose * Cheem * Frigga Five </span> 381 </div> 382 </div> 383 384 385 <div class="event-row" data-event-id="462090"> 386 <div class="event-date"> 387 Sep<br><span class="dom">29</span> 388 </div> 389 <div class="event-title"> 390 <span>Unto Others * Wraith Knight * Coma Recovery</span> 391 </div> 392 </div> 393 394 395 <div class="event-row" data-event-id="457889"> 396 <div class="event-date"> 397 Oct<br><span class="dom">01</span> 398 </div> 399 <div class="event-title"> 400 <span>Hot Water Music * Russian Girlfriends * Past Lives</span> 401 </div> 402 </div> 403 404 405 <div class="event-row" data-event-id="466017"> 406 <div class="event-date"> 407 Oct<br><span class="dom">02</span> 408 </div> 409 <div class="event-title"> 410 <span>Night of the Living Cover Bands - All Nights</span> 411 </div> 412 </div> 413 414 415 <div class="event-row" data-event-id="453230"> 416 <div class="event-date"> 417 Oct<br><span class="dom">02</span> 418 </div> 419 <div class="event-title">
420 <span>Night Of The Living Cover Bands - Night 1 </span> 421 </div> 422 </div> 423 424 425 <div class="event-row" data-event-id="453922"> 426 <div class="event-date"> 427 Oct<br><span class="dom">03</span> 428 </div> 429 <div class="event-title"> 430 <span>Night Of The Living Cover Bands - Night 2</span> 431 </div> 432 </div> 433 434 435 <div class="event-row" data-event-id="466104"> 436 <div class="event-date"> 437 Oct<br><span class="dom">04</span> 438 </div> 439 <div class="event-title"> 440 <span>Bl'ast * Luicidal * Sorry Guero </span> 441 </div> 442 </div> 443 444 445 <div class="event-row" data-event-id="466110"> 446 <div class="event-date"> 447 Oct<br><span class="dom">05</span> 448 </div> 449 <div class="event-title"> 450 <span>Exhorder * No/Mas * Phantom * Intoxicated </span> 451 </div> 452 </div> 453 454 455 <div class="event-row" data-event-id="463679"> 456 <div class="event-date"> 457 Oct<br><span class="dom">07</span> 458 </div> 459 <div class="event-title"> 460 <span>Combichrist * Ludovico Technique * Dead Animal Assembly Plant * DJ K.0ss</span> 461 </div> 462 </div> 463 464 465 <div class="event-row" data-event-id="467195"> 466 <div class="event-date"> 467 Oct<br><span class="dom">08</span> 468 </div> 469 <div class="event-title"> 470 <span> Anti Gravity Love Machine * In Fair Condition * ShyGuy * Happy Accident</span> 471 </div> 472 </div> 473 474 475 <div class="event-row" data-event-id="454145"> 476 <div class="event-date"> 477 Oct<br><span class="dom">09</span> 478 </div> 479 <div class="event-title"> 480 <span>Night Of The Living Cover Bands - Night 3</span> 481 </div> 482 </div> 483 484 485 <div class="event-row" data-event-id="464905"> 486 <div class="event-date"> 487 Oct<br><span class="dom">10</span> 488 </div> 489 <div class="event-title"> 490 <span>Night Of The Living Cover Bands - Night 4</span> 491 </div> 492 </div> 493 494 495 <div class="event-row" data-event-id="464639"> 496 <div class="event-date"> 497 Oct<br><span class="dom">12</span> 498 </div> 499 <div class="event-title"> 500 <span>Chinese Football - Occupy the Absence Tour </span> 501 </div> 502 </div> 503 504 505 <div class="event-row" data-event-id="461057"> 506 <div class="event-date"> 507 Oct<br><span class="dom">13</span> 508 </div> 509 <div class="event-title">
510 <span>Cryogeyser * Starling * Heaven's Night </span> 511 </div> 512 </div> 513 514 515 <div class="event-row" data-event-id="461937"> 516 <div class="event-date"> 517 Oct<br><span class="dom">14</span> 518 </div> 519 <div class="event-title"> 520 <span>Blitzkid - Modern Hel Tour 2026</span> 521 </div> 522 </div> 523 524 525 <div class="event-row" data-event-id="454659"> 526 <div class="event-date"> 527 Oct<br><span class="dom">16</span> 528 </div> 529 <div class="event-title"> 530 <span>Night Of The Living Cover Bands - Night 5</span> 531 </div> 532 </div> 533 534 535 <div class="event-row" data-event-id="464972"> 536 <div class="event-date"> 537 Oct<br><span class="dom">17</span> 538 </div> 539 <div class="event-title"> 540 <span>Night Of The Living Cover Bands - Night 6</span> 541 </div> 542 </div> 543 544 545 <div class="event-row" data-event-id="467020"> 546 <div class="event-date"> 547 Oct<br><span class="dom">18</span> 548 </div> 549 <div class="event-title"> 550 <span>Three Bad Jacks </span> 551 </div> 552 </div> 553 554 555 <div class="event-row" data-event-id="458375"> 556 <div class="event-date"> 557 Oct<br><span class="dom">20</span> 558 </div> 559 <div class="event-title"> 560 <span>she's green * Witches Exist * smush </span> 561 </div> 562 </div> 563 564 565 <div class="event-row" data-event-id="463245"> 566 <div class="event-date"> 567 Oct<br><span class="dom">21</span> 568 </div> 569 <div class="event-title"> 570 <span>Authority Zero * Counterpunch * Right On, Kid * Washpot </span> 571 </div> 572 </div> 573 574 575 <div class="event-row" data-event-id="454712"> 576 <div class="event-date"> 577 Oct<br><span class="dom">23</span> 578 </div> 579 <div class="event-title"> 580 <span>Night Of The Living Cover Bands - Night 7</span> 581 </div> 582 </div> 583 584 585 <div class="event-row" data-event-id="454713"> 586 <div class="event-date"> 587 Oct<br><span class="dom">24</span> 588 </div> 589 <div class="event-title"> 590 <span>Night Of The Living Cover Bands - Night 8</span> 591 </div> 592 </div> 593 594 595 <div class="event-row" data-event-id="464444"> 596 <div class="event-date"> 597 Oct<br><span class="dom">25</span> 598 </div> 599 <div class="event-title">
600 <span>Token - I'm Leaving Soon Tour </span> 601 </div> 602 </div> 603 604 605 <div class="event-row" data-event-id="465315"> 606 <div class="event-date"> 607 Oct<br><span class="dom">26</span> 608 </div> 609 <div class="event-title"> 610 <span>Boundaries * Centennial </span> 611 </div> 612 </div> 613 614 615 <div class="event-row" data-event-id="463660"> 616 <div class="event-date"> 617 Oct<br><span class="dom">27</span> 618 </div> 619 <div class="event-title"> 620 <span>Sex Mex - The Stockpile Tour </span> 621 </div> 622 </div> 623 624 625 <div class="event-row" data-event-id="456304"> 626 <div class="event-date"> 627 Oct<br><span class="dom">28</span> 628 </div> 629 <div class="event-title"> 630 <span>REZN * Lowheaven * Nomestomper </span> 631 </div> 632 </div> 633 634 635 <div class="event-row" data-event-id="457240"> 636 <div class="event-date"> 637 Oct<br><span class="dom">30</span> 638 </div> 639 <div class="event-title"> 640 <span>Launchpad presents Jim Henson's Labyrinth: In Concert 40 Anniversary at KiMo Theatre</span> 641 </div> 642 </div> 643 644 645 <div class="event-row" data-event-id="454714"> 646 <div class="event-date"> 647 Oct<br><span class="dom">30</span> 648 </div> 649 <div class="event-title"> 650 <span>Night Of The Living Cover Bands - Night 9</span> 651 </div> 652 </div> 653 654 655 <div class="event-row" data-event-id="454658"> 656 <div class="event-date"> 657 Oct<br><span class="dom">31</span> 658 </div> 659 <div class="event-title"> 660 <span>5th Annual HALLOWEEN DANCE PARTY at Launchpad!</span> 661 </div> 662 </div> 663 664 665 <div class="event-row" data-event-id="465160"> 666 <div class="event-date"> 667 Nov<br><span class="dom">01</span> 668 </div> 669 <div class="event-title"> 670 <span>Night Of The Living Cover Bands - All Ages Encore </span> 671 </div> 672 </div> 673 674 675 <div class="event-row" data-event-id="466628"> 676 <div class="event-date"> 677 Nov<br><span class="dom">03</span> 678 </div> 679 <div class="event-title"> 680 <span>Escape The Fate * Atlas//Below * Dakota Ave</span> 681 </div> 682 </div> 683 684 685 <div class="event-row" data-event-id="465244"> 686 <div class="event-date"> 687 Nov<br><span class="dom">08</span> 688 </div> 689 <div class="event-title">
690 <span>Ouija Macc * Darby O'Trill * Mikey Rotten</span> 691 </div> 692 </div> 693 694 695 <div class="event-row" data-event-id="465490"> 696 <div class="event-date"> 697 Nov<br><span class="dom">11</span> 698 </div> 699 <div class="event-title"> 700 <span>2Dead Boys</span> 701 </div> 702 </div> 703 704 705 <div class="event-row" data-event-id="464319"> 706 <div class="event-date"> 707 Nov<br><span class="dom">13</span> 708 </div> 709 <div class="event-title"> 710 <span>Kaotic State * Deforme * Unseen Espiritu * Ritual Noise Abuse * Destroy To Recreate</span> 711 </div> 712 </div> 713 714 715 <div class="event-row" data-event-id="463324"> 716 <div class="event-date"> 717 Nov<br><span class="dom">14</span> 718 </div> 719 <div class="event-title"> 720 <span>Violet Grohl - Be Sweet To Me Tour </span> 721 </div> 722 </div> 723 724 725 <div class="event-row" data-event-id="462540"> 726 <div class="event-date"> 727 Nov<br><span class="dom">15</span> 728 </div> 729 <div class="event-title"> 730 <span>INOHA - This Might Be Useful Tour </span> 731 </div> 732 </div> 733 734 735 <div class="event-row" data-event-id="464988"> 736 <div class="event-date"> 737 Nov<br><span class="dom">16</span> 738 </div> 739 <div class="event-title"> 740 <span>Locked Shut * Rust * Music Is The Enemy * Cerberus </span> 741 </div> 742 </div> 743 744 745 <div class="event-row" data-event-id="464660"> 746 <div class="event-date"> 747 Nov<br><span class="dom">20</span> 748 </div> 749 <div class="event-title"> 750 <span>Forbidden - Chaos By Design US Tour 2026</span> 751 </div> 752 </div> 753 754 755 <div class="event-row" data-event-id="461280"> 756 <div class="event-date"> 757 Nov<br><span class="dom">21</span> 758 </div> 759 <div class="event-title"> 760 <span>Holy Wave * Queen Serene * The Art Dept.</span> 761 </div> 762 </div> 763 764 765 <div class="event-row" data-event-id="463353"> 766 <div class="event-date"> 767 Nov<br><span class="dom">24</span> 768 </div> 769 <div class="event-title"> 770 <span>Thoughts on Bowling * Softly * HummusVacuum * MooseCreek Park</span> 771 </div> 772 </div> 773 774 775 <div class="event-row" data-event-id="466717"> 776 <div class="event-date"> 777 Dec<br><span class="dom">11</span> 778 </div> 779 <div class="event-title">
780 <span>Guilt Trip * Gates To Hell</span> 781 </div> 782 </div> 783 784 785 <div class="event-row" data-event-id="462876"> 786 <div class="event-date"> 787 Dec<br><span class="dom">15</span> 788 </div> 789 <div class="event-title"> 790 <span>Kingdom Of Giants * Deadlands * Holy Wars * Night Rider </span> 791 </div> 792 </div> 793 794 795 <div class="event-row" data-event-id="462975"> 796 <div class="event-date"> 797 Dec<br><span class="dom">18</span> 798 </div> 799 <div class="event-title"> 800 <span>Save Ferris * Rocksteady Pickups * DJ Rootz Rocka </span> 801 </div> 802 </div> 803 804 805 <div class="event-row" data-event-id="464317"> 806 <div class="event-date"> 807 Jan<br><span class="dom">19</span> 808 </div> 809 <div class="event-title"> 810 <span> Koffin Kats * The Last Gang * Starving Wolves </span> 811 </div> 812 </div> 813 814 815 <div class="event-row" data-event-id="466284"> 816 <div class="event-date"> 817 Jan<br><span class="dom">23</span> 818 </div> 819 <div class="event-title"> 820 <span>Emo Night Karaoke </span> 821 </div> 822 </div> 823 824 825 <div class="event-row" data-event-id="463904"> 826 <div class="event-date"> 827 Feb<br><span class="dom">03</span> 828 </div> 829 <div class="event-title"> 830 <span>Nervosa - North America Slave Machine Tour 2027</span> 831 </div> 832 </div> 833 834 </section> 835 <style> 836 .footer .footer-item { 837 position: relative; 838 } 839 840 .footer-toggle { 841 background: none; 842 border: 0; 843 color: inherit; 844 font: inherit; 845 text-transform: inherit; 846 letter-spacing: inherit; 847 cursor: pointer; 848 padding: 0; 849 } 850 851 .footer-subnav { 852 list-style: none; 853 margin: 8px 0 0; 854 padding: 0; 855 display: none; 856 gap: 6px; 857 flex-direction: column; 858 font-size: 0.95rem; 859 text-transform: none; 860 letter-spacing: 0.02em; 861 } 862 863 .footer-subnav.is-open { 864 display: flex; 865 } 866 867 .footer-subnav a { 868 color: inherit; 869 text-decoration: none; 870 } 871 872 .footer-subnav a:hover { 873 color: var(--accent); 874 } 875 876 .footer-subnav .is-hidden { 877 display: none; 878 } 879</style> 880<footer class="footer" id="footer"> 881 <ul class="footer-nav"> 882 <li class="footer-item"> 883 <a href="/month">Calendar</a> 884 </li> 885 <li class="footer-item"> 886 <a href="/shopping">Store</a> 887 </li> 888 <li class="footer-item"> 889 <a href="/contact_page">Contact</a> 890 </li> 891 <li class="footer-item"> 892 <button class="footer-toggle" type="button" aria-expanded="false"> 893 Media </button> 894 <ul class="footer-subnav" aria-hidden="true"> 895 <li class=""> 896 <a href="https://www.instagram.com/launchpadabq/?hl=en">Instagram Photos</a> 897 </li> 898 <li class=""> 899 <a href="https://www.facebook.com/launchpadrocks/">Facebook Photos</a> 900 </li> 901 <li class="is-hidden"> 902 <a href="/event_videos">Videos</a> 903 </li> 904 </ul> 905 </li> 906 </ul> 907 <div class="address">618 Central Ave SW, Albuquerque, NM 87102</div> 908 </footer>
909<script> 910 (() => { 911 const toggles = document.querySelectorAll('.footer-toggle'); 912 toggles.forEach((toggle) => { 913 toggle.addEventListener('click', () => { 914 const subnav = toggle.parentElement ? toggle.parentElement.querySelector('.footer-subnav') : null; 915 if (!subnav) { 916 return; 917 } 918 const isOpen = subnav.classList.toggle('is-open'); 919 subnav.setAttribute('aria-hidden', isOpen ? 'false' : 'true'); 920 toggle.setAttribute('aria-expanded', isOpen ? 'true' : 'false'); 921 }); 922 }); 923 })(); 924</script>
924 925 </main> 926 <div class="modal" id="event-modal" aria-hidden="true"> 927 <div class="modal-shell" role="dialog" aria-modal="true" aria-labelledby="modal-title"> 928 <button class="modal-close" type="button" aria-label="Close">Ã</button> 929 <div class="modal-body" id="modal-body">Loading...</div> 930 </div> 931 </div> 932
932<script> 933 (() => { 934 const prefersReduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches; 935 if (prefersReduced) { 936 return; 937 } 938 939 const easeInOut = (t) => (t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t); 940 const animateScroll = (from, to, duration, done) => { 941 const start = performance.now(); 942 const tick = (now) => { 943 const elapsed = now - start; 944 const progress = Math.min(elapsed / duration, 1); 945 const eased = easeInOut(progress); 946 window.scrollTo(0, from + (to - from) * eased); 947 if (progress < 1) { 948 requestAnimationFrame(tick); 949 } else if (done) { 950 done(); 951 } 952 }; 953 requestAnimationFrame(tick); 954 }; 955 956 const getViewportHeight = () => { 957 if (window.visualViewport && window.visualViewport.height) { 958 return window.visualViewport.height; 959 } 960 return window.innerHeight; 961 }; 962 963 const getScrollTarget = () => { 964 const footer = document.querySelector('.footer'); 965 const footerBottom = footer 966 ? footer.getBoundingClientRect().bottom + window.scrollY 967 : 0; 968 const doc = document.documentElement; 969 const body = document.body; 970 const docHeight = Math.max( 971 doc.scrollHeight, 972 body.scrollHeight, 973 doc.offsetHeight, 974 body.offsetHeight, 975 doc.clientHeight 976 ); 977 const viewportHeight = getViewportHeight(); 978 const maxScroll = Math.max(0, Math.min(footerBottom || docHeight, docHeight) - viewportHeight); 979 980 console.log('Footer bottom:', footerBottom); 981 console.log('Doc height:', docHeight); 982 console.log('Viewport height:', viewportHeight); 983 console.log('Max scroll:', maxScroll); 984 985 return maxScroll; 986 }; 987 988 const isHomePath = () => window.location.pathname === '/' || window.location.pathname === ''; 989 990 const runLaunchScroll = () => { 991 if (!isHomePath()) { 992 return; 993 } 994 const maxScroll = getScrollTarget(); 995 if (maxScroll <= 0) { 996 return; 997 } 998 window.scrollTo(0, 0); 999 animateScroll(0, maxScroll, 300, () => { 1000 setTimeout(() => { 1001 const footer = document.querySelector('.footer'); 1002 if (footer) { 1003 footer.classList.add('footer-glow'); 1004 } 1005 document.body.classList.add('launch-shake-violent'); 1006 setTimeout(() => { 1007 document.body.classList.remove('launch-shake-violent'); 1008 document.body.classList.add('launch-shake'); 1009 animateScroll(maxScroll, 0, 2160, () => { 1010 document.body.classList.remove('launch-shake'); 1011 if (footer) { 1012 footer.classList.remove('footer-glow'); 1013 } 1014 }); 1015 }, 320); 1016 }, 250); 1017 }); 1018 }; 1019 1020 window.addEventListener('load', () => { 1021 requestAnimationFrame(() => { 1022 setTimeout(runLaunchScroll, 120); 1023 }); 1024 }); 1025 1026 const rocketLink = document.querySelector('.rocket-link'); 1027 if (rocketLink) { 1028 rocketLink.addEventListener('click', (event) => { 1029 event.preventDefault(); 1030 const target = getScrollTarget(); 1031 if (target <= 0) { 1032 return; 1033 } 1034 animateScroll(window.scrollY, target, 500); 1035 }); 1036 } 1037 1038 const titleEls = document.querySelectorAll('.event-title');
1039 titleEls.forEach((titleEl) => { 1040 const textEl = titleEl.querySelector('span'); 1041 if (!textEl) { 1042 return; 1043 } 1044 const updateScroll = () => { 1045 const overflow = textEl.scrollWidth - titleEl.clientWidth; 1046 if (overflow > 4) { 1047 const duration = Math.min(10, Math.max(4, overflow / 60)); 1048 titleEl.style.setProperty('--scroll-distance', `${overflow}px`); 1049 titleEl.style.setProperty('--scroll-duration', `${duration}s`); 1050 titleEl.classList.add('is-scrolling'); 1051 } else { 1052 titleEl.classList.remove('is-scrolling'); 1053 } 1054 }; 1055 titleEl.addEventListener('mouseenter', updateScroll); 1056 titleEl.addEventListener('mouseleave', () => { 1057 titleEl.classList.remove('is-scrolling'); 1058 }); 1059 }); 1060 1061 const modal = document.getElementById('event-modal'); 1062 const modalBody = document.getElementById('modal-body'); 1063 const modalClose = modal ? modal.querySelector('.modal-close') : null; 1064 const eventRows = Array.from(document.querySelectorAll('.event-row')); 1065 let frameActive = false; 1066 let activeEventId = null; 1067 const basePath = '/'; 1068 const eventPathPrefix = '/event/'; 1069 const eventsEndpoint = '/events.php'; 1070 1071 if (modalBody) { 1072 modalBody.addEventListener('click', (event) => { 1073 const trigger = event.target.closest('.calendars > a'); 1074 if (!trigger) { 1075 return; 1076 } 1077 event.preventDefault(); 1078 const container = trigger.closest('.calendars'); 1079 const options = container ? container.querySelector('#calendar-options') : null; 1080 if (!options) { 1081 return; 1082 } 1083 const isOpen = options.style.display === 'block'; 1084 options.style.display = isOpen ? 'none' : 'block'; 1085 }); 1086 1087 modalBody.addEventListener('click', (event) => { 1088 const trigger = event.target.closest('.venuelink'); 1089 if (!trigger) { 1090 return; 1091 } 1092 event.preventDefault(); 1093 const container = trigger.closest('.venue-details'); 1094 const details = container ? container.querySelector('#venue-more') : null; 1095 if (!details) { 1096 return; 1097 } 1098 const isOpen = details.style.display === 'block'; 1099 details.style.display = isOpen ? 'none' : 'block'; 1100 }); 1101 } 1102 1103 const getEventIdFromPath = () => { 1104 const path = window.location.pathname || ''; 1105 if (!path.startsWith(eventPathPrefix)) { 1106 return null; 1107 } 1108 const id = path.slice(eventPathPrefix.length).split('/')[0]; 1109 return /^[0-9]+$/.test(id) ? id : null; 1110 }; 1111 1112 const frameRows = () => { 1113 const total = eventRows.length; 1114 if (!total) { 1115 return; 1116 } 1117 1118 const baseScale = 1; 1119 const frameGap = 28; 1120 const framePadding = 24; 1121 const modalShell = modal ? modal.querySelector('.modal-shell') : null; 1122 const modalRect = modalShell ? modalShell.getBoundingClientRect() : null; 1123 const modalWidth = modalRect ? modalRect.width : 0;
1124 const modalHeight = modalRect ? modalRect.height : 0; 1125 const frameRect = { 1126 left: framePadding, 1127 right: window.innerWidth - framePadding, 1128 top: framePadding, 1129 bottom: window.innerHeight - framePadding, 1130 }; 1131 console.log('Modal rect:', modalRect); 1132 // console.log('Frame offset:', frameOffsetX, frameOffsetY); 1133 console.log('Frame rect:', frameRect); 1134 const groups = { 1135 top: [], 1136 right: [], 1137 bottom: [], 1138 left: [], 1139 }; 1140 eventRows.forEach((row, index) => { 1141 const slot = index % 4; 1142 if (slot === 0) groups.top.push(row); 1143 if (slot === 1) groups.right.push(row); 1144 if (slot === 2) groups.bottom.push(row); 1145 if (slot === 3) groups.left.push(row); 1146 }); 1147 1148 const randomInRange = (min, max) => min + Math.random() * (max - min); 1149 1150 const positionRow = (row, side) => { 1151 const viewportHeight = getViewportHeight(); 1152 const rect = row.getBoundingClientRect(); 1153 const scale = baseScale; 1154 let targetX = rect.left; 1155 let targetY = rect.top; 1156 let rotate = 0; 1157 1158 if (side === 'top') { 1159 targetX = randomInRange(frameRect.left, frameRect.right - rect.width * scale); 1160 targetY = frameRect.top + frameGap - rect.height * scale; 1161 // targetX = 0; 1162 // targetY = 0; 1163 } 1164 1165 if (side === 'bottom') { 1166 targetX = randomInRange(frameRect.left, frameRect.right - rect.width * scale); 1167 targetY = frameRect.bottom - frameGap; 1168 } 1169 1170 if (side === 'left') { 1171 targetX = frameRect.left - frameGap - rect.height * scale - (modalWidth * 0.5); 1172 if (window.matchMedia('(max-width: 640px)').matches) { 1173 targetX = frameRect.right * -0.5 + frameGap / 2 1174 } 1175 targetY = viewportHeight / 2;//randomInRange(frameRect.top, frameRect.bottom - rect.width * scale) + (modalHeight * 0.5); //frameRect.bottom - rect.width * scale) + (modalHeight) 1176 rotate = -90; 1177 } 1178 1179 if (side === 'right') { 1180 1181 // targetX = frameRect.right - (modalWidth * 1); 1182 // if (window.matchMedia('(max-width: 640px)').matches) { 1183 // targetX = frameRect.right * 0.5 1184 // } 1185 // targetY = randomInRange(frameRect.top, frameRect.bottom - rect.width * scale + modalHeight * 1.5); 1186 // rotate = 90; 1187 rotate = 90; 1188 targetX = frameRect.left + frameGap + (modalWidth * 0.5) 1189 if (window.matchMedia('(max-width: 640px)').matches) { 1190 targetX = frameRect.right * 0.5 1191 } 1192 targetY = viewportHeight / 2; //randomInRange(frameRect.top, frameRect.bottom - rect.width * scale) + (modalHeight * 0.5); //frameRect.bottom - rect.width * scale) + (modalHeight) 1193 } 1194 1195 // if (side === 'top') { 1196 // targetX = randomInRange(frameRect.left, frameRect.right - rect.width * scale); 1197 // targetY = frameRect.top + frameGap - rect.height * scale; 1198 // // targetX = 0; 1199 // // targetY = 0; 1200 // } 1201 1202 // if (side === 'bottom') { 1203 // targetX = randomInRange(frameRect.left, frameRect.right - rect.width * scale); 1204 // targetY = frameRect.bottom - frameGap; 1205 // } 1206 1207 // if (side === 'left') { 1208 // targetX = frameRect.left - frameGap; 1209 // targetY = randomInRange(frameRect.top, frameRect.bottom - rect.width * scale); 1210 // rotate = -90; 1211 // } 1212 1213 // if (side === 'right') { 1214 // targetX = frameRect.right + frameGap; 1215 // targetY = randomInRange(frameRect.top, frameRect.bottom - rect.width * scale); 1216 // rotate = 90; 1217 // } 1218 1219 const deltaX = targetX - rect.left; 1220 const deltaY = targetY - rect.top; 1221 row.style.transform = `translate(${deltaX}px, ${deltaY}px) rotate(${rotate}deg) scale(${scale})`; 1222 }; 1223
1224 groups.top.forEach((row) => positionRow(row, 'top')); 1225 groups.right.forEach((row) => positionRow(row, 'right')); 1226 groups.bottom.forEach((row) => positionRow(row, 'bottom')); 1227 groups.left.forEach((row) => positionRow(row, 'left')); 1228 }; 1229 1230 const resetRows = () => { 1231 eventRows.forEach((row) => { 1232 row.style.transform = ''; 1233 }); 1234 }; 1235 1236 const repositionBuy = () => { 1237 if (!modalBody) { 1238 return; 1239 } 1240 const buy = modalBody.querySelector('.buy'); 1241 if (!buy) { 1242 return; 1243 } 1244 const eventTop = modalBody.querySelector('.event-top'); 1245 const flyer = modalBody.querySelector('.flyer'); 1246 const headerBox = modalBody.querySelector('.header-event-box'); 1247 if (eventTop && flyer && headerBox) { 1248 eventTop.insertBefore(buy, headerBox); 1249 return; 1250 } 1251 const title = modalBody.querySelector('h1'); 1252 if (title && title.parentElement) { 1253 title.parentElement.insertBefore(buy, title); 1254 } 1255 }; 1256 1257 const openModal = (html) => { 1258 if (!modal || !modalBody) { 1259 return; 1260 } 1261 modalBody.innerHTML = html; 1262 repositionBuy(); 1263 modal.classList.add('is-open'); 1264 modal.setAttribute('aria-hidden', 'false'); 1265 document.body.classList.add('modal-open'); 1266 document.documentElement.classList.add('modal-open'); 1267 if (!frameActive) { 1268 frameActive = true; 1269 setTimeout(() => { 1270 requestAnimationFrame(frameRows); 1271 }, 500); 1272 1273 } 1274 }; 1275 const closeModal = () => { 1276 if (!modal) { 1277 return; 1278 } 1279 modal.classList.remove('is-open'); 1280 modal.setAttribute('aria-hidden', 'true'); 1281 document.body.classList.remove('modal-open'); 1282 document.documentElement.classList.remove('modal-open'); 1283 if (activeEventId) { 1284 history.pushState({ eventId: null }, '', basePath); 1285 activeEventId = null; 1286 } 1287 if (frameActive) { 1288 resetRows(); 1289 frameActive = false; 1290 } 1291 }; 1292 if (modal && modalClose) { 1293 modal.addEventListener('click', (event) => { 1294 if (event.target === modal) { 1295 closeModal(); 1296 } 1297 }); 1298 modalClose.addEventListener('click', closeModal); 1299 document.addEventListener('keydown', (event) => { 1300 if (event.key === 'Escape') { 1301 closeModal(); 1302 } 1303 }); 1304 } 1305 1306 document.querySelectorAll('.event-row').forEach((row) => { 1307 row.addEventListener('click', async () => { 1308 const eventId = row.getAttribute('data-event-id'); 1309 if (!eventId) { 1310 return; 1311 } 1312 activeEventId = eventId; 1313 history.pushState({ eventId }, '', `${eventPathPrefix}${eventId}`); 1314 openModal('Loading...'); 1315 try { 1316 const response = await fetch(`${eventsEndpoint}?event_id=${encodeURIComponent(eventId)}`); 1317 console.log('Fetch response:', response); 1318 // if (!response.ok) { 1319 // throw new Error('Request failed'); 1320 // } 1321 const html = await response.text(); 1322 openModal(html); 1323 } catch (error) { 1324 openModal('Unable to load event details right now.'); 1325 } 1326 }); 1327 }); 1328 1329 window.addEventListener('popstate', (event) => { 1330 const stateEventId = event.state && event.state.eventId ? String(event.state.eventId) : null; 1331 const pathEventId = getEventIdFromPath(); 1332 const nextEventId = stateEventId || pathEventId; 1333 if (!nextEventId) { 1334 if (modal && modal.classList.contains('is-open')) { 1335 closeModal(); 1336 } 1337 return; 1338 } 1339 if (nextEventId === activeEventId && modal && modal.classList.contains('is-open')) { 1340 return; 1341 } 1342 activeEventId = nextEventId; 1343 openModal('Loading...'); 1344 fetch(`${eventsEndpoint}?event_id=${encodeURIComponent(nextEventId)}`) 1345 .then((response) => response.text()) 1346 .then((html) => openModal(html)) 1347 .catch(() => openModal('Unable to load event details right now.')); 1348 }); 1349 1350 const initialEventId = getEventIdFromPath(); 1351 if (initialEventId) { 1352 activeEventId = initialEventId; 1353 history.replaceState({ eventId: initialEventId }, '', `${eventPathPrefix}${initialEventId}`); 1354 openModal('Loading...'); 1355 fetch(`${eventsEndpoint}?event_id=${encodeURIComponent(initialEventId)}`) 1356 .then((response) => response.text()) 1357 .then((html) => openModal(html)) 1358 .catch(() => openModal('Unable to load event details right now.')); 1359 } 1360 })(); 1361 </script>
1361 1362 </body> 1363</html>
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.