/* ART.NRZ — Fase 1, the threshold.
   Art direction is pinned by the brand book and the web brief §5 ("obligatoria, no
   interpretable"). See DESIGN.md for the direction contract and its two deviations. */

/* ---------------------------------------------------------------- faces */

@font-face {
  font-family: 'Archivo Exp';
  src: url('../assets/fonts/Archivo-Exp-500.woff2') format('woff2');
  font-weight: 500; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'Archivo Exp';
  src: url('../assets/fonts/Archivo-Exp-800.woff2') format('woff2');
  font-weight: 800; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'Space Grotesk';
  src: url('../assets/fonts/SpaceGrotesk-var-latin.woff2') format('woff2');
  font-weight: 300 600; font-style: normal; font-display: swap;
}

/* Metric-matched fallbacks, so swapping in the licensed Monument Extended later
   costs zero CLS — recalibrate size-adjust and nothing else moves. */
@font-face {
  font-family: 'Display Fallback';
  src: local('Arial Bold'), local('Helvetica Bold'), local('Liberation Sans Bold');
  size-adjust: 112%; ascent-override: 88%; descent-override: 22%; line-gap-override: 0%;
}
@font-face {
  font-family: 'Text Fallback';
  src: local('Arial'), local('Helvetica'), local('Liberation Sans');
  size-adjust: 97%; ascent-override: 95%; descent-override: 25%; line-gap-override: 0%;
}

/* ---------------------------------------------------------------- tokens */

:root {
  --black: #0A0A0A;
  --rock: #2A2A2A;
  --smoke: #3D3D3D;
  --silver: #BFBFBF;
  /* Solid, not BFBFBF@60% — see DESIGN.md "AA correction". Raised from #7A7A7A once
     the footage went in: #7A7A7A measured 3.84:1 over the brightest frame behind the
     statement. #888888 holds 4.65:1 there and 5.6:1 on flat black. */
  --silver-dim: #888888;
  /* PROJECTS is an inactive component, which WCAG exempts from 4.5:1 — but 1.3:1 was
     invisible rather than disabled. This reads as shut and still reads. */
  --inactive: #6E6E6E;
  --amber: #C8A04A;
  --amber-deep: #8C6E2E;
  --text: #EAEAEA;

  /* CHAOS carries its own accent — Documento Maestro §1.4 and §4.2. */
  --red: #DE4032;
  --red-deep: #8C2A20;
  --rock-red: #3D2521;

  --display: 'Archivo Exp', 'Display Fallback', system-ui, sans-serif;
  --grotesk: 'Space Grotesk', 'Text Fallback', system-ui, sans-serif;

  --gutter: clamp(1.25rem, 5vw, 5.5rem);
  --step: clamp(1rem, 3.2vw, 2rem);

  --ease: cubic-bezier(.16, 1, .3, 1);
  --ease-slow: cubic-bezier(.65, 0, .35, 1);

  --nav-h: calc(3.25rem + env(safe-area-inset-top));
  --foot-h: calc(3.5rem + env(safe-area-inset-bottom));

  /* The sigil row runs 15% larger than the type scale — it is the only image on the
     page. It shrinks when the door opens so the whole scene still fits one viewport. */
  --sigil-w: clamp(44px, 12.65vw, 76px);
}

/* ---------------------------------------------------------------- reset */

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

html, body { height: 100%; }

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

/* The page does not scroll: scenes replace one another. The reduced-motion block
   at the foot of this file turns it back into an ordinary document. */
body {
  margin: 0;
  overflow: hidden;
  background: var(--black);
  color: var(--text);
  font-family: var(--grotesk);
  font-size: 1rem;
  line-height: 1.6;
  font-weight: 300;
  -webkit-font-smoothing: antialiased;
  overscroll-behavior: none;
}

h1, h2, p, ul, figure { margin: 0; }
ul { padding: 0; list-style: none; }
img, svg, video { display: block; max-width: 100%; }
a { color: inherit; }
button { font: inherit; color: inherit; background: none; border: 0; }

:focus-visible {
  outline: 1px solid var(--amber);
  outline-offset: 4px;
}

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

.defs { position: absolute; width: 0; height: 0; }

.skip {
  position: fixed; top: -100px; left: var(--gutter); z-index: 200;
  padding: .75rem 1.25rem;
  background: var(--black); border: 1px solid var(--amber);
  font-size: .75rem; letter-spacing: .18em; text-transform: uppercase;
  text-decoration: none;
}
.skip:focus { top: calc(var(--nav-h) + .5rem); }

.label {
  font-family: var(--grotesk);
  font-size: .6875rem;
  font-weight: 400;
  letter-spacing: .22em;
  text-transform: uppercase;
  color: var(--silver-dim);
}

/* ---------------------------------------------------------------- backdrop */

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

.backdrop__v {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  opacity: 0;
  /* Cool and desaturate the footage so it sits under the palette rather than
     competing with it. The amber is the only warm thing on this page. */
  filter: saturate(.55) contrast(1.06) brightness(.82);
}
.backdrop__v.is-front { opacity: 1; }

/* 80/20: the footage never gets more than a fifth of the luminance budget.
   These values are not taste — they were set by measuring the brightest pixel of
   all four clips behind each text zone and pulling until the palette passed AA. */
.backdrop__scrim {
  position: absolute; inset: 0;
  background:
    radial-gradient(115% 85% at 50% 42%, rgba(10,10,10,.56) 0%, rgba(10,10,10,.80) 62%, rgba(10,10,10,.95) 100%),
    linear-gradient(180deg, rgba(10,10,10,.90) 0%, rgba(10,10,10,.62) 26%, rgba(10,10,10,.66) 68%, rgba(10,10,10,.94) 100%);
}

.atmosphere {
  position: fixed; inset: 0; z-index: 1;
  pointer-events: none;
  background-image:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.82' numOctaves='3'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)' opacity='.42'/%3E%3C/svg%3E");
  background-size: 140px 140px;
  mix-blend-mode: soft-light;
  opacity: .42;
}

/* ---------------------------------------------------------------- threshold */

.threshold {
  position: fixed; inset: 0; z-index: 100;
  display: grid; place-items: center;
  background: var(--black);
}
.threshold[hidden] { display: none; }

.threshold__mark {
  font-family: var(--display);
  font-weight: 800;
  font-size: clamp(1.5rem, 6.5vw, 3.25rem);
  letter-spacing: .26em;
  text-indent: .26em;
  text-transform: uppercase;
  background: linear-gradient(100deg,
    var(--rock) 0%, var(--rock) 38%,
    var(--silver) 47%, #FFFFFF 50%, var(--silver) 53%,
    var(--rock) 62%, var(--rock) 100%);
  background-size: 280% 100%;
  background-position: 100% 0;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.threshold__rule {
  position: absolute; left: 0; right: 0; bottom: 0;
  height: 1px; background: var(--rock);
}
.threshold__rule i { display: block; height: 100%; width: 0; background: var(--amber); }

/* ---------------------------------------------------------------- cursor */

.cursor {
  position: fixed; top: 0; left: 0; z-index: 90;
  pointer-events: none; opacity: 0;
  will-change: transform;
}
.cursor i {
  display: block; width: 6px; height: 6px;
  margin: -3px 0 0 -3px;
  border-radius: 50%;
  background: var(--amber);
  transition: box-shadow 320ms var(--ease), transform 320ms var(--ease);
}
.cursor.is-live { opacity: 1; }
.cursor.is-over i {
  transform: scale(1.6);
  box-shadow: 0 0 0 6px rgba(200, 160, 74, .16);
}

/* ---------------------------------------------------------------- nav */

.nav {
  position: fixed; top: 0; left: 0; right: 0; z-index: 60;
  height: var(--nav-h);
  padding: env(safe-area-inset-top) var(--gutter) 0;
  display: flex; align-items: center; justify-content: space-between;
}

.nav__mark {
  font-family: var(--display);
  font-weight: 800;
  font-size: .8125rem;
  letter-spacing: .2em;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--text);
}

.nav__links { display: flex; align-items: center; gap: clamp(1rem, 4vw, 2.5rem); }

.nav__link, .nav__sealed {
  font-family: var(--grotesk);
  font-size: .6875rem;
  font-weight: 400;
  letter-spacing: .22em;
  text-transform: uppercase;
  text-decoration: none;
}
.nav__link { color: var(--silver); cursor: pointer; transition: color 320ms var(--ease); }
.nav__link:hover { color: var(--amber); }

/* Sealed is not a link and never says "coming soon" — the brief is explicit. */
.nav__sealed { color: var(--inactive); cursor: default; user-select: none; }

/* ---------------------------------------------------------------- stage */

.stage {
  position: fixed; inset: 0; z-index: 10;
  outline: none;
}

.scene {
  position: absolute; inset: 0;
  padding: calc(var(--nav-h) + var(--step)) var(--gutter) calc(var(--foot-h) + var(--step));
  display: flex; flex-direction: column;
  opacity: 0;
  visibility: hidden;
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
}
.scene.is-active { opacity: 1; visibility: visible; }

/* Scene 0 — the statement, anchored low. The upper two thirds stay empty. */
/* Centred on the vertical axis. The cue no longer sits under the statement — it runs
   down the right margin — so the lower half is free and the type can hold the middle. */
.scene--statement { justify-content: center; }

/* A fall of black down the left edge only, under the statement. Straight left-to-right,
   70% to nothing by the halfway mark, so the right half of the frame is the footage
   untouched. Local rather than global: raising the scrim over the whole frame would
   flatten the image and break the 80/20 balance. */
.scene--statement::before {
  content: '';
  position: absolute; inset: 0;
  z-index: 0;
  pointer-events: none;
  background: linear-gradient(to right,
    rgba(10, 10, 10, .70) 0%,
    rgba(10, 10, 10, .38) 24%,
    rgba(10, 10, 10, .12) 40%,
    rgba(10, 10, 10, 0) 50%);
}

/* Three registers, not three lines.

   Setting all three sentences at one size made them read as a paragraph broken into
   thirds — six wrapped lines of near-identical length, no hierarchy, and a 559px
   measure at 48px extended type, which is far past comfortable. They do different
   jobs, so they get different registers: a small tracked charge, one monumental
   answer that owns the viewport, and a quiet amber hook. Small → large → small is the
   proportion of an inscription, which is what this brand is built on. */
.statement {
  /* Positioned above the left gradient rather than relying on tree order. */
  position: relative;
  z-index: 1;
  display: flex; flex-direction: column;
  align-items: flex-start;
  max-width: 22ch;
  font-family: var(--display);
}

/* Each register is marked by an amber rule standing in the gutter, clear of the type.
   Absolutely positioned rather than a border so the text keeps its place at the gutter
   line and the rules reach outward into the margin. */
.statement__answer,
.statement__hook { position: relative; }

/* The rules behave like the light the brand book names them after — "Ámbar Industrial ·
   Iluminación · Lujo subterráneo". A filament that never fully settles: long calm held
   at half, broken by short bursts of instability. Nothing travels along them, which is
   the point — the descending light already belongs to the sigils and the scroll cue.

   The two run on 7.3s and 9.1s. Co-prime enough that they never fall into step, so the
   pair never reads as a loop. */
.statement__answer::before,
.statement__hook::before {
  content: '';
  position: absolute;
  left: calc(-1 * clamp(.75rem, 2.6vw, 2.5rem));
  top: 0; bottom: 0;
  width: 1px;
  background: var(--amber);
  opacity: .55;
  animation: filament 7.3s linear infinite;
}
.statement__hook::before { animation-duration: 9.1s; animation-delay: -2.4s; }

@keyframes filament {
  0%   { opacity: .55; }
  4%   { opacity: 1; }
  6%   { opacity: .58; }
  9%   { opacity: .95; }
  13%  { opacity: .5; }
  38%  { opacity: .5; }
  41%  { opacity: 1; }
  43%  { opacity: .62; }
  46%  { opacity: .9; }
  50%  { opacity: .52; }
  74%  { opacity: .55; }
  77%  { opacity: .84; }
  80%  { opacity: .55; }
  100% { opacity: .55; }
}

/* The answer. One sentence holds the frame; nothing else on this scene is display
   scale, so it can go far bigger than three competing lines ever could. */
.statement__answer {
  font-weight: 500;
  font-size: clamp(2.5rem, 6.2vw, 5rem);
  line-height: .98;
  letter-spacing: -.035em;
  color: var(--text);
  max-width: 11ch;
  margin-bottom: clamp(1.1rem, 3vh, 1.75rem);
}

/* The hook. Kept in the display face so it belongs to the answer's family, but small
   and in the accent — a coda, not a third headline. */
.statement__hook {
  font-weight: 500;
  font-size: clamp(1.0625rem, 2.3vw, 1.5rem);
  line-height: 1.25;
  letter-spacing: .01em;
  color: var(--amber);
  max-width: 20ch;
}

/* Scene 1 — sigils centred, the door beneath them. */
.scene--chapters {
  justify-content: center;
  align-items: center;
  text-align: center;
  gap: clamp(1.75rem, 5vh, 3.25rem);
}

/* A local pool of shadow under the centre content. Without it the carved sigils —
   #2A2A2A hairlines — sit *below* the luminance of bright footage and vanish
   outright, which would delete the one message this scene exists to carry. */
.scene--chapters::before {
  content: '';
  position: absolute; inset: 0;
  pointer-events: none;
  background: radial-gradient(62% 50% at 50% 50%,
    rgba(10,10,10,.93) 0%, rgba(10,10,10,.86) 42%, rgba(10,10,10,.55) 72%, rgba(10,10,10,0) 100%);
}
.scene--chapters > * { position: relative; }

.chapters__label { letter-spacing: .3em; }

/* ---------------------------------------------------------------- sigils */

/* The row runs 15% larger than the type scale: it is the only image on this page
   and it carries the whole "five chapters, none open" message. */
.sigils {
  display: flex;
  align-items: flex-start;
  justify-content: center;
  gap: clamp(.75rem, 3.9vw, 3.45rem);
}

/* No transition on width. The size change is applied in one mutation and the motion is
   carried by a FLIP transform in js/main.js — the layout resolves once instead of on
   every frame, and the sigils then travel on the same clock and curve as the rest of
   the scene rather than on a separate CSS timeline. */
.sigil {
  display: flex; flex-direction: column; align-items: center;
  gap: .875rem;
  width: var(--sigil-w);
}

.sigil__svg { width: 100%; height: auto; overflow: visible; }

/* No `vector-effect: non-scaling-stroke`.

   It measures the dash pattern in screen space while getTotalLength() reports viewBox
   units, so the two disagree by the viewBox scale. At 76px over a 56-unit box that is
   1.357: the dash covered only ~57% of the path, which left the tail of every sigil lit
   before the animation began and stopped the light short of the end. Rasterising the
   real SVG both ways: 48 lit pixels at rest with it, 0 without; 57% coverage at full
   draw with it, 100% without.

   The stroke now scales with the sigil — heavier on the landing, lighter once the gate
   shrinks them — which is ordinary SVG behaviour and keeps carved and lit identical to
   each other at every size, since both drop the effect together. */
.sigil__carved,
.sigil__lit {
  fill: none;
  stroke-width: 1.6;
  stroke-linejoin: miter;
}
.sigil__carved { stroke: var(--rock); stroke-linecap: butt; }

/* Round cap only on the lit copy: while the dash travels, the head of the light is
   a point rather than a flat edge. The carving stays butt-ended and brutalist.
   JS sets the dash length from the real path, so replacement vectors just work. */
.sigil__lit {
  stroke: var(--amber);
  stroke-linecap: round;
  filter: url(#ember);
  opacity: 0;
}

/* The carving is what stays "apenas visible" when sealed — not the label. The brief
   shows the chapter title on sealed cards, and #2A2A2A text would be 1.38:1. */
.sigil__num {
  font-family: var(--grotesk);
  font-size: .6875rem;
  font-weight: 400;
  letter-spacing: .2em;
  color: var(--silver-dim);
  transition: color 480ms var(--ease);
}
.sigil.is-lit .sigil__num { color: var(--amber); }

.sigil__name {
  display: none;
  overflow: hidden;              /* GSAP collapses this by height, so it must clip */
  font-size: .6875rem;
  font-weight: 300;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--silver-dim);
  transition: color 480ms var(--ease);
}
.sigil.is-lit .sigil__name { color: var(--silver); }

/* CHAOS burns red. These must sit AFTER the generic .is-lit rules: the lit selectors
   tie on specificity at three classes each, so source order is what decides. */
.sigil--chaos .sigil__carved { stroke: var(--rock-red); }
.sigil--chaos .sigil__lit { stroke: var(--red); }
.sigil--chaos .sigil__num,
.sigil--chaos.is-lit .sigil__num,
.sigil--chaos.is-lit .sigil__name { color: var(--red); }

/* ---------------------------------------------------------------- the gate */

/* Opening the door must not lengthen the page: the scene stays one viewport tall.
   The room is made by shrinking the sigils and retiring the label, not by scrolling. */
/* Only the resting values live here. Every one of them is animated to by GSAP on the
   way in and out, so the class never causes a step change on its own. */
.scene--chapters.is-open {
  --sigil-w: clamp(26px, 6.6vw, 42px);
  gap: clamp(.6rem, 1.6vh, 1.1rem);
  padding-block: calc(var(--nav-h) + .5rem) calc(var(--foot-h) + .5rem);
}
/* Layout only. Opacity is owned by GSAP, which fades the label out before this lands
   and back in on close — an `opacity: 0` here would clobber both. */
.scene--chapters.is-open .chapters__label {
  max-height: 0;
  margin: 0;
  overflow: hidden;
}
/* Collapsed, not `display: none`. A display flip is instantaneous by definition, and
   GSAP animates to exactly these values — so the tween lands on the resting state and
   clearing the inline styles afterwards changes nothing. Owning the resting state here
   is what stops the names springing back the moment the open animation finishes. */
.scene--chapters.is-open .sigil__name {
  max-height: 0;
  opacity: 0;
}

/* Every vertical margin inside the panel tightens together, so the whole thing
   lands inside one 16:9 viewport instead of growing a scrollbar. */
.scene--chapters.is-open .gate__inner {
  margin-top: clamp(.65rem, 1.6vh, .95rem);
}
.scene--chapters.is-open .gate__rule { margin-bottom: clamp(.8rem, 2vh, 1.15rem); }
.scene--chapters.is-open .email { margin-top: clamp(.7rem, 1.8vh, 1rem); }
.scene--chapters.is-open .email__hint { margin-top: .55rem; }
.scene--chapters.is-open .access { margin-top: clamp(.7rem, 1.8vh, 1rem); }
.scene--chapters.is-open .access__intro { margin-bottom: clamp(.65rem, 1.6vh, .95rem); }
.scene--chapters.is-open .btn { margin-top: clamp(.7rem, 1.8vh, 1rem); }
.scene--chapters.is-open .access__msg { margin-top: .8rem; }
.scene--chapters.is-open .gate__where { margin-top: clamp(.7rem, 1.8vh, 1rem); }

/* Collapses in the same single mutation as the sigils; GSAP fades it out first so the
   layout change lands on something already invisible. No transition here either. */
.chapters__label { max-height: 3em; }

.gate { width: 100%; max-width: 34rem; }

.gate__trigger {
  position: relative;
  display: inline-flex; align-items: center; gap: 1.5rem;
  padding: 1rem 1.5rem;
  cursor: pointer;
  border: 1px solid rgba(200, 160, 74, .38);
  /* Blur here is legibility over moving footage, not decoration. */
  background: rgba(10, 10, 10, .5);
  backdrop-filter: blur(6px);
  color: var(--silver);
  transition: color 380ms var(--ease), border-color 380ms var(--ease), background-color 380ms var(--ease);
}
.gate__trigger:hover { color: var(--text); border-color: var(--amber); background: rgba(10, 10, 10, .68); }

.gate__label {
  font-size: .6875rem;
  font-weight: 400;
  letter-spacing: .24em;
  text-transform: uppercase;
}

/* A cross drawn from two hairlines, in the page's own grammar. Both arms turn by the
   same +45°, so the mark rotates as one rigid object into an ×. Turning them by
   different amounts — which is what the first version did — reads as two unrelated
   lines flailing rather than one sign changing state. */
.gate__sign { position: relative; width: 11px; height: 11px; flex: none; }
.gate__sign::before,
.gate__sign::after {
  content: ''; position: absolute; top: 50%; left: 0;
  width: 100%; height: 1px;
  background: var(--amber);
  transition: transform 520ms var(--ease);
}
.gate__sign::before { transform: translateY(-50%) rotate(90deg); }
.gate__sign::after  { transform: translateY(-50%) rotate(0deg); }
.gate__trigger[aria-expanded="true"] .gate__sign::before { transform: translateY(-50%) rotate(135deg); }
.gate__trigger[aria-expanded="true"] .gate__sign::after  { transform: translateY(-50%) rotate(45deg); }

.gate__panel { overflow: hidden; text-align: center; }
.gate__panel[hidden] { display: none; }

.gate__inner {
  /* Slack at the foot. The panel clips its overflow and the last line was landing flush
     against that edge, so any sub-pixel shift shaved its descenders. */
  padding-bottom: 4px;
  margin-top: clamp(1rem, 2.5vh, 1.5rem);
}

/* Was `border-top` on .gate__inner. Now an element so the collapse can fade it with
   everything else; its margin-bottom carries the padding-top the wrapper used to hold,
   which keeps the resting spacing identical. */
.gate__rule {
  display: block;
  height: 1px;
  background: var(--rock);
  margin-bottom: clamp(1.1rem, 3vh, 1.75rem);
}

.gate__sub {
  max-width: 42ch;
  margin-inline: auto;
  color: var(--silver);
  font-size: clamp(.8125rem, 2.2vw, .9375rem);
  line-height: 1.7;
}

.gate__where {
  margin-top: clamp(1rem, 3vh, 1.75rem);
  font-size: .6875rem;
  font-weight: 400;
  letter-spacing: .22em;
  text-transform: uppercase;
  color: var(--silver-dim);
}

/* ---------------------------------------------------------------- the address */

.email { margin-top: clamp(1.25rem, 4vh, 2rem); }

/* The box collapses to the address so the rule beneath can match the word exactly.
   `fit-content` + `stretch` rather than an inline-block with a 100%-wide child: a
   percentage width inside a shrink-to-fit box is circular, and the browser resolves
   it against the wrong basis — that measured 225px against a 383px address. */
.email__wrap {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  width: fit-content;
  max-width: 100%;
  margin-inline: auto;    /* fit-content + auto margins centres it on the address */
}

/* The text face, matching the Request access control, rather than the display face.
   Size stays at display scale: brief §4.5 locks the address as the protagonist of this
   moment. Tracking is loosened from the display's -.015em — Space Grotesk is narrower
   and does not want negative tracking at this size. */
.email__link {
  display: inline-block;
  font-family: var(--grotesk);
  font-weight: 400;
  font-size: clamp(1.25rem, 5.6vw, 2.25rem);
  letter-spacing: 0;
  line-height: 1.15;
  text-decoration: none;
  color: var(--text);
  transition: color 420ms var(--ease);
  word-break: break-word;
}
.email__link:hover { color: var(--amber); }

/* Copy feedback: one amber rule, exactly as wide as the address, lighting for a second.
   The width comes from the inline-block wrapper collapsing to the link, so the rule
   measures the word rather than an arbitrary ch value. */
.email__rule {
  display: block;
  height: 2px;
  margin-top: .4rem;
  background: var(--amber);
  transform: scaleX(0); transform-origin: center;
  opacity: 0;
}
.email.is-copied .email__rule { animation: emailCopied 1200ms var(--ease) forwards; }
@keyframes emailCopied {
  0%   { transform: scaleX(0); opacity: 1; }
  28%  { transform: scaleX(1); opacity: 1; }
  76%  { transform: scaleX(1); opacity: 1; }
  100% { transform: scaleX(1); opacity: 0; }
}

.email__hint {
  margin-top: .875rem;
  font-size: .6875rem;
  font-weight: 400;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--silver-dim);
}

/* ---------------------------------------------------------------- form */

/* The form is the one centred block in an otherwise left-aligned panel. */
.access {
  margin: clamp(1.1rem, 3vh, 1.75rem) auto 0;
  max-width: 24rem;
  text-align: center;
}

.access__intro {
  max-width: 40ch;
  margin: 0 auto clamp(1rem, 2.8vh, 1.5rem);
  color: var(--silver);
  font-size: clamp(.8125rem, 2.1vw, .9375rem);
  line-height: 1.7;
}

/* Narrower than the intro text above it: an email field does not need the full
   measure, and the shorter box sits better under a centred column. */
.field { display: block; max-width: 19rem; margin-inline: auto; }
.field__row { position: relative; }

/* The same box as the gate trigger: a hairline in amber at 38% over a blurred dark
   ground. The two controls on this surface now read as one family. */
.field__input {
  width: 100%;
  /* 11px, not 10: at 10 the box measured 43px tall and a touch target wants 44. */
  padding: .6875rem 1.25rem;
  border: 1px solid rgba(200, 160, 74, .38);
  border-radius: 0;                     /* brutalism does not round */
  background: rgba(10, 10, 10, .5);
  backdrop-filter: blur(6px);
  color: var(--text);
  font-family: var(--grotesk);
  font-size: 1rem;                      /* 16px minimum — iOS zooms below this */
  font-weight: 300;
  /* Explicit, and repeated on the placeholder below. Left at `normal` the two produce
     different line boxes — 20.67px for the 16px value against 16.67px for the 13px
     placeholder — and the smaller text then aligns on the baseline inside the larger
     box, which lands it about two pixels below the optical centre. */
  line-height: 1.3rem;
  letter-spacing: .02em;
  text-align: center;
  transition: border-color 380ms var(--ease), background-color 380ms var(--ease);
}
/* Uppercase and tracked on the placeholder only. `text-transform` on the input itself
   would also uppercase whatever the visitor types, and an email address is not written
   that way. Its own smaller size keeps the input at 16px, which is what stops iOS
   zooming the page on focus. */
/* Painted out, not removed: it still carries the hint for assistive tech and autofill.
   Chrome honours only a subset of properties on ::placeholder — line-height is not one
   of them — so a 13px placeholder inside a 16px input can only align on the baseline
   and sits about two pixels below centre. The overlay below does the drawing. */
.field__input::placeholder { color: transparent; }

.field__hint {
  position: absolute; inset: 0;
  display: flex; align-items: center; justify-content: center;
  pointer-events: none;
  font-size: .8125rem;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--silver-dim);
  opacity: 0;
  transition: opacity 240ms var(--ease);
}
/* Only while the field is empty. It fades rather than blinking out on first keystroke. */
.field__input:placeholder-shown + .field__hint { opacity: 1; }

/* The border going full amber is the focus indicator, and it carries an inset ring so
   it reads at two pixels — a 38%-to-100% shift on one hairline is too quiet to be the
   only signal a keyboard user gets. */
.field__input:focus {
  outline: none;
  border-color: var(--amber);
  background: rgba(10, 10, 10, .68);
  box-shadow: inset 0 0 0 1px var(--amber);
}

.field.is-invalid .field__input {
  border-color: var(--amber-deep);
  box-shadow: inset 0 0 0 1px var(--amber-deep);
}

/* Honeypot. Off-canvas rather than display:none — some bots skip hidden inputs. */
.hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

.btn {
  position: relative;
  margin-top: clamp(1.25rem, 3.5vh, 1.75rem);
  display: inline-block;
  padding: 0 0 .5rem;
  cursor: pointer;
  font-size: .6875rem;
  font-weight: 400;
  letter-spacing: .22em;
  text-transform: uppercase;
  color: var(--silver);
  transition: color 320ms var(--ease);
}
.btn__rule {
  position: absolute; left: 0; bottom: 0;
  width: 100%; height: 1px;
  background: var(--rock);
  transition: background-color 320ms var(--ease);
}
.btn:hover:not(:disabled) { color: var(--amber); }
.btn:hover:not(:disabled) .btn__rule { background: var(--amber); }
.btn:disabled { color: var(--rock); cursor: default; }
.btn.is-busy { color: var(--silver-dim); }

.access__msg {
  margin-top: 1.25rem;
  min-height: 1.5em;
  font-size: .8125rem;
  line-height: 1.6;
  color: var(--silver);
}
.access__msg:empty { margin-top: 0; min-height: 0; }
.access__msg.is-error { color: var(--amber); }

/* Success replaces the form rather than stacking a toast on top of it. */
.access.is-done .access__intro,
.access.is-done .field,
.access.is-done .btn { display: none; }
.access.is-done .access__msg {
  margin-top: 0;
  font-family: var(--display);
  font-weight: 500;
  font-size: clamp(1rem, 2.8vw, 1.25rem);
  line-height: 1.35;
  letter-spacing: -.01em;
  color: var(--text);
}

/* ---------------------------------------------------------------- descend */

/* A plumb line down the right margin. Not a widget placed on the page but a full-height
   piece of the frame: the label set vertically so the word itself points down, and a
   weight falling the whole shaft on a slow loop. Depth is this brand's actual subject,
   and this is the drawing convention for it — architectural rather than interface.
   --silver, not --silver-dim: it sits where neither the left fall nor the vignette
   reaches, and the dim value measured 4.05:1 against the brightest frame. */
.descend {
  position: fixed; z-index: 55;
  right: 0;
  top: var(--nav-h);
  bottom: var(--foot-h);
  width: calc(var(--gutter) + 1.25rem);
  min-width: 2.75rem;                    /* 44px: the gutter alone is 40 on a phone */
  display: flex; flex-direction: column; align-items: center;
  gap: 1rem;
  padding: 1.25rem 0 1.75rem;
  cursor: pointer;
  color: var(--silver);
  transition: color 420ms var(--ease), opacity 600ms var(--ease);
}
.descend:hover { color: var(--amber); }

.descend__label {
  flex: none;
  font-family: var(--grotesk);
  font-size: .625rem;
  font-weight: 400;
  letter-spacing: .34em;
  text-transform: uppercase;
  writing-mode: vertical-rl;             /* the word itself points down */
  text-orientation: mixed;
}

.descend__shaft {
  flex: 1 1 auto;                        /* whatever height is left, down to the footer */
  width: 1px;
  min-height: 3rem;
  position: relative;
  overflow: hidden;
  background: var(--rock);
  transition: background-color 420ms var(--ease);
}
.descend:hover .descend__shaft { background: var(--smoke); }

.descend__shaft i {
  position: absolute; left: 0; top: 0;
  width: 100%; height: 26%;
  background: linear-gradient(to bottom, transparent, var(--amber));
  animation: plumbFall 4.6s var(--ease-slow) infinite;
}
@keyframes plumbFall {
  0%   { transform: translateY(-100%); opacity: 0; }
  10%  { opacity: 1; }
  82%  { opacity: 1; }
  100% { transform: translateY(385%); opacity: 0; }
}

.descend.is-gone { opacity: 0; pointer-events: none; }

/* ---------------------------------------------------------------- footer */

.foot {
  position: fixed; z-index: 55;
  left: 0; right: 0; bottom: 0;
  min-height: var(--foot-h);
  display: flex; flex-wrap: wrap; align-items: center;
  gap: .35rem clamp(1rem, 4vw, 2.5rem);
  padding: .75rem var(--gutter);
  padding-bottom: calc(.75rem + env(safe-area-inset-bottom));
  font-size: .6875rem;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--silver-dim);
}
.foot__mark {
  font-family: var(--display);
  font-weight: 800;
  letter-spacing: .2em;
  color: var(--silver);
  margin-right: auto;
}
.foot__link { text-decoration: none; transition: color 320ms var(--ease); }
.foot__link:hover { color: var(--amber); }

/* ---------------------------------------------------------------- sound */

/* A nav item now, sitting left of Projects, rather than the brief's bottom-right corner
   (§4.1). It carries the same type as its neighbours so the bar reads as one row; the
   dot is what marks it as a state rather than a destination. */
.sound {
  display: flex; align-items: center; gap: .5rem;
  padding: .25rem 0;
  cursor: pointer;
  font-family: var(--grotesk);
  font-size: .6875rem;
  font-weight: 400;
  letter-spacing: .22em;
  text-transform: uppercase;
  color: var(--silver);
  transition: color 320ms var(--ease);
}
.sound:hover { color: var(--amber); }
.sound__dot {
  width: 5px; height: 5px; border-radius: 50%;
  flex: none;
  background: var(--rock);
  transition: background-color 420ms var(--ease), box-shadow 420ms var(--ease);
}
.sound[aria-pressed="true"] { color: var(--amber); }
.sound[aria-pressed="true"] .sound__dot {
  background: var(--amber);
  box-shadow: 0 0 0 4px rgba(200, 160, 74, .14);
}

/* Four labelled items do not fit a phone bar — the word ran straight into the wordmark.
   Below this the control is the dot alone, which is the state anyway. The label is
   hidden rather than removed, so the button keeps its accessible name. */
@media (max-width: 30rem) {
  .sound__text {
    position: absolute; width: 1px; height: 1px;
    margin: -1px; padding: 0; border: 0;
    clip-path: inset(50%); overflow: hidden; white-space: nowrap;
  }
  .sound {
    gap: 0;
    min-width: 2.75rem; min-height: 2.75rem;   /* a real target around a 5px dot */
    justify-content: center;
  }
  /* Four items on a 360px bar leaves nothing between the wordmark and the first
     control. Tighter gaps and less tracking on the mark buy back the clearance. */
  .nav__links { gap: .625rem; }
  .nav__mark { letter-spacing: .12em; }
}

/* ---------------------------------------------------------------- consent */

.consent {
  position: fixed; z-index: 80;
  left: 0; right: 0; bottom: 0;
  display: flex; flex-wrap: wrap; align-items: center;
  gap: 1rem clamp(1rem, 4vw, 3rem);
  padding: 1.25rem var(--gutter);
  padding-bottom: calc(1.25rem + env(safe-area-inset-bottom));
  background: rgba(10, 10, 10, .96);
  border-top: 1px solid var(--rock);
  backdrop-filter: blur(8px);
}
.consent[hidden] { display: none; }

.consent__text {
  flex: 1 1 18rem; margin: 0;
  font-size: .75rem; line-height: 1.6;
  color: var(--silver);
}

.consent__actions { display: flex; gap: 1.5rem; }

/* Two buttons of equal weight. No inverted dark pattern. */
.consent__btn {
  padding: .5rem 0;
  border-bottom: 1px solid var(--rock);
  cursor: pointer;
  font-size: .6875rem;
  font-weight: 400;
  letter-spacing: .22em;
  text-transform: uppercase;
  color: var(--silver);
  transition: color 320ms var(--ease), border-color 320ms var(--ease);
}
.consent__btn:hover { color: var(--amber); border-color: var(--amber); }

/* ---------------------------------------------------------------- responsive */

@media (min-width: 33rem) {
  .sigil__name { display: block; }
}

/* The footer credit is the first thing to go when the bar gets tight. */
@media (max-width: 46rem) {
  .foot__credit { display: none; }
}

/* The amber cursor is a desktop enhancement and never gates interaction. */
@media (hover: none), (pointer: coarse) {
  .cursor { display: none; }
}

/* ---------------------------------------------------------------- reduced motion */

/* With motion suppressed the whole conceit is dropped: no threshold, no video, no
   scene machine. It becomes an ordinary scrolling page with everything present. */
@media (prefers-reduced-motion: reduce) {
  body { overflow: auto; }
  .threshold, .cursor, .descend { display: none; }
  .backdrop__v { display: none; }

  .stage { position: static; }
  .scene {
    position: static;
    opacity: 1 !important;
    visibility: visible !important;
    min-height: auto;
    padding-block: clamp(3rem, 10vh, 6rem);
    overflow: visible;
  }
  .scene:first-child { padding-top: calc(var(--nav-h) + 2rem); }
  .scene--chapters { padding-bottom: calc(var(--foot-h) + 3rem); }
  .foot { position: static; }
  .sound { position: static; margin: 0 auto 2rem; }

  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
  }
}
