/* Self-hosted brand faces (assets/fonts/, woff2). Neue Montreal has no 600 —
   font-weight:600 in the UI resolves to Bold(700) via CSS weight matching. */
@font-face {
  font-family: "Neue Montreal";
  src: url("assets/fonts/NeueMontreal-Regular.woff2") format("woff2");
  font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "Neue Montreal";
  src: url("assets/fonts/NeueMontreal-Medium.woff2") format("woff2");
  font-weight: 500; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "Neue Montreal";
  src: url("assets/fonts/NeueMontreal-Bold.woff2") format("woff2");
  font-weight: 700; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "Matrix Sans";
  src: url("assets/fonts/MatrixSans-Regular.woff2") format("woff2");
  font-weight: 400; font-style: normal; font-display: swap;
}

:root {
  --ink: #0a0a0a;
  --paper: #ffffff;                /* white paper (graph-grid trial) */
  --rule: #807b6f;                 /* nav separator stroke */
  --grid: #e2e2e2;                 /* graph-paper grid */
  --nav-font: "Neue Montreal", ui-sans-serif, system-ui, sans-serif;
  --pixel-font: "Matrix Sans", "Neue Montreal", ui-sans-serif, sans-serif;

  --nav-h: clamp(56px, 5vw, 80px);
  --gutter: clamp(20px, 5vw, 64px);

  /* graph-paper pitch (square cells, page-wide) */
  --grid-size: 3.6vw;

  --ease: cubic-bezier(0.22, 1, 0.36, 1);

  /* semantic z-scale */
  --z-grid: 0;
  --z-back: 1;
  --z-info: 5;
  --z-curtain: 15;                 /* intro poster, above the page but below nav */
  --z-nav: 20;

  /* White paper + graph-paper grid. The teaser/poster ::before paint these. */
  --bg-layer:
    linear-gradient(to right, var(--grid) 1px, transparent 1px),
    linear-gradient(to bottom, var(--grid) 1px, transparent 1px);
  --bg-size: var(--grid-size) var(--grid-size);
  --bg-opacity: 1;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--nav-font);
  -webkit-font-smoothing: antialiased;
}
body { overflow-x: hidden; }

.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 100;
  background: var(--ink);
  color: var(--paper);
  padding: 10px 16px;
  border-radius: 0 0 8px 0;
  font-size: 14px;
  font-weight: 500;
}
.skip-link:focus { left: 0; }

/* ---------- Header / nav ---------- */
.site-header {
  position: relative;
  z-index: var(--z-nav);
  background: var(--paper);
  border-bottom: 1px solid var(--rule);
}

.nav-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  height: var(--nav-h);
  padding: 0 var(--gutter);
}

.brand {
  display: inline-flex;
  align-items: center;
  line-height: 0;
  transition: opacity 0.35s var(--ease);
}
.brand img {
  /* Devfolio's glyphs are denser than the old ETHIndia lockup, so it reads
     bigger at the same height — trimmed ~1/3 to keep the old nav footprint */
  height: clamp(18px, 1.8vw, 26px);
  width: auto;
  display: block;
}
.brand:hover { opacity: 0.82; }
.brand:focus-visible { outline: 2px solid var(--ink); outline-offset: 6px; border-radius: 4px; }

/* ---------- Teaser: full-screen stage ---------- */
.teaser {
  position: relative;
  isolation: isolate;
  min-height: calc(100svh - var(--nav-h));
  display: grid;
  place-items: center;
  /* extra bottom padding lifts the centered content above the motif band,
     so the hero sits in the upper-middle like the source layout. Bumped up
     (client: push the copy up — the lede was sitting into the motifs). */
  padding: clamp(16px, 3vw, 44px) var(--gutter) clamp(120px, 18vh, 240px);
  overflow: hidden;                /* clip the motif bleed */
}

.teaser::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: var(--z-grid);
  background-image: var(--bg-layer);
  background-size: var(--bg-size);
  opacity: var(--bg-opacity);
}

/* ---------- Centered announcement ---------- */
.stage-center {
  position: relative;
  z-index: var(--z-info);
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  /* Rhythm is grouped, not uniform: the lockup + tagline read as one identity
     unit (tight), then a wide breath isolates the date/venue hero as the payoff,
     then a medium gap to the supporting lede. Margins per child, not a flat gap. */
  gap: 0;
  max-width: min(92vw, 760px);
  /* 1.08, eased down from 1.2: the scale is transform-only so it's invisible to
     grid centring — at 1.2 the visual content ran slightly taller than the stage
     at some laptop heights and the symmetric scale-around-centre pushed the top
     up under the nav, where overflow:hidden clipped it silently. 10% smaller
     keeps the whole group inside the stage (no top clip, no motif overlap). */
  transform: scale(1.08);
}

/* ETHIndia lockup crowning the centered block, above the tagline */
.wordmark {
  display: block;
  width: clamp(150px, 20vw, 210px);
  height: auto;
}

.eyebrow {
  margin: clamp(8px, 1vw, 12px) 0 0;   /* tight to the wordmark — one lockup */
  font-family: var(--nav-font);
  font-weight: 600;
  font-size: clamp(15px, 1.6vw, 20px);
  /* Fixed line-height so the rotating last word can't drive the line box: each
     Indian script has its own intrinsic ascent/descent, and with line-height
     `normal` every 2s swap re-measured the eyebrow and shoved the vertically
     centred group up/down. A fixed value pins the box height across all scripts
     (client: language change makes the content fluctuate). */
  line-height: 1.25;
  letter-spacing: -0.01em;
  word-spacing: 0.12em;   /* a touch more air between words */
  color: var(--ink);
  /* The rotating word renders in device fallback fonts (Arabic/Bengali are far
     wider on real phones than on desktop). Wrap it to its own line and, worst
     case, break it rather than let it run off the edge (client: getting cut off). */
  max-width: 100%;
  overflow-wrap: break-word;
  text-wrap: balance;
}
.eyebrow .rot { unicode-bidi: isolate; }   /* keep RTL/mixed-script words self-contained */

/* eyebrow→hero and hero→lede share one value so the payoff sits evenly between
   its neighbours (client: the gaps read uneven when they differed) */
.hero { margin: clamp(22px, 3vw, 38px) 0 0; line-height: 0; }
.hero img {
  display: block;
  /* Height-aware width: the `68vh` term caps the hero by viewport height so the
     whole group scales down continuously on short windows instead of snapping at
     a breakpoint. It only binds below ~910px tall; taller screens keep the 620px
     max. Replaces the old short-landscape regime swap (client: below 821px the
     content scaled down in an ugly way — now it's uniformly responsive). */
  width: min(clamp(260px, 46vw, 620px), 68vh);
  height: auto;
}

.lede {
  margin: clamp(22px, 3vw, 38px) 0 0;   /* matches the eyebrow→hero gap (even rhythm) */
  max-width: 40rem;
  font-size: clamp(15px, 1.7vw, 21px);
  font-weight: 400;
  line-height: 1.42;
  color: color-mix(in oklab, var(--ink) 84%, var(--paper));
  text-wrap: pretty;
}

/* ---------- CTA button ---------- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5em;
  /* height tracks the nav (clamp 56→80) so the pill stays proportioned, not a
     fixed 44px that swallows the short mobile bar. Horizontal padding only. */
  min-height: clamp(38px, 5vw, 44px);
  padding: 0 clamp(14px, 2vw, 22px);
  font-family: var(--nav-font);
  font-weight: 600;
  font-size: clamp(14px, 1.05vw, 16px);
  white-space: nowrap;
  text-decoration: none;
  transition: transform 0.35s var(--ease), opacity 0.35s var(--ease), background 0.35s var(--ease);
}
.btn:focus-visible { outline: 2px solid var(--ink); outline-offset: 4px; border-radius: 999px; }
.btn--primary {
  background: var(--ink);
  color: var(--paper);
  border-radius: 999px;
}
.btn--primary:hover { transform: translateY(-2px); }
.btn--primary:active { transform: translateY(0) scale(0.98); }

/* hero CTA lives in the announcement block on mobile only — see the mobile query */
.hero-cta { display: none; }

/* ---------- Motif frame (decorative) ---------- */
.frame {
  position: absolute;
  left: 0;
  bottom: 0;
  width: 100%;
  /* Height normally follows the 1200:675 composition (= 56.25vw, same as the old
     aspect-ratio). But on short/wide windows that box grows taller than the
     viewport and the motifs climb into the copy — so cap it at a share of the
     viewport height, keeping the art a bottom fringe. The motifs keep their
     width-driven size; only their vertical spread compresses into the band. */
  height: min(56.25vw, 68vh);
  z-index: var(--z-back);
  pointer-events: none;
}
.motif {
  position: absolute;
  left: var(--l);
  top: var(--t);
  width: var(--w);
  height: auto;
  transform: translate3d(calc(var(--px, 0px) * var(--depth)), calc(var(--py, 0px) * var(--depth)), 0);
  transition: transform 0.55s var(--ease);
  will-change: transform;
}

/* ---------- Intro poster: the opening curtain ----------
   The full-blown folk-art wordmark assembles at full bleed, holds, then recedes
   to reveal the centered announcement underneath (script.js drives the timing).
   Gated on `html.js` + no reduced motion, so the plain final layout still ships. */
.intro-poster {
  display: none;                   /* no JS → never shown; final layout is default */
  position: fixed;
  top: var(--nav-h);
  left: 0;
  right: 0;
  bottom: 0;
  z-index: var(--z-curtain);
  place-items: center;
  background: var(--paper);
  overflow: hidden;                /* motifs bleed off the edges */
  pointer-events: none;
}
html.js .intro-poster { display: grid; }

/* same background as the teaser → seamless hand-off when it fades */
.intro-poster::before {
  content: "";
  position: absolute;
  inset: 0;
  background-image: var(--bg-layer);
  background-size: var(--bg-size);
  opacity: var(--bg-opacity);
}

.pstage { position: relative; width: 100%; aspect-ratio: 1200 / 555; }
.pword {
  position: absolute;
  left: 0;
  top: 10.27%;
  width: 98.25%;
  z-index: 2;
}
.pmotif {
  position: absolute;
  left: var(--l);
  top: var(--t);
  width: var(--w);
  z-index: var(--z, 1);
}
.pword img, .pmotif img { display: block; width: 100%; height: auto; }

/* pre-composed mobile poster — hidden by default, swapped in for .pstage ≤620px */
.pflat { display: none; }

/* recede — kept in sync with LEAVE_MS in script.js */
.intro-poster.leaving {
  opacity: 0;
  transform: scale(0.945) translateY(-1.5%);
  transition: opacity 0.85s var(--ease), transform 0.95s var(--ease);
}

@keyframes pGridIn { from { opacity: 0; } to { opacity: var(--bg-opacity); } }
@keyframes pRiseIn { from { opacity: 0; transform: translateY(22px) scale(1.01); } to { opacity: 1; transform: none; } }
@keyframes pPopIn  { from { opacity: 0; transform: scale(0.9) translateY(12px); } to { opacity: 1; transform: none; } }
@keyframes posterFailsafe { to { opacity: 0; visibility: hidden; pointer-events: none; } }

@media (prefers-reduced-motion: no-preference) {
  html.js .intro-poster::before { animation: pGridIn 0.8s var(--ease) both; }
  html.js .pword img  { animation: pRiseIn 1.05s var(--ease) 0.1s both; }
  html.js .pmotif img { animation: pPopIn 0.9s var(--ease) var(--d, 0ms) both; }
  /* mobile flat poster: same gentle rise so it eases in, not snaps in */
  html.js .pflat      { animation: pRiseIn 1.05s var(--ease) 0.1s both; }
  /* last resort: if script.js never runs, the poster can't block the page forever */
  html.js .intro-poster { animation: posterFailsafe 0.01s linear 5.5s forwards; }
}

/* ---------- Intro: motifs settle up → hero rises → copy + grid fade ---------- */
.teaser.intro .motif { opacity: 0; transform: translateY(64px); }
.teaser.intro .wordmark,
.teaser.intro .eyebrow,
.teaser.intro .hero,
.teaser.intro .lede,
.teaser.intro .hero-cta { opacity: 0; transform: translateY(26px); }

.teaser.intro.play .wordmark,
.teaser.intro.play .eyebrow {
  opacity: 1; transform: none;
  transition: opacity 0.85s var(--ease) 1.05s, transform 0.85s var(--ease) 1.05s;
}

.teaser.intro.play .motif {
  opacity: 1;
  transform: translateY(0);
  transition: opacity 0.8s var(--ease) var(--d, 0ms), transform 0.95s var(--ease) var(--d, 0ms);
}
.teaser.intro.play .hero {
  opacity: 1; transform: none;
  transition: opacity 0.85s var(--ease) 1.05s, transform 0.85s var(--ease) 1.05s;
}
.teaser.intro.play .lede {
  opacity: 1; transform: none;
  transition: opacity 0.7s var(--ease) 1.35s, transform 0.7s var(--ease) 1.35s;
}
.teaser.intro.play .hero-cta {
  opacity: 1; transform: none;
  transition: opacity 0.7s var(--ease) 1.55s, transform 0.7s var(--ease) 1.55s;
}

/* ---------- Tablet ---------- */
@media (max-width: 900px) {
  :root { --grid-size: 4.4vw; }
  .hero img { width: min(clamp(240px, 62vw, 520px), 68vh); }
}

/* ---------- Mobile ---------- */
@media (max-width: 620px) {
  :root { --grid-size: 8vw; }

  /* the desktop scale(1.2) on the group overshoots a phone — the hero and lede
     run past both edges and clip. Drop it so the announcement sits at its
     natural size, fully inside the viewport. */
  .stage-center { transform: none; max-width: 90vw; }
  .hero img { width: min(84vw, 440px); }
  .lede { font-size: clamp(14px, 4vw, 18px); }

  /* CTA drops out of the nav into the open space below the copy, where the
     read-then-act flow lands. The nav is left with just the brand. */
  .nav-cta { display: none; }
  .hero-cta {
    display: inline-flex;
    margin-top: clamp(24px, 6vw, 34px);   /* clear read-then-act separation from the lede */
    min-height: 48px;
    font-size: 15px;
    padding: 0 26px;
  }

  /* the layered .pstage is a wide landscape that crops on a phone — swap it for
     the pre-composed illustration, re-laid-out to sit fully inside the screen. */
  .pstage { display: none; }
  .pflat { display: block; width: min(44vw, 230px); height: auto; }
}

/* Short/wide windows used to swap to a separate top-anchored, height-driven
   layout at max-height:820px — a hard breakpoint that snapped the hero size and
   re-anchored the group as you crossed it. The `68vh` cap on `.hero img` now
   scales the centred layout down continuously instead, so that regime is gone. */

/* ---------- Wide/short: fill the motif band ----------
   The motif frame is a fixed-aspect composition whose height caps at 68vh while
   the motifs keep scaling with viewport WIDTH. Past ~2:1 the composition is so
   squished that the art sinks below the fold and the page reads bare (client ref).
   On these very wide aspect ratios give the band most of the viewport height so
   the collage fills the bottom. Scoped to wide desktops/laptops (aspect ≥ 2 and
   ≥1000px) so 16:9 / 16:10 screens and phones keep the tuned 68vh band. */
@media (min-aspect-ratio: 2/1) and (min-width: 1000px) {
  .frame { height: min(56.25vw, 82vh); }
}

/* ---------- Reduced motion ---------- */
@media (prefers-reduced-motion: reduce) {
  html.js .intro-poster { display: none; }   /* skip the curtain entirely */
  .teaser::before { opacity: var(--bg-opacity); }
  .motif { transform: none !important; transition: none !important; }
  .teaser.intro .motif,
  .teaser.intro .wordmark,
  .teaser.intro .eyebrow,
  .teaser.intro .hero,
  .teaser.intro .lede,
  .teaser.intro .hero-cta { opacity: 1 !important; transform: none !important; }
  .brand, .btn { transition: none !important; }
}
