/* ==========================================================================
   ANIME NIGHT AGARTALA — standalone landing page
   Exported from the Tripura Cosplay Guild site system. Carries only the
   tokens and components this one page uses, with the event's crimson-and-
   gold theme folded straight into :root (no override layer to load).

   Order, as in the parent system: TOKENS -> BASE -> COMPONENTS.
   ========================================================================== */

:root {
  /* --- Ground ------------------------------------------------------------
     Near-black plus a deep crimson. Crimson is the dominant ground; the
     near-black sections exist to give it something to push against.
     The raised/card/line steps carry a warm cast so they sit under
     crimson instead of fighting it.                                      */
  --pitch:        #0d0c0f;
  --pitch-raised: #17100f;
  --pitch-card:   #1e1413;
  --pitch-line:   #33211f;
  --pitch-hint:   #4d2f2c;

  --crimson:      #8e1220;   /* dominant ground                          */
  --crimson-deep: #6b0d18;   /* cards and recesses on crimson            */
  --crimson-lift: #a81628;   /* hairlines and hovers on crimson          */

  /* --- Accents -----------------------------------------------------------
     Two voices only: crimson carries the ground, gold carries every price
     and every ticket button. Nothing else gets to be loud.              */
  --gold:  #e8b53c;
  --bone:  #f2ece0;

  --gold-dim: #e8b53c1f;   /* tinted fill behind gold-outlined tags */

  /* --- Text ---------------------------------------------------------------
     Cream body copy. --text-mute is tuned for the near-black sections and
     lifted inside .section--crimson, where the ground is lighter.        */
  --text:       var(--bone);
  --text-mute:  #b8aca2;   /* 8.6:1 on --pitch  */
  --text-faint: #9a8d84;   /* 5.9:1 on --pitch  */
  --text-on-accent: var(--pitch);

  /* --- Type ---------------------------------------------------------------
     Display carries the poster attitude. Body stays out of the way.
     Mono is for dates, prices and labels — anything that reads as data.  */
  --font-display: "Bricolage Grotesque", "Archivo Black", Impact, sans-serif;
  --font-body:    "Inter Tight", "Helvetica Neue", Arial, sans-serif;
  --font-mono:    "Space Mono", ui-monospace, "SF Mono", Menlo, monospace;

  --t-mono:  0.6875rem;
  --t-xs:    0.8125rem;
  --t-sm:    0.9375rem;
  --t-base:  clamp(1rem, 0.97rem + 0.15vw, 1.0625rem);
  --t-lg:    clamp(1.125rem, 1.06rem + 0.3vw, 1.3125rem);
  --t-xl:    clamp(1.5rem, 1.3rem + 0.9vw, 2.125rem);
  --t-2xl:   clamp(2rem, 1.6rem + 1.9vw, 3.25rem);
  --t-3xl:   clamp(2.75rem, 1.9rem + 4vw, 5.5rem);
  --t-4xl:   clamp(3.5rem, 1.8rem + 8vw, 9rem);      /* hero only */

  --lh-tight: 0.92;
  --lh-snug:  1.08;
  --lh-body:  1.6;
  --track-display: -0.03em;
  --track-mono:     0.14em;

  /* --- Space — 4px base --------------------------------------------------- */
  --s-1:  0.25rem;
  --s-2:  0.5rem;
  --s-3:  0.75rem;
  --s-4:  1rem;
  --s-5:  1.5rem;
  --s-6:  2rem;
  --s-7:  3rem;
  --s-8:  4rem;
  --s-9:  6rem;
  --section-y: clamp(4rem, 9vw, 8rem);

  /* --- Grid — 12 col @ 1440 · 8 col @ 768 · 4 col @ 428 ------------------- */
  --page-max: 1440px;
  --gutter:   clamp(1rem, 2.2vw, 1.75rem);
  --margin:   clamp(1.25rem, 5vw, 4rem);
  --cols: 12;

  /* --- Edges ---------------------------------------------------------------
     Hard borders and offset shadows, never blur. A print look —
     screen-printed posters, not glass.                                   */
  --r-sm: 4px;
  --r-md: 10px;
  --r-lg: 18px;
  --r-pill: 999px;
  --border: 2px solid var(--pitch-line);

  /* --- Motion ---------------------------------------------------------------
     Scroll-triggered only. Nothing on this page animates on its own, and
     everything here is transform/opacity so it stays on the compositor. */
  --ease: cubic-bezier(0.2, 0.8, 0.2, 1);
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);   /* arrives and stops, no overshoot */
  --dur-fast: 140ms;
  --dur:      260ms;
  --dur-slow: 620ms;

  --dur-rise: 460ms;   /* entry: inside the 400–600ms band */
  --stagger:   60ms;   /* per-item offset for a row of cards */
  --rise:      20px;   /* how far an element travels on entry */

  /* --- Motif ------------------------------------------------------------- */
  --tone-size: 7px;
  --tone-dot:  1.6px;

  --stickybar-h: 68px;

  --z-grain: 900;
  --z-nav:   800;
  --z-bar:   850;
  --z-lightbox: 1000;
}

@media (max-width: 900px) { :root { --cols: 8; } }
@media (max-width: 560px) { :root { --cols: 4; } }


/* ==========================================================================
   BASE
   ========================================================================== */

*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }

body {
  margin: 0;
  background: var(--pitch);
  color: var(--text);
  font-family: var(--font-body);
  font-size: var(--t-base);
  line-height: var(--lh-body);
  font-synthesis-weight: none;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

img, svg { display: block; max-width: 100%; height: auto; }
button { font: inherit; color: inherit; }

::selection { background: var(--gold); color: var(--pitch); }

:focus-visible {
  outline: 3px solid var(--gold);
  outline-offset: 3px;
  border-radius: var(--r-sm);
}

/* --- Headings ------------------------------------------------------------ */

h1, h2, h3 {
  font-family: var(--font-display);
  font-weight: 800;
  line-height: var(--lh-tight);
  letter-spacing: var(--track-display);
  margin: 0;
  text-wrap: balance;
}

h1 { font-size: var(--t-4xl); }
h2 { font-size: var(--t-3xl); }
h3 { font-size: var(--t-xl); line-height: var(--lh-snug); }

p { margin: 0 0 var(--s-4); max-width: 62ch; text-wrap: pretty; }
p:last-child { margin-bottom: 0; }

a { color: var(--gold); text-decoration-thickness: 2px; text-underline-offset: 3px; }

/* --- Layout primitives --------------------------------------------------- */

.page {
  width: 100%;
  max-width: var(--page-max);
  margin-inline: auto;
  padding-inline: var(--margin);
}

.grid {
  display: grid;
  grid-template-columns: repeat(var(--cols), minmax(0, 1fr));
  gap: var(--gutter);
}

/* overflow: clip contains the .speedlines bleed without making a scroll
   container. No section border rule here — on this page the alternating
   crimson and near-black grounds separate the sections themselves.      */
.section { padding-block: var(--section-y); position: relative; overflow: clip; }

.stack > * + * { margin-top: var(--flow, var(--s-4)); }

/* --- Utility type -------------------------------------------------------- */

.eyebrow {
  font-family: var(--font-mono);
  font-size: var(--t-mono);
  font-weight: 700;
  letter-spacing: var(--track-mono);
  text-transform: uppercase;
  color: var(--text-faint);
  display: flex;
  align-items: center;
  gap: var(--s-3);
}
.eyebrow::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--pitch-line);
}

.mono { font-family: var(--font-mono); font-size: var(--t-xs); letter-spacing: 0.04em; }
.lede { font-size: var(--t-lg); color: var(--text-mute); max-width: 48ch; }
.mute { color: var(--text-mute); }

.ink-gold    { color: var(--gold); }

/* --- Texture -------------------------------------------------------------
   A single fixed grain layer over the whole page. Screen-print paper feel;
   without it the flat grounds read as "unstyled dark mode".             */

.grain {
  position: fixed;
  inset: 0;
  z-index: var(--z-grain);
  pointer-events: none;
  opacity: 0.045;
  mix-blend-mode: screen;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* Screentone — two halftone layers at different pitches, the coarser one
   offset by half a step so it never resolves into an obvious lattice.   */

.screentone {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background-image:
    radial-gradient(var(--tone-dot) at 50% 50%, color-mix(in srgb, var(--tone, var(--bone)) 46%, transparent) 99%, transparent 100%),
    radial-gradient(var(--tone-dot) at 50% 50%, color-mix(in srgb, var(--tone, var(--bone)) 22%, transparent) 99%, transparent 100%);
  background-size:
    var(--tone-size) var(--tone-size),
    calc(var(--tone-size) * 2) calc(var(--tone-size) * 2);
  background-position: 0 0, var(--tone-size) var(--tone-size);
  --tone-mask: radial-gradient(
    120% 90% at var(--tone-x, 84%) var(--tone-y, 24%),
    #000 0%, rgba(0,0,0,0.35) 42%, transparent 72%);
  -webkit-mask-image: var(--tone-mask);
  mask-image: var(--tone-mask);
  opacity: var(--tone-opacity, 0.5);
}

/* Speed lines — the burst behind the hero. One per page, no more. */

.speedlines {
  position: absolute;
  inset: -20%;
  pointer-events: none;
  background: repeating-conic-gradient(
    from 0deg at var(--burst-x, 50%) var(--burst-y, 50%),
    color-mix(in srgb, var(--burst, var(--bone)) 13%, transparent) 0deg 0.7deg,
    transparent 0.7deg 3.4deg);
  --burst-mask: radial-gradient(
    circle at var(--burst-x, 50%) var(--burst-y, 50%),
    transparent 0%, transparent 22%, #000 58%, transparent 82%);
  -webkit-mask-image: var(--burst-mask);
  mask-image: var(--burst-mask);
  opacity: var(--burst-opacity, 0.55);
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
  /* script.js already withholds .js-motion here, so nothing is hidden in
     the first place. This is the backstop: if that ever fails, no element
     can be left stranded at opacity 0. */
  [data-rise] { opacity: 1 !important; transform: none !important; }
}

/* --- Screen-reader-only -------------------------------------------------- */

.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* --- Skip link ----------------------------------------------------------- */

.skip {
  position: absolute;
  left: var(--s-4);
  top: -100px;
  z-index: calc(var(--z-nav) + 1);
  padding: var(--s-3) var(--s-4);
  background: var(--gold);
  color: var(--pitch);
  font-weight: 700;
  border-radius: var(--r-sm);
  text-decoration: none;
  transition: top var(--dur-fast) var(--ease);
}
.skip:focus { top: var(--s-4); }

/* --- Scroll reveal -------------------------------------------------------
   The hiding styles live behind .js-motion, added by script.js only when
   motion is welcome. Without it nothing is ever hidden, so a failed
   observer, a no-JS visitor or a reduced-motion preference can't leave the
   page blank.

   [data-reveal] marks a group the observer watches; [data-rise] marks the
   individual things inside it that move. --i is the stagger index, set by
   script.js so the stagger numbers stay out of the markup.

   The delay sits on the arrival state only, so it applies to entry and
   never holds anything up on the way out.                              */

.js-motion [data-rise] {
  opacity: 0;
  transform: translateY(var(--rise));
  transition: opacity var(--dur-rise) var(--ease-out),
              transform var(--dur-rise) var(--ease-out);
}
.js-motion [data-reveal].is-in [data-rise] {
  opacity: 1;
  transform: none;
  transition-delay: calc(var(--i, 0) * var(--stagger));
}


/* ==========================================================================
   GROUND SECTIONS
   Crimson is the page's dominant ground; near-black sections break it up.
   Components that sit on crimson need their own recessed fill and a
   lifted mute, because the ground under them is lighter.
   ========================================================================== */

.section--crimson { background: var(--crimson); }

.section--crimson .eyebrow          { color: color-mix(in srgb, var(--bone) 74%, transparent); }
.section--crimson .eyebrow::after   { background: var(--crimson-lift); }
.section--crimson .lede,
.section--crimson .mute             { color: #ddd2c8; }          /* 6.3:1 on crimson */
.section--crimson .ticket           { background: var(--crimson-deep); border-color: var(--crimson-lift); }

/* .btn--ghost's default hairline is a near-black step — invisible on
   crimson, so it borrows the cream instead. */
.section--crimson .btn--ghost {
  color: var(--bone);
  border-color: color-mix(in srgb, var(--bone) 42%, transparent);
}
.section--crimson .btn--ghost:hover { border-color: var(--bone); }


/* ==========================================================================
   COMPONENTS
   ========================================================================== */

/* --- Header --------------------------------------------------------------
   A landing page, so there is no nav: the logo and one ticket button.   */

.nav {
  position: sticky;
  top: 0;
  z-index: var(--z-nav);
  display: flex;
  align-items: center;
  gap: var(--s-5);
  padding: var(--s-4) var(--margin);
  background: color-mix(in srgb, var(--pitch) 86%, transparent);
  backdrop-filter: blur(14px);
  border-bottom: 1px solid var(--pitch-line);
}

.nav > .btn { margin-left: auto; }

/* The supplied lockup is black and red on an opaque white ground, so it
   can't float on a dark or crimson page. It sits on a deliberate white
   plaque, which keeps the brand colours exact and reads as a sticker. */

.nav__logo {
  display: inline-flex;
  align-items: center;
  padding: 6px 10px;
  background: #fff;
  border-radius: var(--r-sm);
  flex: none;
}
.nav__logo img { height: 26px; width: auto; }

/* --- Buttons -------------------------------------------------------------
   Hard offset shadow, no blur. Pressing collapses the offset — the button
   moves onto the page rather than fading.                               */

.btn {
  --btn-bg: var(--gold);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-2);
  padding: var(--s-3) var(--s-5);
  background: var(--btn-bg);
  color: var(--text-on-accent);
  border: 2px solid var(--pitch);
  border-radius: var(--r-pill);
  font-weight: 700;
  font-size: var(--t-sm);
  letter-spacing: -0.01em;
  text-decoration: none;
  text-align: center;
  cursor: pointer;
  box-shadow: 0 0 0 2px var(--btn-bg);
  transition: transform var(--dur-fast) var(--ease),
              box-shadow var(--dur-fast) var(--ease);
}

.btn:hover  { transform: translate(-2px, -2px); box-shadow: 0 0 0 2px var(--btn-bg), 4px 4px 0 var(--pitch); }
.btn:active { transform: translate(0, 0);       box-shadow: 0 0 0 2px var(--btn-bg); }

/* Crimson button, for the one case where it sits on a near-black ground.
   Cream label, because --text-on-accent is the dark ink for gold.      */
.btn--crimson { --btn-bg: var(--crimson); color: var(--bone); border-color: var(--crimson-lift); }

.btn--ghost {
  background: transparent;
  color: var(--bone);
  border-color: var(--pitch-hint);
  box-shadow: none;
}
.btn--ghost:hover { border-color: var(--bone); box-shadow: none; transform: translate(-2px, -2px); }

/* Gold gleam that crosses the button on hover/focus. Borrowed from the
   shiny-button idea, but triggered by the pointer rather than looping on
   its own. Pure CSS, and overflow:hidden clips it to the pill radius. */
.btn--sweep { position: relative; overflow: hidden; }
.btn--sweep::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(105deg,
    transparent 38%,
    color-mix(in srgb, #fff 55%, transparent) 50%,
    transparent 62%);
  transform: translateX(-125%);
  transition: transform 720ms var(--ease-out);
}
.btn--sweep:hover::after,
.btn--sweep:focus-visible::after { transform: translateX(125%); }

/* --- Ticket cards -------------------------------------------------------
   The page's whole job. Gold hairline, gold price, and the CTA pushed to
   the bottom with margin-top:auto so both cards' buttons line up even
   though one card's copy is far longer than the other's.               */

.ticket {
  --accent: var(--gold);
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--s-4);
  padding: clamp(1.5rem, 3.5vw, 2.5rem);
  background: var(--pitch-card);
  border: 2px solid var(--pitch-line);
  border-radius: var(--r-lg);
  overflow: hidden;
}
/* The lift the pruned .card:hover used to provide.

   .ticket also carries [data-rise], and `.js-motion [data-rise]` is more
   specific than `.ticket`, so it wins the `transition` shorthand outright.
   Two consequences, both handled:
     - transform is in the reveal's list already, so the lift still eases;
       this base rule is only the fallback for when .js-motion is absent.
     - border-color is NOT, and would have snapped. The gold edge is drawn
       on ::after instead, which has a transition of its own that nothing
       competes for. */
.ticket { transition: transform var(--dur) var(--ease-out); }
.ticket:hover { transform: translateY(-4px); }

.ticket::after {
  content: "";
  position: absolute;
  inset: 0;
  border: 2px solid var(--gold);
  border-radius: inherit;
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--dur) var(--ease-out);
}
.ticket:hover::after { opacity: 1; }

.ticket::before {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: 4px;
  background: var(--accent);
}

/* nowrap, so the price stays pinned to the right on both cards. Letting
   this wrap dropped the longer card's price onto its own line and the two
   cards stopped matching. The name wraps internally instead. */
.ticket__head {
  display: flex;
  flex-wrap: nowrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s-4);
}

.ticket__name { font-size: var(--t-2xl); flex: 1; min-width: 0; }
.ticket__price {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: var(--t-2xl);
  line-height: 1;
  letter-spacing: var(--track-display);
  color: var(--gold);
  flex: none;
}
.ticket__when {
  font-family: var(--font-mono);
  font-size: var(--t-mono);
  font-weight: 700;
  letter-spacing: var(--track-mono);
  text-transform: uppercase;
  color: var(--gold);
}
.ticket__body { font-size: var(--t-base); color: var(--text-mute); margin: 0; max-width: 46ch; }
.section--crimson .ticket__body { color: #ddd2c8; }

.ticket__cta { margin-top: auto; padding-top: var(--s-2); }
.ticket__cta .btn { width: 100%; }

/* --- Programme list -----------------------------------------------------
   Two columns per row so the icon and the label share a baseline however
   the label wraps.                                                      */

.programme { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--s-4); }

.programme li {
  display: grid;
  grid-template-columns: auto 1fr;
  align-items: start;
  gap: var(--s-4);
  padding-bottom: var(--s-4);
  border-bottom: 1px solid var(--crimson-lift);
}
.programme li:last-child { border-bottom: 0; padding-bottom: 0; }

.programme svg {
  width: 26px; height: 26px;
  flex: none;
  color: var(--gold);
  margin-top: 2px;
  transition: transform var(--dur) var(--ease-out);
}
.programme li:hover svg { transform: translateX(3px); }

.programme__name {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: var(--t-lg);
  line-height: var(--lh-snug);
  letter-spacing: -0.02em;
  transition: color var(--dur-fast) var(--ease-out);
}
.programme li:hover .programme__name { color: var(--gold); }

@media (min-width: 760px) { .programme { grid-template-columns: 1fr 1fr; column-gap: var(--s-7); } }

/* --- Guest cards ---------------------------------------------------------
   Two guests, so this is its own two-up grid rather than the 12-col one —
   two col-4s in a 12-col row would sit lopsided against the left margin.
   Photos ship uncropped and are framed here, so reframing one is an
   object-position change, never a re-export.                            */

.guests {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--s-8);          /* generous air between the two guests */
  max-width: 1180px;        /* larger cards than the original 880px */
}
@media (max-width: 900px) { .guests { gap: var(--s-7); } }
@media (max-width: 560px) { .guests { grid-template-columns: 1fr; gap: var(--s-7); } }

.guest {
  display: flex;
  flex-direction: column;
  gap: var(--s-4);
}

.guest__figure {
  position: relative;
  margin: 0;
  border: 2px solid var(--pitch-line);
  border-radius: var(--r-lg);
  overflow: hidden;
}
.guest__figure img {
  width: 100%;
  aspect-ratio: 4 / 5;
  object-fit: cover;
  display: block;
  transition: transform var(--dur-slow) var(--ease-out);
}
/* .guest__figure already clips, so the zoom stays inside the rounded frame. */
.guest:hover .guest__figure img { transform: scale(1.03); }

.guest__name { font-size: var(--t-2xl); }
.guest__from {
  font-family: var(--font-mono);
  font-size: var(--t-mono);
  font-weight: 700;
  letter-spacing: var(--track-mono);
  text-transform: uppercase;
  color: var(--gold);
}

/* Instagram handle under each guest. Inherits the .socials pill look but
   sized down so it sits under the name without competing with it. */
.guest__ig {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  padding: var(--s-2) var(--s-4);
  border: 1px solid var(--pitch-hint);
  border-radius: var(--r-pill);
  color: var(--text-mute);
  text-decoration: none;
  font-size: var(--t-xs);
  transition: color var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out),
              transform var(--dur-fast) var(--ease-out);
}
.guest__ig svg { width: 15px; height: 15px; flex: none; }
.guest__ig:hover {
  color: var(--pitch);
  background: var(--bone);
  border-color: var(--bone);
  transform: translateY(-2px);
}

/* The photographer's watermark sits low enough that the 4:5 frame clips
   it, so the credit is carried as text instead of being cropped away. */
.guest__credit {
  font-family: var(--font-mono);
  font-size: var(--t-mono);
  letter-spacing: 0.04em;
  color: var(--text-faint);
}

/* --- Gallery ------------------------------------------------------------
   Photographs from CosFest IXA 2019, TCG's own past event. A 6-cell
   mosaic: the group lineup takes a double-width cell so the community
   scale reads first, the rest fill in around it.

   Photos ship uncropped and are framed here, so reframing one is an
   object-position change rather than a re-export.                     */

.gallery {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  /* Fixed row height + two double-width cells = 8 column-units over 4
     columns, so the six photos fill two rows exactly with no hole. */
  grid-auto-rows: 220px;
  gap: var(--s-3);
}

.shot {
  position: relative;
  display: block;
  width: 100%;
  padding: 0;
  margin: 0;
  border: 2px solid var(--pitch-line);
  border-radius: var(--r-md);
  overflow: hidden;
  background: var(--pitch-card);
  cursor: zoom-in;
  transition: transform var(--dur) var(--ease-out),
              border-color var(--dur) var(--ease-out);
}
.shot img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  /* Faces sit above the middle of a frame far more often than not. */
  object-position: center 30%;
  transition: transform var(--dur-slow) var(--ease-out);
}
.shot:hover { border-color: var(--gold); }
.shot:hover img { transform: scale(1.04); }

/* The lineup and the music shot each take two columns. */
.shot--wide { grid-column: span 2; }

@media (max-width: 900px) {
  .gallery { grid-template-columns: repeat(2, minmax(0, 1fr)); grid-auto-rows: 200px; }
  .shot--wide { grid-column: span 2; }
}
@media (max-width: 560px) {
  .gallery { grid-template-columns: 1fr; grid-auto-rows: 240px; }
  .shot--wide { grid-column: span 1; }
}

.gallery__note {
  font-family: var(--font-mono);
  font-size: var(--t-mono);
  letter-spacing: var(--track-mono);
  text-transform: uppercase;
  color: var(--text-faint);
}

/* --- Lightbox -----------------------------------------------------------
   Ported back from the parent TCG site. Opens on click, closes on Esc,
   backdrop click or the close button; arrows step through the set.    */

.lightbox {
  position: fixed;
  inset: 0;
  z-index: var(--z-lightbox);
  display: grid;
  place-items: center;
  padding: var(--margin);
  background: color-mix(in srgb, var(--pitch) 93%, transparent);
}
.lightbox[hidden] { display: none; }

.lightbox__frame { position: relative; max-width: min(1100px, 100%); }

.lightbox img {
  width: 100%;
  max-height: 82vh;
  object-fit: contain;
  border-radius: var(--r-md);
  border: 2px solid var(--pitch-line);
}

.lightbox__close {
  position: absolute;
  top: calc(var(--s-4) * -1 - 24px);
  right: 0;
  display: inline-grid;
  place-items: center;
  width: 40px; height: 40px;
  background: transparent;
  border: 1px solid var(--pitch-hint);
  border-radius: var(--r-pill);
  color: var(--bone);
  cursor: pointer;
  transition: border-color var(--dur-fast) var(--ease-out);
}
.lightbox__close:hover { border-color: var(--bone); }

.lightbox__caption {
  margin-top: var(--s-3);
  font-family: var(--font-mono);
  font-size: var(--t-xs);
  color: var(--text-mute);
}

/* --- Event description --------------------------------------------------
   Sits between the hero and the tickets: answers "what is this?" at the
   moment the question arises, then hands straight off to the prices.  */

.intro {
  font-size: var(--t-xl);
  line-height: 1.45;
  max-width: 34ch;
  color: var(--text);
  text-wrap: pretty;
}

/* --- CTA panel ----------------------------------------------------------- */

.cta {
  position: relative;
  padding: clamp(2rem, 6vw, 4.5rem);
  border-radius: var(--r-lg);
  background:
    radial-gradient(120% 140% at 12% 0%, color-mix(in srgb, var(--gold) 16%, transparent) 0%, transparent 55%),
    radial-gradient(100% 120% at 100% 100%, color-mix(in srgb, var(--crimson-lift) 34%, transparent) 0%, transparent 55%),
    var(--pitch-raised);
  border: 2px solid var(--pitch-line);
  overflow: hidden;
}

/* --- The mask motif -----------------------------------------------------
   An original kitsune-style mask, drawn inline in the markup so it costs
   no request and recolours from the tokens. Three uses: large and quiet
   behind the hero, small and gold as a section marker, and as the bullet
   on the stallholder panel.                                            */

.mask { color: var(--bone); }
.mask--mark { width: 38px; color: var(--gold); flex: none; }

/* --- Hero figure + tracking eyes ----------------------------------------
   The hero photograph replaced the faint SVG backdrop that used to sit
   here; the inline mask symbol is still used as the stallholder marker.

   Only the pupils move, and only toward the pointer — there is no
   self-running animation. They are two spans layered over the still,
   positioned as percentages of the figure so they stay locked to the
   irises at every screen size. script.js writes --mx/--my.            */

.hero-figure {
  position: relative;
  border: 2px solid var(--crimson-lift);
  border-radius: var(--r-lg);
  overflow: hidden;
  margin: 0;
}
.hero-figure img {
  width: 100%;
  aspect-ratio: 4 / 5;
  object-fit: cover;
  display: block;
}

/* Below 900px the column collapses to full width, and a 4:5 figure then
   stands ~990px tall and buries the ticket buttons. Cap it. */
@media (max-width: 900px) {
  .hero-figure { max-width: 420px; }
}

.eyes__pupil {
  position: absolute;
  left: var(--x);
  top: var(--y);
  /* Sized as a share of the figure so it scales with the image, and wide
     enough to cover the painted iris underneath — any narrower and the
     original eye shows beside the pupil as soon as it moves. */
  width: 3.3%;
  aspect-ratio: 1;
  border-radius: 50%;
  pointer-events: none;
  background: radial-gradient(circle at 50% 50%,
    var(--pitch) 0 56%,
    color-mix(in srgb, var(--glow, var(--bone)) 90%, transparent) 70%,
    color-mix(in srgb, var(--glow, var(--bone)) 30%, transparent) 82%,
    transparent 94%);
  transform: translate(calc(-50% + var(--mx, 0px)), calc(-50% + var(--my, 0px)));
  transition: transform 120ms var(--ease);
  will-change: transform;
}
/* No pointer to follow — leave the pupils centred on the irises. */
@media (hover: none) { .eyes__pupil { transition: none; } }

/* --- Sticky mobile ticket bar -------------------------------------------
   Mobile only. Shown by CSS by default, so with JS absent the bar is
   simply always there rather than missing; script.js then hides it while
   the hero's own buttons are on screen.                                */

.stickybar {
  position: fixed;
  left: 0; right: 0; bottom: 0;
  z-index: var(--z-bar);
  display: none;
  gap: var(--s-3);
  padding: var(--s-3) var(--s-4);
  background: color-mix(in srgb, var(--pitch) 94%, transparent);
  backdrop-filter: blur(14px);
  border-top: 2px solid var(--crimson-lift);
  transition: transform var(--dur) var(--ease);
}

.stickybar .btn { flex: 1; padding-inline: var(--s-3); font-size: var(--t-xs); }

.stickybar--hidden { transform: translateY(110%); }

@media (max-width: 760px) {
  .stickybar { display: flex; }
  /* Keep the fixed bar off the footer's last line. */
  body { padding-bottom: var(--stickybar-h); }
}

/* --- Footer --------------------------------------------------------------
   Two ideas borrowed from the cinematic-footer reference and rebuilt in
   TCG's own language: a giant outlined wordmark behind the content, and a
   soft crimson wash for depth. Both are static.

   Deliberately NOT borrowed: the glass pills and backdrop blur, because
   this system's rule is hard borders and offset shadows, never blur — it
   is a screen-print look, not glass. Also dropped: the marquee, the
   breathing aurora and the heartbeat, all of which move on their own. */

.footer {
  position: relative;
  overflow: clip;
  padding-block: var(--s-8) var(--s-6);
  border-top: 1px solid var(--pitch-line);
}

/* Soft crimson wash — the .cta panel already uses radial washes, so this
   stays inside the established vocabulary. No animation on it. */
.footer::before {
  content: "";
  position: absolute;
  left: 50%; top: 45%;
  width: min(86vw, 1000px);
  aspect-ratio: 2 / 1;
  transform: translate(-50%, -50%);
  background: radial-gradient(50% 50% at 50% 50%,
    color-mix(in srgb, var(--crimson) 34%, transparent) 0%,
    transparent 72%);
  pointer-events: none;
  z-index: 0;
}

/* Giant outlined wordmark. Stroke only, so it reads as a watermark rather
   than competing with the real footer type. */
.footer__ghost {
  position: absolute;
  left: 50%;
  bottom: -2.5vw;
  transform: translateX(-50%);
  margin: 0;
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 30vw;
  line-height: 0.75;
  letter-spacing: -0.05em;
  white-space: nowrap;
  color: transparent;
  -webkit-text-stroke: 1px color-mix(in srgb, var(--bone) 11%, transparent);
  pointer-events: none;
  user-select: none;
  z-index: 0;
}

/* Footer content rides above both decorative layers. */
.footer > .page { position: relative; z-index: 1; }

/* Back to top — the arrow nudges up on hover. */
.totop {
  display: inline-grid;
  place-items: center;
  width: 46px; height: 46px;
  flex: none;
  background: transparent;
  border: 1px solid var(--pitch-hint);
  border-radius: var(--r-pill);
  color: var(--text-mute);
  cursor: pointer;
  transition: color var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out),
              transform var(--dur-fast) var(--ease-out);
}
.totop:hover { color: var(--bone); border-color: var(--bone); transform: translateY(-2px); }
.totop svg { width: 18px; height: 18px; transition: transform var(--dur) var(--ease-out); }
.totop:hover svg { transform: translateY(-3px); }

.footer__logo {
  display: inline-flex;
  padding: 10px 14px;
  background: #fff;
  border-radius: var(--r-md);
}
.footer__logo img { height: 44px; width: auto; }

.footer__big {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: var(--t-3xl);
  line-height: var(--lh-tight);
  letter-spacing: var(--track-display);
}

.socials { display: flex; flex-wrap: wrap; gap: var(--s-3); list-style: none; margin: 0; padding: 0; }
.socials a {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  padding: var(--s-2) var(--s-4);
  border: 1px solid var(--pitch-hint);
  border-radius: var(--r-pill);
  color: var(--text-mute);
  text-decoration: none;
  font-size: var(--t-sm);
  transition: color var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out),
              transform var(--dur-fast) var(--ease-out);
}
.socials a:hover {
  color: var(--pitch);
  background: var(--bone);
  border-color: var(--bone);
  transform: translateY(-2px);
}
.socials svg { width: 16px; height: 16px; flex: none; }

/* --- Span helpers (12-col) ----------------------------------------------- */

.col-4  { grid-column: span 4; }
.col-5  { grid-column: span 5; }
.col-6  { grid-column: span 6; }
.col-7  { grid-column: span 7; }
.col-8  { grid-column: span 8; }
.col-12 { grid-column: span 12; }

@media (max-width: 900px) {
  .col-4, .col-5, .col-6, .col-7, .col-8, .col-12 { grid-column: span 8; }
}
@media (max-width: 560px) {
  .col-4, .col-5, .col-6, .col-7, .col-8, .col-12 { grid-column: span 4; }
}
