/* ============================================================================
   Prayas '26 — Plaksha University
   Theme: AI for good.

   Palette is the Prayas brand palette, sampled from the brand board. Only the
   arch card silhouette is borrowed from reference — none of the colour.

   A uniform palette is about restraint in APPLICATION, not a smaller set of
   colours. House rules, kept to throughout:
     · ink is the only ground; plum is the only raised surface
     · ice is the only body-text colour
     · marigold carries structure — numerals, labels, the arch, CTAs
     · crimson is the brand accent and belongs to Prayas itself
     · mint is reserved for "sealed / technical" states, used sparingly
     · brick appears only as a solid offset behind display type
   Never more than two accents in one component.
   ========================================================================= */

:root {
  /* -- brand palette ------------------------------------------------------ */
  --ink:        #0a0a0e;   /* near-black — the one ground                    */
  --plum:       #433552;   /* violet — the one raised surface                */
  --crimson:    #aa295a;   /* rani pink — brand accent                       */
  --marigold:   #e19d39;   /* amber — structural accent                      */
  --brick:      #842618;   /* deep red — offsets only                        */
  --mint:       #57bba8;   /* cool accent — sealed / technical states        */
  --ice:        #eefeff;   /* near-white — all body text                     */

  /* Derived steps, declared once so no component invents its own. */
  --ink-raised:  #121218;
  --ink-sunk:    #060609;
  --plum-deep:   #2b2138;
  --plum-shade:  #221a2d;
  --marigold-lo: #c4832a;
  --marigold-hi: #f0bd72;
  /* The ring runs hotter than the brand crimson so it holds against the
     dancer's own pink. Still crimson's hue — it is not a second accent. */
  --crimson-hot: #d8367a;
  /* A genuine neon magenta — brighter and cooler than crimson-hot — reserved
     for the two things on the page meant to read as lit signage rather than
     brand colour: the wordmark's glow and the halo behind the dancer. */
  --neon-pink: #ff2e9f;
  /* Mint is a light cool accent — it disappears on paper stock. This is the
     same hue taken dark enough to read there. */
  --mint-deep:   #2c6b5f;

  --ice-70: rgba(238, 254, 255, .70);
  --ice-55: rgba(238, 254, 255, .55);
  --ice-38: rgba(238, 254, 255, .38);
  --ice-22: rgba(238, 254, 255, .22);

  --line: rgba(238, 254, 255, .13);

  /* Card interior. The ornamental plate is printed on warm stock, so the
     card reads as paper laid on the dark ground rather than a dark panel. */
  --paper:       #f9f6e7;
  --paper-shade: #efe8d2;
  --on-paper:    #191428;
  --on-paper-2:  #4a3f5c;

  /* -- type --------------------------------------------------------------- */
  --font-display: "Yatra One", Georgia, serif;
  --font-body:    "Poppins", ui-sans-serif, system-ui, sans-serif;

  --t-h2:    clamp(2.1rem, 6vw, 5rem);
  --t-h4:    clamp(1.05rem, 1.5vw, 1.25rem);
  --t-body:  clamp(.9rem, 1.05vw, 1rem);
  --t-small: clamp(.78rem, .92vw, .86rem);
  --t-label: clamp(.62rem, .78vw, .72rem);

  /* -- rhythm ------------------------------------------------------------- */
  --gap-xs: .5rem;
  --gap-s:  .875rem;
  --gap-m:  1.5rem;
  --gap-l:  2.75rem;
  --gap-xl: 5rem;
  --gap-2xl: clamp(5rem, 12vh, 9rem);

  --shell: min(1180px, 92vw);
  --radius: 4px;

  /* -- scroll-driven values ------------------------------------------------
     The engine writes these once per frame. Defaults are the "everything
     visible" state so content is readable with JS off or still loading. */
  --p: 0;  --rot: 0deg;
  --hero-o: 1;   --hero-y: 0px;
  --e1-o: 1;     --e1-x: 0px;
  --e2-o: 1;     --e2-x: 0px;
  --cue-o: 1;  --glow-o: 0;  --halo-o: 0;
  --ring-o: 1;   --ring-s: 1;
  --lock-o: 0;
  /* How far the dancer rises and shrinks as the closing line arrives.
     Overridden per breakpoint — a short landscape stage needs much more. */
  --lift-vh: -7vh;  --lift-k: .08;
  /* Where the polar buta field radiates from — the engine keeps this on the
     dancer so the field and the ring share one centre. */
  --stage-x: 50%; --stage-y: 46%;
}

/* ============================================================================
   Reset
   ========================================================================= */

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

html {
  background: var(--ink);
  /* The engine does its own smoothing; native smooth scroll would fight it. */
  scroll-behavior: auto;
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  background: var(--ink);
  color: var(--ice);
  font-family: var(--font-body);
  font-size: var(--t-body);
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
}

h1, h2, h3, h4 {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 400;
  line-height: 1.06;
}

p, dl, dd, dt, ol, ul { margin: 0; }
ol, ul { padding: 0; list-style: none; }
img, svg, video { display: block; max-width: 100%; }

a { color: var(--marigold); text-decoration: none; }
a:hover { color: var(--marigold-hi); }

:focus-visible { outline: 2px solid var(--marigold); outline-offset: 3px; border-radius: 2px; }

.defs { position: absolute; width: 0; height: 0; overflow: hidden; }
.dot { color: var(--marigold); padding: 0 .35em; }

.rule {
  display: inline-block;
  width: clamp(24px, 4vw, 46px);
  height: 1px;
  background: linear-gradient(90deg, transparent, var(--marigold));
  vertical-align: middle;
}
.hero__theme .rule:last-child,
.hack__sub .rule:last-child { background: linear-gradient(90deg, var(--marigold), transparent); }

/* ============================================================================
   Backdrop
   ========================================================================= */

.backdrop {
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  overflow: hidden;
  background: var(--ink);
}

/* -- the buta field --------------------------------------------------------
   The emblems are NOT a scattered wallpaper and never were a square lattice.
   Measured off the reference they sit on a POLAR lattice centred on the
   stage: rings ~108px apart, ~150px of arc between neighbours, and every
   other ring offset by half a step so no two rings line up into spokes. Each
   emblem is also tilted toward its own radius, which is what makes the field
   read as radiating from the dancer rather than as tiling behind her.

   That geometry cannot come out of a repeating background image, so the layer
   is filled at boot by the engine (see buildWeave in js/prayas.js) and rebuilt
   on resize. This block only styles what it produces. */
.backdrop__weave {
  position: absolute;
  inset: 0;
  /* Turns with the act, an eighth as fast as everything else — enough to feel
     alive over a 460vh scroll, slow enough never to read as spinning. */
  transform: rotate(calc(var(--rot) * .12));
  transform-origin: var(--stage-x, 50%) var(--stage-y, 46%);
  will-change: transform;
}

.buta {
  position: absolute;
  /* The engine sets --x/--y/--a/--w per emblem; everything else is here so a
     hundred-odd nodes carry one shared rule rather than a style string each. */
  left: var(--x);
  top: var(--y);
  width: var(--w);
  aspect-ratio: 147 / 180;
  transform: translate(-50%, -50%) rotate(var(--a));
  background: url("../assets/bg-motif.webp") center / contain no-repeat;
  opacity: var(--buta-o, .3);
}

/* Reserved for the burst — flat black at rest, exactly like the reference. */
.backdrop__glow {
  position: absolute;
  inset: 0;
  opacity: var(--glow-o);
  background: radial-gradient(58% 46% at 50% 50%, rgba(216, 54, 122, .22), transparent 68%);
}

/* Vertical hairlines, 4px apart. Off the reference: barely a value step above
   the ground, but they stop a full-bleed black from looking like dead space. */
.backdrop__grain {
  position: absolute;
  inset: 0;
  background-image: repeating-linear-gradient(
    90deg,
    rgba(225, 157, 57, .055) 0 1px,
    transparent 1px 4px
  );
}

main { position: relative; z-index: 1; }

/* ============================================================================
   ACT ONE — tall track, sticky stage
   ========================================================================= */

.act {
  position: relative;
  /* Fallback only. The engine replaces this with a pixel height computed from
     CONFIG.actVh + CONFIG.holdVh; retune the pacing there, not here. */
  height: 1180vh;
}

.act__stage {
  position: sticky;
  top: 0;
  height: 100vh;
  height: 100svh;
  overflow: clip;
  display: grid;
  grid-template-rows: auto 1fr;
  align-items: center;
  /* The top pad has to clear the hero's 10px rise, since the stage clips. */
  padding: clamp(2rem, 5vh, 3rem) 0 clamp(2rem, 5vh, 3.5rem);
}

/* -- hero ------------------------------------------------------------------ */

.hero {
  grid-row: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--gap-s);
  text-align: center;
  opacity: var(--hero-o);
  transform: translateY(var(--hero-y));
  will-change: opacity, transform;
}

.hero__eyebrow {
  font-size: var(--t-label);
  font-weight: 500;
  letter-spacing: .34em;
  text-transform: uppercase;
  color: var(--ice-55);
}

.hero__logo {
  width: min(38vw, 360px);
  height: auto;
  /* A neon-sign rim rather than one soft shadow: each drop-shadow follows
     the wordmark's own alpha silhouette, so stacking several at rising blur
     radii builds a tight bright edge with a wider halo falling away from
     it — the layering is what reads as "lit from within" instead of "sitting
     in front of a glow". */
  filter:
    drop-shadow(0 0 2px rgba(255, 46, 159, .9))
    drop-shadow(0 0 10px rgba(255, 46, 159, .7))
    drop-shadow(0 0 24px rgba(255, 46, 159, .5))
    drop-shadow(0 0 46px rgba(255, 46, 159, .32));
  animation: float 7s ease-in-out infinite;
}

.hero__theme {
  display: flex;
  align-items: center;
  gap: var(--gap-s);
  font-family: var(--font-display);
  font-size: clamp(.95rem, 1.5vw, 1.35rem);
  letter-spacing: .14em;
  color: var(--marigold);
}

/* -- dancer ---------------------------------------------------------------- */

.dancer {
  grid-row: 2;
  align-self: stretch;          /* the stage centres its rows; without this the
                                   row collapses to content and the plate spills */
  position: relative;
  min-height: 0;
  display: grid;
  place-items: center;
  padding: clamp(1rem, 3vh, 2rem) 0;

  /* The plate and the ring move as one group. The only thing that drives this
     is the closing beat: she rises and settles back a little so the line at
     the bottom of the stage lands on clear ground instead of across her legs. */
  transform:
    translateY(calc(var(--lock-o) * var(--lift-vh)))
    scale(calc(1 - var(--lock-o) * var(--lift-k)));
  transform-origin: 50% 38%;
  will-change: transform;
}

/* -- the glow behind her -----------------------------------------------------
   A soft pink-neon halo at her back — nothing more elaborate than that. (An
   earlier pass drew this as a full sunburst of rays; it read as gimmicky, so
   this replaced it with a plain glow.) Scoped to .dancer rather than the
   fixed backdrop, so it simply sits behind the ring and the plate in DOM
   order and needs no coordinate math to stay centred on her — it moves and
   settles with her for free. --halo-o is written by the engine — see
   CONFIG.beats.haloIn/haloOut in js/prayas.js — opening only in the window
   around event two, closed again well before the hackathon. */
.dancer__glow {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 58%;
  height: 58%;
  transform: translate(-50%, -50%);
  border-radius: 50%;
  /* Capped well under 1 — at full strength the flat colour read as a solid
     disc behind her rather than a glow, no matter how soft the gradient. */
  opacity: calc(var(--halo-o) * .55);
  will-change: opacity;
  background: radial-gradient(circle at 50% 50%, var(--neon-pink) 0%, rgba(255, 46, 159, .4) 45%, transparent 75%);
  filter: blur(52px);
  mix-blend-mode: screen;
}

/* -- the ring ---------------------------------------------------------------
   A true circle, not an ellipse: two hairlines 9 units apart carrying thirty
   beads that spell PRAYAS 2026 three times around, a pair of dots in every
   gap. Sized off the row HEIGHT rather than the width so it keeps its
   relationship to the figure — she is height-constrained by object-fit, and
   so is this. */
.ring {
  position: absolute;
  top: 2%;
  left: 50%;
  width: auto;
  height: 74%;
  aspect-ratio: 1;
  max-width: 88vw;
  transform: translateX(-50%) rotate(calc(var(--rot) * -.18)) scale(var(--ring-s));
  transform-origin: 50% 50%;
  overflow: visible;
  color: var(--crimson-hot);
  filter: drop-shadow(0 0 5px rgba(216, 54, 122, .55));
  animation: ring-in 1.5s .25s ease-out both;
  will-change: transform;
}

@keyframes ring-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* --ring-o lands on stroke/fill opacity, not opacity: the load animation above
   owns opacity, and an animation always wins over a plain declaration. */
.ring__band {
  fill: none;
  stroke: currentColor;
  stroke-width: 1;
  stroke-opacity: calc(var(--ring-o) * .8);
}

.ring__beads rect {
  fill: var(--ink-sunk);
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-opacity: var(--ring-o);
}

/* The letters are ~7px on screen. They are not meant to be read at a glance —
   at size they are texture, and only reward someone who leans in. */
.ring__beads text {
  font-family: var(--font-body);
  font-size: 9px;
  font-weight: 600;
  letter-spacing: 0;
  text-anchor: middle;
  fill: currentColor;
  fill-opacity: calc(var(--ring-o) * .85);
}

.ring__dots {
  fill: currentColor;
  fill-opacity: calc(var(--ring-o) * .6);
}

/* Absolutely filling the row is the only sizing that cannot overflow it.
   With width:auto the box was sized by max-width and the poster's aspect
   ratio, so the percentage height never constrained anything and she spilled
   over the closing line below. object-fit letterboxes her inside the box
   instead — the actual rendered size still follows the video's own aspect
   ratio, so this is a generous ceiling, not a forced size. */
.dancer__plate {
  position: absolute;
  top: 2%;
  left: 50%;
  width: min(100%, 50vw);
  height: 88%;
  object-fit: contain;
  transform: translateX(-50%);
  filter: url(#dancerKey) drop-shadow(0 0 18px rgba(170, 41, 90, .28));
  /* Her entrance belongs to page load, not to scroll: the reference has her
     on screen at the very top of the act, alongside the scroll cue. Tying it
     to progress instead meant an empty stage until you had already scrolled. */
  animation: plate-in 1.2s .1s cubic-bezier(.2, .7, .25, 1) both;
  will-change: opacity, transform;
}

@keyframes plate-in {
  from { opacity: 0; transform: translateX(-50%) translateY(26px) scale(.95); }
  to   { opacity: 1; transform: translateX(-50%) translateY(0)    scale(1);   }
}

/* -- lockline + cue -------------------------------------------------------- */

/* The closing line of the pinned act. Both cards are gone by 80% and the
   burst has peaked, so this fades up over an otherwise empty stage — the
   dancer standing, one line of type beneath her. It sits low enough to clear
   her feet, and the act holds for another 45vh after it lands so it is not
   scrolled off the instant it arrives. */
.lockline {
  position: absolute;
  left: 0; right: 0;
  bottom: clamp(4rem, 9vh, 7rem);
  z-index: 2;
  max-width: 40ch;
  margin: 0 auto;
  padding: 0 6vw;
  text-align: center;
  text-wrap: pretty;
  font-family: var(--font-display);
  font-size: clamp(1.05rem, 2vw, 1.7rem);
  line-height: 1.5;
  color: var(--ice);
  opacity: var(--lock-o);
  will-change: opacity;
}

.cue {
  position: absolute;
  left: 0; right: 0;
  bottom: clamp(1.2rem, 4vh, 2.5rem);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--gap-xs);
  opacity: var(--cue-o);
  animation: bob 2.4s ease-in-out infinite;
}
.cue__label {
  font-size: var(--t-label);
  font-weight: 500;
  letter-spacing: .3em;
  text-transform: uppercase;
  color: var(--ice-38);
}
.cue__line { width: 1px; height: 34px; background: linear-gradient(var(--marigold), transparent); }

/* ============================================================================
   Event cards — the ornamental plate
   ============================================================================
   The border is an illustrated Pattachitra-style frame, recoloured into the
   Prayas palette, applied with border-image: the four corners stay pristine
   (border-image never stretches them) while the vine tiles along the edges.
   Content sits on warm paper inside, so the card reads as a printed plate.
   ========================================================================= */

.card {
  position: absolute;
  top: 50%;
  width: min(33vw, 440px);
  transform: translateY(-50%) translateX(var(--card-x, 0px));
  opacity: var(--card-o, 1);
  will-change: opacity, transform;

  /* Thickness of the ornament band. The source plate is 139px of border on a
     956px-wide artwork — 14.5% — so keeping the rendered band near that ratio
     is what stops the linework looking stretched. */
  --band: clamp(24px, 3.4vw, 54px);
}
.card--one { left: 4vw;  --card-o: var(--e1-o); --card-x: var(--e1-x); --accent: var(--crimson); }
.card--two { right: 4vw; --card-o: var(--e2-o); --card-x: var(--e2-x); --accent: var(--brick); }

/* -- the ornamental plate ---------------------------------------------------
   Every surface on this page that reads as printed stock shares this paper
   styling: the two event cards and the boxes in the hackathon section. Only
   the BORDER differs between the two groups now — everything else about the
   stock (colour, press texture, shadow depth) is one rule so a plate is
   recognisably a plate wherever it sits.

   Event cards keep the original illustrated Pattachitra border (peacocks,
   elephants, dense multicolour vine) — they sit over the pinned stage and
   read as the page's centrepiece, so they keep the richer plate. Everything
   in the hackathon section takes the lighter marigold frame lifted from the
   Insta banner's side panels — see the rule below. */
.card__frame,
.panel,
.track,
.timeline__card {
  position: relative;
  border-style: solid;
  background-color: var(--paper);
  /* faint press texture so the interior is stock, not flat fill */
  background-image: radial-gradient(rgba(132, 38, 24, .07) 1px, transparent 1.2px);
  background-size: 9px 9px;
  color: var(--on-paper);
  box-shadow: 0 10px 26px rgba(6, 6, 9, .5);
}

/* -- event cards: the original illustrated plate ----------------------------
   Untouched by the banner-frame swap below. The source plate is 139px of
   border on a 956px-wide artwork — 14.5% — so keeping the rendered band near
   that ratio is what stops the linework looking stretched. */
.card__frame {
  border-image-source: url("../assets/card-frame.webp");
  /* source slices in source pixels: top right bottom left */
  border-image-slice: 168 139 148 139;
  border-image-repeat: round;
  border-width: calc(var(--band) * 1.21) var(--band) calc(var(--band) * 1.07) var(--band);
  /* The largest plate on the page, sitting over the pinned stage, so it
     carries a deeper shadow than the boxes further down. */
  box-shadow: 0 20px 44px rgba(6, 6, 9, .62);
}

/* -- hackathon boxes: the banner frame ---------------------------------------
   assets/card-frame.svg — the marigold rule, hairline inset, cusped corner
   finials and teal vine ticks taken from the Insta banner's side panels —
   applied with border-image, so the four corners stay pristine (border-image
   never stretches a corner) while the straight runs tile along the edges.

   --band is the only thing that varies, and each component sets its own: the
   frame is line art rather than a dense illustrated plate, so it holds up far
   thinner than the event cards' border does. */
.panel,
.track,
.timeline__card {
  border-image-source: url("../assets/card-frame.svg");
  /* Slices in the source's own units: the drawing is 400 square with a
     64-unit border, so one number does all four sides — symmetrical by
     construction, which is also why the band is a single flat value. */
  border-image-slice: 64;
  border-image-repeat: round;
  border-width: var(--band);
}

.card__body {
  position: relative;
  z-index: 1;
  padding: 0 clamp(.4rem, 3%, 1.1rem);
  display: flex;
  flex-direction: column;
  gap: clamp(.35rem, 1vh, .65rem);
  text-align: center;
  color: var(--on-paper);
}

.card__index {
  font-size: var(--t-label);
  font-weight: 600;
  letter-spacing: .3em;
  text-transform: uppercase;
  color: var(--brick);
}
.card__index span { color: var(--on-paper-2); font-weight: 400; }

.card__title {
  font-size: clamp(1.45rem, 2.4vw, 2.2rem);
  line-height: 1.1;
  color: var(--on-paper);
  text-wrap: balance;
}
.card__title-num { color: var(--accent); }

.card__kicker {
  font-size: var(--t-small);
  font-weight: 400;
  font-style: italic;
  color: var(--accent);
}

.card__hook {
  font-size: var(--t-small);
  line-height: 1.5;
  color: var(--on-paper-2);
  text-wrap: pretty;
  padding: clamp(.3rem, .8vh, .55rem) 0;
  border-top: 1px solid rgba(25, 20, 40, .16);
  border-bottom: 1px solid rgba(25, 20, 40, .16);
}

.rounds { display: flex; flex-direction: column; gap: clamp(.2rem, .7vh, .42rem); text-align: left; }
.rounds__item { display: grid; grid-template-columns: auto 1fr; column-gap: .55rem; align-items: baseline; }
.rounds__n { grid-row: span 2; font-family: var(--font-display); font-size: .82rem; color: var(--brick); }
.rounds__name { font-size: var(--t-small); font-weight: 600; letter-spacing: .03em; color: var(--on-paper); }
.rounds__desc {
  grid-column: 2;
  font-size: var(--t-label);
  font-weight: 400;
  line-height: 1.35;
  color: var(--on-paper-2);
}

.meta { margin-top: auto; display: flex; flex-direction: column; text-align: left; }
.meta__row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: .7rem;
  padding: clamp(.18rem, .6vh, .34rem) 0;
  border-bottom: 1px dotted rgba(25, 20, 40, .22);
}
.meta__row:last-child { border-bottom: 0; }
.meta dt {
  font-size: var(--t-label);
  font-weight: 500;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--on-paper-2);
}
.meta dd { font-size: var(--t-small); font-weight: 600; color: var(--on-paper); text-align: right; }

/* On paper the marigold CTA loses all its contrast, so the card button is
   crimson — the one place crimson is used as a fill rather than an accent. */
.card .btn { background: var(--crimson); color: var(--paper); align-self: center; }
.card .btn:hover { background: var(--brick); color: var(--paper); box-shadow: 0 8px 22px rgba(170, 41, 90, .34); }

/* ============================================================================
   Buttons
   ========================================================================= */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: .55em;
  margin-top: clamp(.5rem, 1.4vh, .9rem);
  padding: .7em 1.4em;
  border: 0;
  border-radius: 999px;
  background: var(--marigold);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: var(--t-small);
  font-weight: 600;
  letter-spacing: .06em;
  white-space: nowrap;
  transition: background .18s ease, transform .18s ease, box-shadow .18s ease;
}
.btn:hover {
  background: var(--marigold-hi);
  color: var(--ink);
  transform: translateY(-1px);
  box-shadow: 0 8px 22px rgba(225, 157, 57, .3);
}
.btn:active { transform: translateY(0); }
.btn__arrow { transition: transform .18s ease; }
.btn:hover .btn__arrow { transform: translateX(3px); }

.btn--lg { padding: .95em 2.1em; font-size: var(--t-body); letter-spacing: .1em; }

/* ============================================================================
   ACT TWO — the hackathon
   ========================================================================= */

.hack {
  position: relative;
  z-index: 1;
  /* Mostly transparent so the buta field carries on behind this section — an
     opaque panel cut a hard horizontal seam across the field right where the
     act ended. The tint is only enough to lift the section off the ground. */
  background: linear-gradient(180deg,
    transparent 0%,
    rgba(18, 18, 24, .82) 14%,
    rgba(18, 18, 24, .82) 86%,
    transparent 100%);
  padding: var(--gap-2xl) 0;
  border-top: 1px solid var(--line);
}

.hack__inner {
  position: relative;
  z-index: 1;                 /* above the pointer glow */
  width: var(--shell);
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: var(--gap-xl);
}

/* -- pointer glow -----------------------------------------------------------
   A soft marigold light that follows the cursor across the section. The
   engine writes --mx / --my in px (rAF-throttled, one write per frame) and
   this translates a fixed circle to them — cheaper than repainting a
   viewport-sized gradient on every move, and it stays on the compositor.

   The wrapper clips, so the light never spills past the section edges. */
.hack__glow {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
  opacity: 0;
  transition: opacity .4s ease;
}
.hack:hover .hack__glow { opacity: 1; }

.hack__glow::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  width: 460px;
  height: 460px;
  margin: -230px 0 0 -230px;
  border-radius: 50%;
  background: radial-gradient(circle,
    rgba(225, 157, 57, .15),
    rgba(225, 157, 57, .05) 42%,
    transparent 70%);
  transform: translate3d(var(--mx, 50%), var(--my, 50%), 0);
  will-change: transform;
}

/* A pointer that cannot hover has nothing to follow, and reduced motion does
   not want a light chasing it either. */
@media (hover: none), (pointer: coarse), (prefers-reduced-motion: reduce) {
  .hack__glow { display: none; }
}

/* -- corner ornaments ------------------------------------------------------- */

.corner {
  position: absolute;
  width: clamp(38px, 4.4vw, 70px);
  height: auto;
  aspect-ratio: 1;
  color: var(--marigold);
  opacity: .5;
  filter: drop-shadow(0 0 7px rgba(225, 157, 57, .3));
  pointer-events: none;
}

/* The ornament is authored for the top-left; the other three are the same
   drawing turned a quarter at a time. */
.corner--tl { top: calc(var(--gap-l) * -1); left: calc(var(--gap-m) * -1); }
.corner--tr { top: calc(var(--gap-l) * -1); right: calc(var(--gap-m) * -1); transform: rotate(90deg); }
.corner--br { bottom: calc(var(--gap-l) * -1); right: calc(var(--gap-m) * -1); transform: rotate(180deg); }
.corner--bl { bottom: calc(var(--gap-l) * -1); left: calc(var(--gap-m) * -1); transform: rotate(270deg); }

.hack__head {
  display: grid;
  grid-template-columns: 1fr minmax(240px, 300px);
  gap: var(--gap-l);
  align-items: start;
}

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

.hack__eyebrow {
  font-size: var(--t-label);
  font-weight: 600;
  letter-spacing: .3em;
  text-transform: uppercase;
  color: var(--crimson);
}

/* One flat brick offset. The previous build stacked three coloured shadows,
   which is exactly what made this section read as noise. */
.hack__title {
  font-size: var(--t-h2);
  color: var(--ice);
  text-shadow: 4px 4px 0 var(--brick);
}

.hack__sub {
  display: flex;
  align-items: center;
  gap: var(--gap-s);
  font-family: var(--font-display);
  font-size: clamp(1rem, 1.7vw, 1.4rem);
  letter-spacing: .12em;
  color: var(--marigold);
}

.hack__blurb { max-width: 54ch; color: var(--ice-70); text-wrap: pretty; }
.hack__blurb strong { color: var(--marigold); font-weight: 600; }

.ledger { display: flex; flex-direction: column; padding-left: var(--gap-m); border-left: 1px solid var(--line); }
.ledger__row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--gap-m);
  padding: .7rem 0;
  border-bottom: 1px dashed var(--line);
}
.ledger__row:last-child { border-bottom: 0; }
.ledger dt {
  font-size: var(--t-label);
  font-weight: 500;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--ice-55);
}
.ledger dd { font-size: var(--t-small); font-weight: 600; color: var(--ice); text-align: right; }

.block { display: flex; flex-direction: column; gap: var(--gap-m); }
.block__label {
  font-family: var(--font-body);
  font-size: var(--t-label);
  font-weight: 600;
  letter-spacing: .3em;
  text-transform: uppercase;
  color: var(--ice-38);
}
.block__note { max-width: 72ch; font-size: var(--t-small); color: var(--ice-55); text-wrap: pretty; }

.grid { display: grid; gap: var(--gap-m); }
.grid--two  { grid-template-columns: repeat(2, 1fr); }
.grid--four { grid-template-columns: repeat(4, 1fr); }

.panel {
  --band: clamp(12px, 1.5vw, 20px);
  /* The band already insets the content; this is only the breathing room
     between the vine and the type. */
  padding: clamp(.5rem, 1.3vw, 1rem) clamp(.7rem, 1.6vw, 1.2rem);
}
.panel__n { font-family: var(--font-display); font-size: 1.5rem; line-height: 1; color: var(--brick); }
.panel__title {
  margin-top: .35rem;
  font-family: var(--font-body);
  font-size: var(--t-h4);
  font-weight: 600;
  letter-spacing: .04em;
  color: var(--on-paper);
}
.panel__text { margin-top: .5rem; font-size: var(--t-small); color: var(--on-paper-2); text-wrap: pretty; }

/* -- the mentorship timeline -------------------------------------------------
   A horizontal rule threading through numbered nodes, each carrying a small
   plate. Column count is not hardcoded — auto-fit lets the same component
   hold three steps today without leaving a rule built for five. */

.timeline {
  position: relative;
  z-index: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: var(--gap-m);
  margin: 0;
  padding: 0;
  list-style: none;
}

/* The connecting rule. Behind everything and z-index'd under the nodes, whose
   opaque fill is what visually "breaks" it into five segments. */
.timeline::before {
  content: "";
  position: absolute;
  z-index: -1;
  left: 5%;
  right: 5%;
  top: 17px;
  height: 1px;
  background: linear-gradient(90deg, transparent, var(--marigold-lo) 8%, var(--marigold-lo) 92%, transparent);
}

.timeline__step {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--gap-s);
  text-align: center;
}

.timeline__node {
  display: grid;
  place-items: center;
  flex-shrink: 0;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  background: var(--ink-sunk);
  border: 1.5px solid var(--marigold);
  font-family: var(--font-display);
  font-size: .9rem;
  color: var(--marigold);
}

.timeline__card {
  width: 100%;
  --band: clamp(9px, 1vw, 14px);
  padding: clamp(.6rem, 1.4vw, 1rem) clamp(.7rem, 1.5vw, 1.2rem);
}
.timeline__title {
  font-family: var(--font-body);
  font-size: var(--t-small);
  font-weight: 600;
  letter-spacing: .04em;
  color: var(--on-paper);
}
.timeline__text { margin-top: .3rem; font-size: var(--t-label); color: var(--on-paper-2); text-wrap: pretty; }

/* -- tracks: genuinely sealed until day one -------------------------------- */

.track {
  --band: clamp(10px, 1.2vw, 16px);
  display: flex;
  flex-direction: column;
  gap: .55rem;
  padding: clamp(.5rem, 1.3vw, 1rem) clamp(.7rem, 1.6vw, 1.1rem);
}
.track__n { font-family: var(--font-display); font-size: 1.35rem; line-height: 1; color: var(--brick); }
/* A redaction bar struck across the stock, rather than a lit panel — the
   sealed state has to read as something withheld from a printed plate. */
.track__plate {
  align-self: flex-start;
  max-width: 100%;
  padding: .3rem .55rem;
  background: var(--on-paper);
  border-radius: 2px;
  overflow: hidden;
  font-size: .7rem;
  letter-spacing: .15em;
  line-height: 1.2;
  color: rgba(249, 246, 231, .34);
}
.track__state {
  font-size: var(--t-label);
  font-weight: 600;
  letter-spacing: .22em;
  text-transform: uppercase;
  color: var(--mint-deep);
}

.hack__cta {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--gap-m);
  flex-wrap: wrap;
  padding-top: var(--gap-s);
  border-top: 1px solid var(--line);
}
.hack__ctatext { max-width: 52ch; font-size: var(--t-small); color: var(--ice-70); }

/* ============================================================================
   Mentorship timeline — its own section, not another shelf inside the
   hackathon block. Plain ground (the buta field shows straight through,
   same as the footer below it), with only a hairline top border to mark
   where the hackathon block ends.
   ========================================================================= */

.mentor-timeline {
  position: relative;
  z-index: 1;
  padding: var(--gap-2xl) 0;
  border-top: 1px solid var(--line);
}
.mentor-timeline__inner {
  width: var(--shell);
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: var(--gap-m);
}

/* ============================================================================
   Footer
   ========================================================================= */

.foot { position: relative; z-index: 1; padding: var(--gap-xl) 0; border-top: 1px solid var(--line); }
.foot__inner {
  width: var(--shell);
  margin: 0 auto;
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--gap-m);
  flex-wrap: wrap;
}
.foot__mark { font-family: var(--font-display); font-size: clamp(1.2rem, 2.4vw, 1.8rem); color: var(--crimson); }
.foot__where { font-size: var(--t-small); color: var(--ice-55); }
.foot__social { font-size: var(--t-small); font-weight: 500; }

/* ============================================================================
   Motion
   ========================================================================= */

@keyframes float { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-9px); } }
@keyframes bob   { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(7px); } }

/* ============================================================================
   Phone + tablet
   ========================================================================= */

@media (max-width: 1024px) {
  .hack__head { grid-template-columns: 1fr; }
  .ledger { padding-left: 0; border-left: 0; border-top: 1px solid var(--line); padding-top: var(--gap-s); }
  .grid--four { grid-template-columns: repeat(2, 1fr); }
  .card { width: min(40vw, 380px); }
}

@media (max-width: 760px) {
  .act__stage { padding-bottom: clamp(3rem, 8vh, 5rem); }

  /* One card at a time, near-full-bleed and centred over the stage. */
  .card,
  .card--one,
  .card--two {
    left: 5vw;
    right: 5vw;
    width: auto;
    max-width: 420px;
    margin-inline: auto;
    pointer-events: none;
  }
  .card .btn { pointer-events: auto; }

  /* A thinner band keeps the interior usable once the card goes full-bleed. */
  .card { --band: clamp(17px, 6.4vw, 34px); }

  .hero__logo { width: min(58vw, 300px); }
  .dancer__plate { width: min(100%, 78vw); }
  .ring { height: 48%; }
  .lockline { bottom: clamp(3rem, 7vh, 5rem); font-size: clamp(1rem, 4vw, 1.3rem); }

  .grid--two, .grid--four { grid-template-columns: 1fr; }

  .timeline { grid-template-columns: 1fr; gap: var(--gap-l); }
  .timeline::before {
    left: 17px; right: auto; top: 6px; bottom: 6px; width: 1px; height: auto;
    background: linear-gradient(180deg, transparent, var(--marigold-lo) 6%, var(--marigold-lo) 94%, transparent);
  }
  .timeline__step { flex-direction: row; align-items: flex-start; text-align: left; }
  .timeline__card { text-align: left; }

  /* The shell is 92vw here, so the desktop inset would hang the ornaments off
     the side of the screen. Pull them in and size them down to match. */
  .corner { width: clamp(28px, 9vw, 44px); }
  .corner--tl, .corner--bl { left: calc(var(--gap-xs) * -1); }
  .corner--tr, .corner--br { right: calc(var(--gap-xs) * -1); }
  .corner--tl, .corner--tr { top: calc(var(--gap-m) * -1); }
  .corner--bl, .corner--br { bottom: calc(var(--gap-m) * -1); }

  .hack__cta { flex-direction: column; align-items: flex-start; }
  .foot__inner { flex-direction: column; align-items: flex-start; gap: var(--gap-xs); }
}

@media (max-width: 420px) {
  .card { --band: clamp(15px, 5.6vw, 26px); }
  .rounds__desc { display: none; }
}

/* Landscape phones and short laptops. There is only ~330px of interior once
   the ornament band is drawn, so the plate has to lose the parts that are
   nice-to-have — the rounds and the hook — and tighten what is left. Without
   this the card simply runs off the top and bottom of the stage. */
@media (max-height: 620px) and (min-width: 761px) {
  .card {
    width: min(26vw, 300px);
    --band: clamp(16px, 2.2vw, 30px);
  }
  .rounds,
  .card__hook { display: none; }
  .card__body { gap: clamp(.2rem, .8vh, .4rem); }
  .card__title { font-size: clamp(1.2rem, 2vw, 1.6rem); }
  .meta__row { padding: clamp(.1rem, .4vh, .22rem) 0; }
  .card .btn { padding: .5rem 1.1rem; }

  /* The stage is barely taller than the figure here, so the closing beat has
     to move her much further out of the line's way than it does on desktop. */
  :root {
    --lift-vh: -15vh;
    --lift-k: .3;
  }

  /* The ring would otherwise be taller than the stage on a 390px viewport. */
  .ring { height: 66%; }
  .lockline { bottom: clamp(1.5rem, 6vh, 3rem); font-size: clamp(.95rem, 2.6vw, 1.25rem); }
}

/* Very short viewports keep only the essentials on the plate. */
@media (max-height: 460px) and (min-width: 761px) {
  .card__kicker { display: none; }
  .meta__row:nth-child(3) { display: none; }   /* Teams — the least load-bearing */
}

/* ============================================================================
   Reduced motion + no-JS — hand back a plain, complete document
   ========================================================================= */

@media (prefers-reduced-motion: reduce) {
  .act { height: auto; }
  .act__stage {
    position: static;
    height: auto;
    display: flex;
    flex-direction: column;
    gap: var(--gap-xl);
    padding: var(--gap-xl) 5vw;
  }
  .card, .card--one, .card--two {
    position: static;
    width: min(100%, 400px);
    margin: 0 auto;
    transform: none;
    opacity: 1;
  }
  .hero, .lockline { opacity: 1; transform: none; }
  /* Not transform:none — the plate is left:50% and owes its centring to the
     translate, so clearing it would shove her off to the right. */
  .dancer__plate { opacity: 1; transform: translateX(-50%); animation: none; }
  .lockline {
    position: static;
    max-width: 34ch;
    margin: 0 auto;
    padding: var(--gap-l) 0 0;
    opacity: 1;
  }
  .ring { display: none; }
  .cue { display: none; }
  .hero__logo { animation: none; }
  .btn, .btn__arrow { transition: none; }
}

/* The engine adds .js on boot. Without it the sticky stage would pin a
   viewport-tall box and stack both absolute cards over the hero. */
html:not(.js) .act { height: auto; }
html:not(.js) .act__stage {
  position: static;
  height: auto;
  display: flex;
  flex-direction: column;
  gap: var(--gap-xl);
  padding: var(--gap-xl) 5vw;
}
html:not(.js) .card,
html:not(.js) .card--one,
html:not(.js) .card--two {
  position: static;
  width: min(100%, 400px);
  margin: 0 auto;
  transform: none;
  opacity: 1;
}
html:not(.js) .lockline {
  position: static;
  max-width: 34ch;
  margin: 0 auto;
  padding: var(--gap-l) 0 0;
  opacity: 1;
}
html:not(.js) .ring { display: none; }
/* The polar field is built by the engine, so without it the ground would be
   bare black. Fall back to the pre-baked scatter tile — not the same layout,
   but the same texture, and it costs one request. */
html:not(.js) .backdrop__weave {
  background: url("../assets/bg-scatter.webp") repeat;
  background-size: clamp(430px, 34vw, 560px) auto;
  opacity: .3;
}
html:not(.js) .cue { display: none; }
