:root{
  --cyan: #7fe7e0;
  --cyan-soft: rgba(127,231,224,.55);
  --ink: #0b1012;
  --card-bg: rgba(16,24,27,.55);
  --text: #eaf6f6;
  --text-dim: rgba(234,246,246,.72);
}

*{box-sizing:border-box;}

html,body{
  margin:0;
  padding:0;
  min-height:100%;
  color:var(--text);
  font-family:'Inter', system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
  overflow-x:hidden;
}

body{
  min-height:100vh;
  /* .stage's own height is locked to the artwork's aspect ratio, which on
     a window proportioned taller than 16:9 leaves a sliver of body below
     it — this is the same dark tone as the art's own background, so that
     sliver reads as more canvas, not a broken/blank gap. */
  background:#12181b;
}

/* Locks to the artwork's own 1920x1080 ratio at the current page width, so
   background-size:cover on .bg-layer inside it never needs to crop the
   sides — only ever the bottom (top-anchored), same as it was drawn. This
   matters because the nav buttons below are pixel-positioned with vw-based
   math that assumes that exact width-bound scale; without .stage locking
   the box to the image's own ratio, a browser window that happens to be
   taller relative to its width than 16:9 (plenty of ordinary laptop sizes:
   1440x900, 1366x768...) makes "cover" scale by height instead and crop
   the sides, and the nav math — still assuming a width-based scale — drifts
   out of alignment with the actual drawing underneath it. Locking the
   ratio here means cover is always width-bound, on any window shape.

   It's also what makes the page properly scrollable rather than clipped:
   .stage is normal in-flow content now (not position:fixed to the
   viewport), so on a short, wide window where its aspect-ratio height
   exceeds the window's, the page simply becomes taller than the viewport
   and scrolls, instead of hiding "about"/"events" off-screen with no way
   to reach them. */
.stage{
  position:relative;
  width:100%;
  aspect-ratio: var(--canvas) / 1080;
}

/* ---------- background art ---------- */
.bg-layer{
  position:absolute;
  inset:0;
  background:#12181b url("assets/bg.jpg") top center / cover no-repeat;
  z-index:0;
}

/* ---------- nav, pinned to the exact spot they were drawn ----------
   The background uses background-size:cover + background-position:top,
   which on any screen wider than it is tall (the common case) scales the
   1920x1920 art by (viewport width / 1920) and crops only from the
   bottom. Positioning these buttons with the SAME basis — vw divided by
   1920, for both axes — keeps them locked to the artwork at any window
   size, because the button and the art are scaling by the same factor. */
:root{
  --canvas: 1920;
}

/* Everything here is position:absolute (relative to <body>, see above), not
   position:fixed. It used to be fixed, with the background pinned via
   background-attachment:fixed, which is a neat trick for keeping a button
   perfectly aligned to the art — but "fixed" also means glued to the
   viewport no matter what, so on a short/wide window where the artwork
   (at cover scale) is taller than the window, the bottom of it — including
   "about" and "events" — was rendered off-screen with no way to scroll
   down and reach it. Absolute positioning keeps the exact same pixel-lock
   math (vw-based, so numerically identical either way) but lets the whole
   page grow taller than the viewport and scroll normally when it needs to
   (see the min-height rule on .hero below), instead of silently clipping. */
.art-btn{
  position:absolute;
  z-index:20;
  display:block;
  border:none;
  padding:0;
  margin:0;
  cursor:pointer;
  background:none;
}

/* The glow itself: an identical "window" onto the same image, blurred and
   brightened. Earlier attempts blended this against the real page backdrop
   with mix-blend-mode:screen — but .art-btn was its own stacking context,
   so the blend only ever "saw" its own empty local backdrop instead of the
   true drawing beneath it. In practice that meant the filtered rectangle
   was just painted directly: over the drawing's dark-charcoal (not pure
   black) fill, that showed up as a visible gray (or, once crushed darker to
   compensate, black) box/blob behind the text — an artifact, not a blend.

   The fix: don't rely on blending at all. Mask this layer with an alpha
   copy of the exact same artwork, sized/positioned as a sprite crop onto
   this button's own box (the standard "big sprite sheet, small crop"
   formula: mask-size scales the full image so this button's slice lands at
   100% of its box; mask-position shifts that slice into place). That mask
   source, assets/glow-mask.png, is generated from bg.jpg with its actual
   alpha channel set from luminance — but with a real cutoff, not a plain
   1:1 mapping: the flat charcoal fill (and its faint JPEG noise) is pushed
   all the way to alpha 0, so it's genuinely gone, not just very faint.
   That distinction matters at high brightness — plain luminance-as-alpha
   (mask-mode:luminance straight off bg.jpg) still gives the "empty"
   charcoal a few-percent alpha, which is invisible at a gentle boost but
   turns back into a visible gray box once the glow is cranked up. With a
   true zero floor, the background can never show through, box or no box,
   no matter how strong the glow gets — only the bright glyph strokes (and
   their own already-drawn glow) keep any opacity, so hovering just makes
   the lettering itself bloom brighter with a soft blurred halo. */
.art-btn::after{
  content:"";
  position:absolute;
  inset:0;
  background-image:url("assets/glow-mask.png");
  background-repeat:no-repeat;
  filter:blur(3.5px) brightness(2.5) saturate(1.6);
  mix-blend-mode:screen;
  -webkit-mask-image:url("assets/glow-mask.png");
  -webkit-mask-repeat:no-repeat;
  mask-image:url("assets/glow-mask.png");
  mask-repeat:no-repeat;
  opacity:0;
  transition:opacity .35s ease;
  pointer-events:none;
}

.art-btn:hover::after,
.art-btn:focus-visible::after{
  opacity:1;
}

/* origin (left, top) and width/height, straight from the cropped 1920x1080 layout */
.logo-btn{
  left:calc(1075 / var(--canvas) * 100vw);
  top: calc(44   / var(--canvas) * 100vw);
  width:calc(700 / var(--canvas) * 100vw);
  height:calc(204 / var(--canvas) * 100vw);
}
.logo-btn::after{
  /* sprite formula: size = fullImage / thisCrop (how much the whole 1920x1080
     image has to be blown up so this crop fills the box at 1:1), position =
     cropOrigin / (fullImage - thisCrop) -- that's the CSS percentage-position
     rule (percentage is measured against the *slack* between the box and
     the oversized image, not against the crop itself), not the more
     intuitive-looking -origin/crop that an earlier pass used, which put the
     image wildly off-frame and made the mask resolve to nothing (a hover
     that visibly did nothing). */
  -webkit-mask-size:calc(1920 / 700 * 100%) calc(1080 / 204 * 100%);
  mask-size:calc(1920 / 700 * 100%) calc(1080 / 204 * 100%);
  -webkit-mask-position:calc(1075 / (1920 - 700) * 100%) calc(44 / (1080 - 204) * 100%);
  mask-position:calc(1075 / (1920 - 700) * 100%) calc(44 / (1080 - 204) * 100%);
  background-size:calc(1920 / 700 * 100%) calc(1080 / 204 * 100%);
  background-position:calc(1075 / (1920 - 700) * 100%) calc(44 / (1080 - 204) * 100%);
}

/* nudged up and in a touch smaller than where they were originally drawn */
.about-btn{
  left:calc(859 / var(--canvas) * 100vw);
  top: calc(873 / var(--canvas) * 100vw);
  width:calc(263 / var(--canvas) * 100vw);
  height:calc(154 / var(--canvas) * 100vw);
}
.about-btn::after{
  -webkit-mask-size:calc(1920 / 263 * 100%) calc(1080 / 154 * 100%);
  mask-size:calc(1920 / 263 * 100%) calc(1080 / 154 * 100%);
  -webkit-mask-position:calc(859 / (1920 - 263) * 100%) calc(873 / (1080 - 154) * 100%);
  mask-position:calc(859 / (1920 - 263) * 100%) calc(873 / (1080 - 154) * 100%);
  background-size:calc(1920 / 263 * 100%) calc(1080 / 154 * 100%);
  background-position:calc(859 / (1920 - 263) * 100%) calc(873 / (1080 - 154) * 100%);
}

.events-btn{
  left:calc(1397 / var(--canvas) * 100vw);
  top: calc(846  / var(--canvas) * 100vw);
  width:calc(396 / var(--canvas) * 100vw);
  height:calc(178 / var(--canvas) * 100vw);
}
.events-btn::after{
  -webkit-mask-size:calc(1920 / 396 * 100%) calc(1080 / 178 * 100%);
  mask-size:calc(1920 / 396 * 100%) calc(1080 / 178 * 100%);
  -webkit-mask-position:calc(1397 / (1920 - 396) * 100%) calc(846 / (1080 - 178) * 100%);
  mask-position:calc(1397 / (1920 - 396) * 100%) calc(846 / (1080 - 178) * 100%);
  background-size:calc(1920 / 396 * 100%) calc(1080 / 178 * 100%);
  background-position:calc(1397 / (1920 - 396) * 100%) calc(846 / (1080 - 178) * 100%);
}

/* ---------- mobile stage ----------
   Its own hand-drawn layout, not a squeezed-in copy of the desktop one:
   logo/about/events stacked in the open sky, skyline lower down. Same
   pixel-lock "window" technique as .stage above (see the long comment on
   .art-btn::after for why it's built this way), just against its own
   1080x2340 composite. Hidden on desktop; swapped in for .stage under the
   breakpoint below. */
.m-stage{ display:none; }

.m-bg{
  position:absolute;
  inset:0;
  background:#0d1114 url("assets/mobile-bg.jpg") top center / cover no-repeat;
  z-index:0;
}

.m-btn{
  position:absolute;
  z-index:20;
  display:block;
  border:none;
  padding:0;
  margin:0;
  cursor:pointer;
  background:none;
}

.m-btn::after{
  content:"";
  position:absolute;
  inset:0;
  background-image:url("assets/mobile-glow-mask.png");
  background-repeat:no-repeat;
  filter:blur(3px) brightness(2.5) saturate(1.6);
  mix-blend-mode:screen;
  -webkit-mask-image:url("assets/mobile-glow-mask.png");
  -webkit-mask-repeat:no-repeat;
  mask-image:url("assets/mobile-glow-mask.png");
  mask-repeat:no-repeat;
  opacity:0;
  transition:opacity .35s ease;
  pointer-events:none;
}

.m-btn:hover::after,
.m-btn:focus-visible::after,
.m-btn:active::after{
  opacity:1;
}

/* left/top/width/height as % of the 1080x2340 mobile composite (a few px
   of padding baked into each box beyond the bare glyph, for an easier tap
   target); mask/background size+position use the same sprite formula as
   the desktop buttons above. */
.m-logo-btn{
  left:calc(118 / 1080 * 100%);
  top: calc(372 / 2340 * 100%);
  width:calc(841 / 1080 * 100%);
  height:calc(219 / 2340 * 100%);
}
.m-logo-btn::after{
  -webkit-mask-size:calc(1080 / 841 * 100%) calc(2340 / 219 * 100%);
  mask-size:calc(1080 / 841 * 100%) calc(2340 / 219 * 100%);
  -webkit-mask-position:calc(118 / (1080 - 841) * 100%) calc(372 / (2340 - 219) * 100%);
  mask-position:calc(118 / (1080 - 841) * 100%) calc(372 / (2340 - 219) * 100%);
  background-size:calc(1080 / 841 * 100%) calc(2340 / 219 * 100%);
  background-position:calc(118 / (1080 - 841) * 100%) calc(372 / (2340 - 219) * 100%);
}

.m-about-btn{
  left:calc(309 / 1080 * 100%);
  top: calc(659 / 2340 * 100%);
  width:calc(383 / 1080 * 100%);
  height:calc(213 / 2340 * 100%);
}
.m-about-btn::after{
  -webkit-mask-size:calc(1080 / 383 * 100%) calc(2340 / 213 * 100%);
  mask-size:calc(1080 / 383 * 100%) calc(2340 / 213 * 100%);
  -webkit-mask-position:calc(309 / (1080 - 383) * 100%) calc(659 / (2340 - 213) * 100%);
  mask-position:calc(309 / (1080 - 383) * 100%) calc(659 / (2340 - 213) * 100%);
  background-size:calc(1080 / 383 * 100%) calc(2340 / 213 * 100%);
  background-position:calc(309 / (1080 - 383) * 100%) calc(659 / (2340 - 213) * 100%);
}

.m-events-btn{
  left:calc(283 / 1080 * 100%);
  top: calc(921 / 2340 * 100%);
  width:calc(570 / 1080 * 100%);
  height:calc(235 / 2340 * 100%);
}
.m-events-btn::after{
  -webkit-mask-size:calc(1080 / 570 * 100%) calc(2340 / 235 * 100%);
  mask-size:calc(1080 / 570 * 100%) calc(2340 / 235 * 100%);
  -webkit-mask-position:calc(283 / (1080 - 570) * 100%) calc(921 / (2340 - 235) * 100%);
  mask-position:calc(283 / (1080 - 570) * 100%) calc(921 / (2340 - 235) * 100%);
  background-size:calc(1080 / 570 * 100%) calc(2340 / 235 * 100%);
  background-position:calc(283 / (1080 - 570) * 100%) calc(921 / (2340 - 235) * 100%);
}

/* ---------- small / portrait screens ----------
   Below this, the desktop drawing's own vertical space runs out before
   the viewport does (cover starts cropping the *sides* instead of the
   bottom), so its pixel-locking breaks down — swap to the mobile stage,
   which is drawn for exactly this shape of screen instead. */
@media (max-width:700px), (max-aspect-ratio: 4/5){
  .stage{ display:none; }

  .m-stage{
    display:block;
    position:relative;
    width:100%;
    aspect-ratio: 1080 / 2340;
  }
}

/* ---------- modal ---------- */
.modal-overlay{
  position:fixed;
  inset:0;
  z-index:100;
  display:flex;
  align-items:center;
  justify-content:center;
  padding:2rem;
  background:rgba(6,10,12,.38);
  opacity:0;
  visibility:hidden;
  transition:opacity .35s ease, visibility 0s linear .35s;
}

.modal-overlay.open{
  opacity:1;
  visibility:visible;
  transition:opacity .35s ease;
}

.modal-card{
  position:relative;
  width:min(640px, 92vw);
  max-height:82vh;
  overflow-y:auto;
  border-radius:32px;
  padding:clamp(2rem,5vw,3.25rem);
  background:var(--card-bg);
  backdrop-filter: blur(26px) saturate(150%);
  -webkit-backdrop-filter: blur(26px) saturate(150%);
  border:1px solid rgba(148,231,224,.28);
  box-shadow:
    0 30px 90px rgba(0,0,0,.55),
    0 0 60px rgba(127,231,224,.12);
  transform:scale(.92) translateY(12px);
  opacity:0;
  transition:transform .4s cubic-bezier(.2,.9,.25,1), opacity .35s ease;
}

.modal-overlay.open .modal-card{
  transform:scale(1) translateY(0);
  opacity:1;
}

.modal-close{
  position:absolute;
  top:1.1rem;
  right:1.35rem;
  background:none;
  border:none;
  color:var(--cyan);
  font-size:1.4rem;
  cursor:pointer;
  padding:.35rem;
  line-height:1;
  filter:drop-shadow(0 0 6px var(--cyan-soft));
  transition:filter .25s ease, transform .25s ease;
}

.modal-close:hover,
.modal-close:focus-visible{
  filter:drop-shadow(0 0 14px var(--cyan));
  transform:scale(1.12) rotate(90deg);
}

/* ---------- modal content typography ---------- */
.modal-content h2{
  font-family:'Caveat', cursive;
  font-size:clamp(2.4rem,6vw,3.4rem);
  color:var(--cyan);
  margin:0 0 1rem;
  text-shadow:0 0 18px rgba(127,231,224,.45);
}

.modal-content .lede{
  font-family:'Caveat', cursive;
  font-size:clamp(1.3rem,3vw,1.7rem);
  color:var(--text);
  margin:0 0 1.25rem;
  line-height:1.35;
}

.modal-content p{
  font-size:.98rem;
  line-height:1.65;
  color:var(--text-dim);
  margin:0 0 1.1rem;
}

.modal-content .cta{
  color:var(--text);
}

.modal-content .cta a{
  color:var(--cyan);
  text-decoration:none;
  border-bottom:1px solid rgba(127,231,224,.5);
  transition:color .2s ease, border-color .2s ease;
}

.modal-content .cta a:hover{
  color:#fff;
  border-color:#fff;
}

.event-block{
  margin-bottom:1.75rem;
}

.event-block:last-child{
  margin-bottom:0;
}

.event-block h3{
  font-family:'Caveat', cursive;
  font-size:clamp(1.5rem,3.4vw,1.9rem);
  color:var(--cyan);
  margin:0 0 .5rem;
  text-shadow:0 0 12px rgba(127,231,224,.35);
}

.special-list{
  list-style:none;
  margin:0;
  padding:0;
  display:flex;
  flex-direction:column;
  gap:.9rem;
}

.special-list li{
  display:flex;
  flex-wrap:wrap;
  align-items:baseline;
  gap:.4rem .6rem;
  padding:.85rem 1rem;
  border-radius:14px;
  background:rgba(127,231,224,.06);
  border:1px solid rgba(127,231,224,.16);
}

.event-name{
  font-weight:600;
  color:var(--text);
}

.event-desc{
  color:var(--text-dim);
  font-size:.92rem;
  flex:1 1 auto;
}

.tba{
  font-size:.7rem;
  letter-spacing:.06em;
  color:var(--ink);
  background:var(--cyan);
  padding:.15rem .5rem;
  border-radius:999px;
  font-weight:600;
}

/* ---------- responsive ---------- */
@media (max-width:560px){
  .modal-card{
    border-radius:24px;
  }
}
