/* VISION 04 — THE BLOCK IN CHORUS
   Layout, material and the static fallback.

   COLOUR AUTHORITY. Only two colours in this file are approved: the ink #081826
   and the paper #F5F2EA carried by the CWC assets themselves. Everything else is
   derived from them or is exploratory night material, and is labelled as such in
   the material notes. Nothing is locked.

   TYPOGRAPHY is deliberately unresolved — system stacks only. No typeface has
   been downloaded, because no permission to download one has been given.        */

:root {
  /* authoritative */
  --ink:    #081826;
  --paper:  #F5F2EA;

  /* derived night — the mineral midnight blue the block is read against */
  --night-0: #060c16;
  --night-1: #0a1422;
  --night-2: #101d2e;
  --night-3: #17273a;

  /* exploratory material light */
  --amber:     #ffb066;
  --amber-dim: #c9812f;
  --cool:      #8fb0dc;
  --terracotta:#c9603a;

  --text:     #f2efe6;
  --text-dim: #bcc6d1;
  --rule:     rgba(242,239,230,.16);

  --pad: clamp(1.25rem, 4vw, 3.5rem);
  --sans: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --mark-h-ratio: var(--cwc-h-positive-ratio, 2.2176);
  --mark-v-ratio: var(--cwc-v-positive-ratio, 0.48324);
}

* { box-sizing: border-box; }

html, body {
  margin: 0; padding: 0;
  background: var(--night-0);
  color: var(--text);
  font-family: var(--sans);
  -webkit-font-smoothing: antialiased;
}
/* the page must never scroll sideways at any width */
html, body { max-width: 100%; overflow-x: hidden; }

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

.skip {
  position: fixed; left: 1rem; top: -4rem; z-index: 99;
  background: var(--paper); color: var(--ink);
  padding: .85rem 1.15rem; border-radius: .35rem;
  font-weight: 650; font-size: .95rem; text-decoration: none;
  transition: top .16s ease;
}
.skip:focus-visible { top: 1rem; outline: 3px solid var(--amber); outline-offset: 3px; }

:where(a, button):focus-visible {
  outline: 3px solid var(--amber);
  outline-offset: 3px;
}

/* ── HERO ─────────────────────────────────────────────────────────────────── */

.hero {
  position: relative;
  min-height: 100svh;
  display: flex; flex-direction: column;
  isolation: isolate;
}
.stage { position: relative; flex: 1 1 auto; min-height: 100svh; }

.stage-gl {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  display: block;
  opacity: 0;
  transition: opacity .7s ease;
}
/* only once the renderer has actually produced a frame */
.stage-gl[data-live="1"] { opacity: 1; }

/* ── THE POSTER / STATIC FALLBACK ─────────────────────────────────────────────
   Present from first paint and never removed — only faded once a live frame
   exists. With JavaScript off, WebGL missing, or the context lost, this IS the
   hero: a layered CSS night street carrying the approved mark as a mask. */

.poster { position: absolute; inset: 0; overflow: hidden; transition: opacity .7s ease; }
.poster[data-hidden="1"] { opacity: 0; }

.poster-sky {
  position: absolute; inset: 0;
  background:
    linear-gradient(to bottom,
      var(--night-0) 0%, var(--night-1) 34%, var(--night-2) 56%, #1d2230 74%, #241f24 100%);
}
.poster-glow {
  position: absolute; left: 50%; top: 62%; width: 140%; height: 46%;
  transform: translate(-50%, -50%);
  background: radial-gradient(ellipse at center,
    rgba(255,166,92,.20) 0%, rgba(255,140,70,.09) 38%, transparent 72%);
}
/* an abstracted block of frontages, in the same material family as the scene */
/* An abstracted block of frontages: varied bay widths, a course of lit windows
   on each upper floor, and a brighter storefront band at street level — the same
   grammar the 3D scene is built from, at a fraction of the cost. */
.poster-block {
  position: absolute; left: 0; right: 0; top: 26%; height: 46%;
  background:
    /* frontages, at three widths so the rhythm is uneven like a real block */
    repeating-linear-gradient(90deg,
      rgba(48,25,20,.92) 0 3.1%, rgba(64,40,28,.92) 3.1% 5.4%,
      rgba(40,45,45,.92) 5.4% 8.0%, rgba(74,71,64,.88) 8.0% 10.2%,
      rgba(44,29,24,.92) 10.2% 13.6%),
    linear-gradient(to bottom, rgba(6,12,22,.10), rgba(6,12,22,.88));
  -webkit-mask-image: linear-gradient(to bottom, transparent 0%, #000 14%, #000 88%, rgba(0,0,0,.55) 100%);
          mask-image: linear-gradient(to bottom, transparent 0%, #000 14%, #000 88%, rgba(0,0,0,.55) 100%);
  opacity: .78;
}
/* Windows are the INTERSECTION of a column rhythm and a floor rhythm. A single
   90deg gradient gives continuous vertical stripes — a barcode, not a building —
   so the columns are painted and then cut into floors by a horizontal mask. */
.poster-block::after {
  content: ""; position: absolute; inset: 0;
  background:
    linear-gradient(to bottom, transparent 0 72%, rgba(255,183,112,.22) 72% 94%, transparent 94%),
    repeating-linear-gradient(90deg,
      transparent 0 1.15%, rgba(255,198,142,.34) 1.15% 1.95%, transparent 1.95% 3.1%);
  -webkit-mask-image: repeating-linear-gradient(to bottom,
      transparent 0 7%, #000 7% 14%, transparent 14% 21%);
          mask-image: repeating-linear-gradient(to bottom,
      transparent 0 7%, #000 7% 14%, transparent 14% 21%);
  mix-blend-mode: screen;
}
.poster-ground {
  position: absolute; left: -10%; right: -10%; bottom: 0; height: 34%;
  background:
    linear-gradient(to bottom, #241f24 0%, #14161f 40%, #0b1019 100%);
}
/* the pooled light: separate sources whose pools overlap on the pavement */
.poster-pool {
  position: absolute; left: 0; right: 0; bottom: 0; height: 36%;
  background:
    radial-gradient(60% 120% at 18% 12%, rgba(255,176,102,.30), transparent 62%),
    radial-gradient(52% 110% at 39% 4%,  rgba(255,190,120,.24), transparent 62%),
    radial-gradient(58% 120% at 62% 10%, rgba(255,168,96,.28),  transparent 62%),
    radial-gradient(46% 100% at 82% 2%,  rgba(143,176,220,.16), transparent 62%);
  mix-blend-mode: screen;
}

/* THE MARK. Approved geometry used as a mask — never redrawn, never stretched.
   `contain` against the ink ratio, so the silhouette is exact at every size. */
.poster-mark {
  position: absolute; left: 50%; top: 37.5%;
  transform: translate(-50%, -50%);
  width: min(74vw, calc(40svh * var(--mark-h-ratio)));
  aspect-ratio: var(--mark-h-ratio);
  -webkit-mask-image: var(--cwc-h-positive); mask-image: var(--cwc-h-positive);
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  -webkit-mask-position: center;   mask-position: center;
  -webkit-mask-size: contain;      mask-size: contain;
  background:
    linear-gradient(168deg, #ffd7a8 0%, #f0ab63 30%, #c9743a 62%, #8d4f2c 100%);
  filter: drop-shadow(0 18px 34px rgba(255,150,70,.20));
}

/* ── THE CROSSINGS (primary navigation) ──────────────────────────────────────
   Two paths meeting in the clearing. Real anchors, real text, always on the
   first screen, never dependent on the canvas. */

.crossings {
  position: absolute; left: 0; right: 0; bottom: clamp(2.5rem, 7svh, 5rem);
  z-index: 3;
  display: flex; justify-content: center;
  gap: clamp(1rem, 3vw, 2.75rem);
  padding: 0 var(--pad);
  flex-wrap: wrap;
}

.crossing {
  position: relative;
  display: flex; flex-direction: column; gap: .3rem;
  min-height: 44px; min-width: 44px;
  padding: 1rem 1.5rem 1.05rem;
  flex: 0 1 22rem;
  text-decoration: none; color: var(--text);
  border-radius: .2rem;
  background: linear-gradient(180deg, rgba(10,20,34,.62), rgba(6,12,22,.78));
  border: 1px solid rgba(242,239,230,.20);
  border-bottom: 2px solid rgba(255,176,102,.55);
  backdrop-filter: blur(3px);
  transition: background .22s ease, border-color .22s ease, transform .22s ease;
}
/* the light the crossing throws forward onto the pavement */
.crossing::after {
  content: ""; position: absolute; left: 6%; right: 6%; bottom: -14px; height: 16px;
  background: radial-gradient(ellipse at center, rgba(255,176,102,.42), transparent 72%);
  opacity: .5; transition: opacity .22s ease; pointer-events: none;
}
.crossing:hover { background: linear-gradient(180deg, rgba(20,34,52,.76), rgba(10,18,30,.86)); transform: translateY(-2px); }
.crossing:hover::after { opacity: 1; }
.crossing:active { transform: translateY(0); }
.crossing:focus-visible { outline: 3px solid var(--amber); outline-offset: 4px; border-color: rgba(255,176,102,.9); }
.crossing:focus-visible::after { opacity: 1; }

.crossing-label {
  font-size: clamp(1.02rem, 1.5vw, 1.22rem);
  font-weight: 680; letter-spacing: .045em; text-transform: uppercase;
  color: #fff8ef;
}
.crossing-sub { font-size: .875rem; line-height: 1.4; color: #cfd8e2; }

/* ── BELOW THE HERO ───────────────────────────────────────────────────────── */

.band { padding: clamp(3rem, 8vh, 6rem) var(--pad); max-width: 74rem; margin: 0 auto; }
.band-alt { background: var(--night-1); max-width: none; }
.band-alt > * { max-width: 74rem; margin-left: auto; margin-right: auto; }

.eyebrow {
  margin: 0 0 .7rem; font-size: .78rem; font-weight: 700;
  letter-spacing: .18em; text-transform: uppercase; color: var(--amber);
}
h2 { margin: 0 0 1rem; font-size: clamp(1.6rem, 3.4vw, 2.5rem); line-height: 1.15; letter-spacing: -.015em; }
.lede { margin: 0 0 1rem; font-size: clamp(1.02rem, 1.6vw, 1.16rem); line-height: 1.65; max-width: 48rem; color: #e8e4da; }
.note { margin: 0; color: var(--text-dim); line-height: 1.6; max-width: 46rem; font-size: .95rem; }

.manifesto { margin: 0 0 1.1rem; padding-left: 1.1rem; max-width: 46rem; }
.manifesto li { margin: .5rem 0; line-height: 1.6; color: #e4e0d6; }

/* the sample venue card — the factual shape every real entry will take */
.card {
  margin-top: clamp(1.75rem, 4vh, 2.75rem);
  padding: clamp(1.15rem, 3vw, 1.9rem);
  background: var(--night-2);
  border: 1px solid var(--rule);
  border-left: 3px solid var(--amber-dim);
  border-radius: .3rem; max-width: 44rem;
}
.card-flag {
  margin: 0 0 .8rem; font-size: .74rem; font-weight: 700;
  letter-spacing: .13em; text-transform: uppercase; color: #ffcf9a;
}
.card-venue { margin: 0 0 1rem; font-size: 1.3rem; letter-spacing: -.01em; }
.card-fields { margin: 0 0 1.1rem; display: grid; gap: .7rem; }
.card-fields > div { display: grid; grid-template-columns: 11.5rem 1fr; gap: .6rem; }
.card-fields dt { font-size: .82rem; font-weight: 700; letter-spacing: .07em; text-transform: uppercase; color: #b9c4d0; }
.card-fields dd { margin: 0; color: #e6e2d8; line-height: 1.5; }
.pill {
  display: inline-block; padding: .18rem .6rem; border-radius: 1rem;
  font-size: .8rem; font-weight: 650;
}
.pill-unverified { background: #4a2f14; color: #ffd39c; border: 1px solid #7a4f22; }
.card-confirm { margin: 0; font-weight: 650; color: #ffd7a8; }

/* ── OFFICIAL SOURCE — separate, never a navigation action ────────────────── */

.source {
  margin: 0; padding: clamp(2rem, 6vh, 3.5rem) var(--pad);
  background: var(--night-3);
  border-top: 1px solid var(--rule); border-bottom: 1px solid var(--rule);
}
.source > * { max-width: 74rem; margin-left: auto; margin-right: auto; }
.source-title {
  margin: 0 0 .6rem; font-size: .8rem; font-weight: 700;
  letter-spacing: .16em; text-transform: uppercase; color: #ffd7a8;
}
.source-body { margin: 0 0 .9rem; color: #dfe4ea; line-height: 1.6; max-width: 46rem; }
.source-link {
  display: inline-block; min-height: 44px; padding: .7rem 0;
  color: #ffd7a8; font-weight: 650; line-height: 1.7;
  text-decoration: underline; text-underline-offset: .22em; text-decoration-thickness: 2px;
}
.source-link:hover { color: #fff2e0; }
.disclaimer {
  margin: 1.1rem 0 0; padding-top: 1.1rem; border-top: 1px solid var(--rule);
  color: #cdd6e0; font-size: .92rem; line-height: 1.6; max-width: 52rem;
}

/* ── FOOT ─────────────────────────────────────────────────────────────────── */

.foot {
  padding: clamp(2rem, 6vh, 3.25rem) var(--pad) clamp(2.5rem, 7vh, 4rem);
  display: flex; flex-direction: column; gap: 1rem; align-items: flex-start;
}
.foot-mark {
  display: block; width: 92px; aspect-ratio: var(--mark-h-ratio);
  -webkit-mask-image: var(--cwc-h-positive); mask-image: var(--cwc-h-positive);
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  -webkit-mask-position: left center; mask-position: left center;
  -webkit-mask-size: contain; mask-size: contain;
  background: var(--paper);
}
.foot-sig {
  display: block; width: 168px; aspect-ratio: var(--cwc-sig-positive-ratio, 15.3461);
  -webkit-mask-image: var(--cwc-sig-positive); mask-image: var(--cwc-sig-positive);
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  -webkit-mask-position: left center; mask-position: left center;
  -webkit-mask-size: contain; mask-size: contain;
  background: #aeb8c4;
}
.foot-note { margin: 0; color: #9fabb8; font-size: .84rem; }

/* ── MOBILE PORTRAIT ──────────────────────────────────────────────────────────
   Not the desktop composition rotated or cropped. The APPROVED VERTICAL asset
   leads, the mark is rebuilt against its own geometry, and the two crossings
   become stacked thresholds so they stay immediately readable. */

@media (max-width: 767px) {
  .poster-mark {
    top: 33.5%;
    width: min(46vw, calc(40svh * var(--mark-v-ratio)));
    aspect-ratio: var(--mark-v-ratio);
    -webkit-mask-image: var(--cwc-v-positive); mask-image: var(--cwc-v-positive);
  }
  .poster-block { top: 34%; height: 38%; }
  .crossings { flex-direction: column; align-items: stretch; bottom: clamp(1.75rem, 5svh, 3rem); gap: .8rem; }
  .crossing { flex: 1 1 auto; padding: .95rem 1.15rem 1rem; }
  .crossing::after { bottom: -10px; height: 12px; }
  .card-fields > div { grid-template-columns: 1fr; gap: .15rem; }
  .foot-mark { width: 76px; }
}

/* ── REDUCED MOTION ───────────────────────────────────────────────────────────
   A still, fully aligned hero. No parallax, no automatic separation, no pulsing
   light, no camera travel — the renderer draws exactly one resolved frame and
   stops. Every action and all content stay exactly where they are. */

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important; animation-iteration-count: 1 !important;
    transition-duration: .001ms !important; scroll-behavior: auto !important;
  }
  .crossing:hover { transform: none; }
}


/* ── Desktop-only fallback backdrop (Adoption Pass 01) ────────────────────────
   Scoped to min-width: the mobile poster is the approved one, untouched.
   z-index is required because the poster layers are absolutely positioned
   siblings painting in DOM order; the mask stays above the backdrop. */

.poster-backdrop { display: none; }          /* mobile + default: never fetched */

@media (min-width: 768px) {
  .poster-backdrop {
    display: block;
    position: absolute; inset: 0;
    background: url("chorus-fallback-desktop.jpg") center 78%/cover no-repeat;
    z-index: 1;
  }
  .poster-mark { z-index: 2; }

  /* once a real street is behind the mark, the abstracted CSS layers are
     redundant — but ONLY on desktop, and only when the backdrop is present */
  .poster:has(.poster-backdrop) .poster-sky,
  .poster:has(.poster-backdrop) .poster-glow,
  .poster:has(.poster-backdrop) .poster-block,
  .poster:has(.poster-backdrop) .poster-ground,
  .poster:has(.poster-backdrop) .poster-pool { opacity: 0; }
}


/* ═══════════════════════════════════════════════════════════════════════════
   NAVIGATION SURFACE STUDY 01 · VARIANT A — STOREFRONT THRESHOLD
   Appended block. Nothing above this line was edited. Deleting from the ruler
   above to the end of the file returns this file to the frozen PC01 source
   (fb81f6b2710ac1958afeef73d44a38ce75b692dfed27b305c34fba0864dda19a).

   THE ARGUMENT. Each action is an abstract storefront opening at the edge of
   the shared clearing: dark imperfect glass in an oxidised-metal jamb, standing
   on a limestone sill, with a lit transom overhead and one interior-light seam
   at its foot. Nothing beyond the glass is drawn — the surface only has to be
   the kind of surface that has something behind it.

   COLOUR AUTHORITY. Every colour below is the renderer's own. The seven surface
   materials in chorus-gl.js §materialColour (lines 82-88) are taken at their
   published linear albedo and put through the renderer's own night lighting
   term (line 379):

       night = albedo × (sky(0.070,0.098,0.148)·1.35 + key(0.128,0.104,0.086)·0.62)
             = albedo × (0.17386, 0.19678, 0.25312)

   which yields, in sRGB:
       storefront glass  #3C3C42      oxidised metal  #424E57
       limestone         #585C64      red brick       #462F2F
       painted wood      #464F59      terracotta      #56433F

   These are then mixed toward the card's own existing field colour --night-1
   (#0a1422). No colour is invented; the card is literally the scene's material
   under the scene's light. Derivation and arithmetic:
   documentation/MATERIAL_CONTINUITY_PRINCIPLES.md.

   WHAT THIS BLOCK MAY NOT TOUCH, and does not:
     card position · card dimensions · card spacing · target size · href · DOM ·
     tab order · the ::after pooled forward light · the focus outline's colour,
     width, style and offset · the -2px hover lift · the copy.
   ═══════════════════════════════════════════════════════════════════════════ */

.crossings {
  /* the two thresholds are one system: a shared token set, biased per surface */
  --a-glass:  #3C3C42;   /* storefront glass, night-lit  */
  --a-metal:  #424E57;   /* oxidised metal, night-lit    */
  --a-lime:   #585C64;   /* limestone, night-lit         */
  --a-seam:   255, 176, 102;   /* --amber, as components  */
  --a-jamb:   rgba(150, 168, 176, .26);  /* metal catching the street */
  --a-sill:   rgba(150, 156, 162, .44);
  --a-transom: .195;    /* warm side; the cool side runs lower — see below */
  --a-hairline: .38;
}

.crossing {
  /* ── the field ──────────────────────────────────────────────────────────
     Opaque enough that nothing behind can reach the text, translucent enough
     that the glass is a real surface in the same air as the scene. Replaces
     the frozen linear-gradient(rgba(10,20,34,.62), rgba(6,12,22,.78)). */
  background-color: rgba(18, 26, 39, .93);   /* mix(--night-1, glass 16%) */

  /* ── the layers, painted top-most first ─────────────────────────────────
     1  LINTEL    the hairline the transom is glazed under. Without it the band
                  above reads as a ruled strip of interface chrome rather than
                  as a piece of the building — this was the first version's
                  actual failure, seen in the captures and corrected here.
     2  MULLIONS  the transom's vertical divisions. Six lights across a 352px
                  opening, floated 2px clear of the card's own top edge so they
                  never touch it and never become tick marks.
     3  TRANSOM   the lit glazed band itself, 9px, entirely inside the top
                  padding: the type begins at 17px.
     4  SILL      the limestone the opening stands on, 3px at the foot, with
                  its own shadow line above so it projects instead of printing.
     5  SEAM      one hairline of interior light immediately above the sill.
     6  GLASS     two broad, shallow, differently-angled sheets. Old glass is
                  never flat; this is that and nothing more.                */
  background-image:
    /* 1 lintel */
    linear-gradient(90deg, rgba(150, 156, 162, .34), rgba(150, 156, 162, .20)),
    /* 2 mullions */
    repeating-linear-gradient(90deg,
      transparent 0 57px, rgba(214, 226, 232, .16) 57px 58px, transparent 58px 115px),
    /* 3 transom bleed */
    linear-gradient(180deg, rgba(var(--a-seam), var(--a-transom)),
                            rgba(var(--a-seam), calc(var(--a-transom) * .18))),
    /* 4 sill, and the shadow it casts */
    linear-gradient(180deg, rgba(10, 16, 26, .55) 0 1px, var(--a-sill) 1px 4px),
    /* 5 interior-light seam */
    linear-gradient(90deg, rgba(var(--a-seam), .00), rgba(var(--a-seam), var(--a-hairline)) 22%,
                           rgba(var(--a-seam), var(--a-hairline)) 78%, rgba(var(--a-seam), .00)),
    /* 6 imperfect glass */
    linear-gradient(101deg, rgba(143, 176, 220, .045) 0 24%, transparent 52%),
    linear-gradient(258deg, rgba(255, 215, 168, .035) 0 30%, transparent 58%);
  background-repeat: no-repeat;
  background-size:
    100% 1px,          /* lintel      */
    100% 5px,          /* mullions    */
    100% 9px,          /* transom     */
    100% 4px,          /* sill        */
    100% 1px,          /* seam        */
    100% 100%,
    100% 100%;
  background-position:
    0 9px,             /* lintel under the transom  */
    0 2px,             /* mullions clear of the edge */
    0 0,
    0 100%,            /* sill at the foot          */
    0 calc(100% - 4px),/* seam directly above it    */
    0 0, 0 0;

  /* ── the jamb ───────────────────────────────────────────────────────────
     One inset reveal inside the frame, so the opening has a thickness. This
     is the only depth on the surface and it costs one inset box-shadow. */
  border: 1px solid var(--a-jamb);
  border-bottom: 2px solid rgba(255, 176, 102, .55);   /* THE FROZEN VALUE, verbatim,
                                                          identical on both surfaces */
  box-shadow: inset 0 0 0 1px rgba(6, 12, 22, .55);

  /* square. A threshold has an arris, not a corner radius. */
  border-radius: 0;

  /* the frozen surface used backdrop-filter: blur(3px). This study forbids it,
     so it is switched off and the readability it was buying is bought instead
     by the field's own opacity above. */
  backdrop-filter: none;
  -webkit-backdrop-filter: none;

  /* only the field colour and the frame move on interaction: no gradient is
     interpolated, no filter is animated, nothing is repainted that was not
     already being repainted by the frozen surface. */
  transition: background-color .22s ease, border-color .22s ease,
              box-shadow .22s ease, transform .22s ease;

  /* ── spacing, rebalanced at constant height ─────────────────────────────
     padding-top + row-gap + padding-bottom is held at the frozen 37.6px
     (desktop/tablet) so the card box does not move or resize:
        frozen 16.0 + 4.8 + 16.8 = 37.6
        here   16.0 + 8.8 + 12.8 = 37.6
     The title gets its air from the subtitle's foot, not from the box.      */
  gap: 8.8px;
  padding: 1rem 1.5rem .8rem;
}

/* the two states inherit everything above and differ only in exposure —
   the warm side is a frontage with its lights on, the cool side one lit from
   the street. Same construction, same value, same weight in the composition. */
.crossing[data-crossing="0"] { background-color: rgba(19, 25, 36, .93); }  /* toward glass  */
.crossing[data-crossing="1"] { background-color: rgba(18, 27, 40, .93); }  /* toward metal  */
/* the cool bias reaches the interior only: the transom's bleed, the interior-light
   hairline and the jamb. It never reaches the card's bottom edge.

   The two alphas are NOT equal, and that is the point. A cool tint reads far more
   strongly than a warm one against a warm-dark field, so matching the numbers gave
   a visibly blue band beside a nearly invisible amber one — a difference in
   PRESENCE dressed up as a difference in temperature. Matched by eye at 1:1 over
   the photographic fallback, which is where the disparity was worst. */
.crossing[data-crossing="1"] {
  --a-seam: 143, 176, 220;
  --a-jamb: rgba(156, 172, 176, .26);
  --a-transom: .115;
  --a-hairline: .30;
}

/* ── THE CORRECTION THE FALLBACK CAPTURES FORCED ─────────────────────────────
   The first build biased --*-seam per surface, and --*-seam also fed the frozen
   `border-bottom: 2px rgba(255,176,102,.55)`. So the cool card's bottom edge came
   out BLUE against the warm card's AMBER — the most prominent 2px on either
   surface, and by a long way the first thing the eye found. That is not the
   "restrained differentiation" §5 authorises; it reads as two different kinds of
   card, and it silently changed a frozen value.

   The frozen amber seam is now hard-coded identically on both surfaces, and the
   temperature bias is confined to the interior layers — the transom bleed, the
   interior-light hairline, the jamb — where it is atmosphere rather than an edge.
   Seen in captures/*-desktop-webgl-unavailable.png, corrected, re-shot.
   ──────────────────────────────────────────────────────────────────────────── */


.crossing:hover { background-color: rgba(25, 34, 48, .94); }
.crossing[data-crossing="1"]:hover { background-color: rgba(24, 35, 50, .94); }
.crossing:hover,
.crossing:focus-visible { box-shadow: inset 0 0 0 1px rgba(6, 12, 22, .38); }

/* ── TYPOGRAPHY ─────────────────────────────────────────────────────────────
   The question becomes the largest thing on the surface, and the line box it
   occupies does not change. font-size rises ~15%; line-height is pinned to the
   frozen box (23px desktop, 19px tablet and mobile) with the same 1.5vw curve
   the frozen font-size used, scaled by the system font's normal ratio:
       frozen box ≈ clamp(1.02rem, 1.5vw, 1.22rem) × 1.1765
                  = clamp(19px,  1.7647vw, 23px)
   so the card's height is arithmetically unchanged at every width.          */
.crossing-label {
  font-size: clamp(1.16rem, 1.70vw, 1.40rem);
  line-height: clamp(19px, 1.7647vw, 23px);
  font-weight: 720;
  letter-spacing: .052em;
}
.crossing-sub {
  /* untouched metrics — the wrap is what fixes the card's height, so it is
     the one thing in the type that must not be tuned. Colour is unchanged. */
  font-size: .875rem; line-height: 1.4;
}

@media (max-width: 767px) {
  /* mobile holds the same identity: 15.2 + 8.8 + 12.0 = 36.0 = frozen 15.2 + 4.8 + 16.0 */
  .crossing { gap: 8.8px; padding: .95rem 1.15rem .75rem; }
  .crossing-label { font-size: 1.15rem; }
  /* the transom is shallower on a 77.6px card, and the mullions widen so the
     rhythm stays a rhythm rather than a screen */
  .crossing {
    background-size: 100% 1px, 100% 4px, 100% 8px, 100% 4px, 100% 1px, 100% 100%, 100% 100%;
    background-position: 0 8px, 0 2px, 0 0, 0 100%, 0 calc(100% - 4px), 0 0, 0 0;
    background-image:
      linear-gradient(90deg, rgba(150, 156, 162, .34), rgba(150, 156, 162, .20)),
      repeating-linear-gradient(90deg,
        transparent 0 86px, rgba(214, 226, 232, .16) 86px 87px, transparent 87px 173px),
      linear-gradient(180deg, rgba(var(--a-seam), var(--a-transom)),
                              rgba(var(--a-seam), calc(var(--a-transom) * .18))),
      linear-gradient(180deg, rgba(10, 16, 26, .55) 0 1px, var(--a-sill) 1px 4px),
      linear-gradient(90deg, rgba(var(--a-seam), .00), rgba(var(--a-seam), var(--a-hairline)) 22%,
                             rgba(var(--a-seam), var(--a-hairline)) 78%, rgba(var(--a-seam), .00)),
      linear-gradient(101deg, rgba(143, 176, 220, .045) 0 24%, transparent 52%),
      linear-gradient(258deg, rgba(255, 215, 168, .035) 0 30%, transparent 58%);
  }
}


/* ═══════════════════════════════════════════════════════════════════════════
   ESPAÑOL 3D ENTRY STUDY 01 · THE LANGUAGE GATEWAY
   Appended block. Nothing above this line was edited, including the
   NAVIGATION SURFACE STUDY 01 · VARIANT A block it follows — the selected
   cards are carried through untouched and are not this study's to reinterpret.
   Deleting from the ruler above to the end of the file returns this file to
   the A — STOREFRONT THRESHOLD source exactly.

   WHAT THIS BLOCK IS RESPONSIBLE FOR, AND WHAT IT IS NOT.

   It is NOT responsible for drawing the object. The object is geometry, in
   WebGL, at 33 units. Everything here belongs to the CONTROL: where it sits,
   how big its target is, what it looks like when the renderer is absent, and
   what focus looks like. If this block were deleted and the JavaScript kept,
   the object would still be drawn and would still be at the right place; the
   link over it would simply be unpositioned. That division is deliberate — it
   is what stops the gateway from being a styled box with a picture in it.

   Every position below is set by espanol-bootstrap.js from the same derivation
   the geometry uses, as inline left/top/width/height. There are no breakpoint
   rules for placement here, because placement is derived from the mark rect
   and not from a media query. The one media query present switches a hover
   affordance off on coarse pointers.                                          */

.espanol {
  position: absolute;
  z-index: 4;                 /* above the poster, below nothing that matters */
  display: block;
  min-width: 44px; min-height: 44px;
  text-decoration: none;
  -webkit-tap-highlight-color: transparent;

  /* ── THE PRE-SCRIPT POSITION ────────────────────────────────────────────
     espanol-bootstrap.js sets left/top/width/height inline from the same
     derivation the geometry uses, and does it before the first live frame.
     These values are what stands when it never runs at all — JavaScript
     disabled — and they are therefore the ONLY position the no-JS visitor
     ever sees. That makes them worth deriving rather than eyeballing.

     A first version used right:4.2vw and landed the object 177 px right of
     where the renderer puts it at 1440 x 900 — the no-JS page and the live
     page were showing the same object in different places, and only the live
     one had been looked at.

     Derived instead, for the height-limited case (viewport aspect > 1.198,
     which is every ordinary desktop). markRectFor contain-fits ratio 2.2176
     into 74% x 40% centred at 50% / 37.5%, so:

         markRect.h = 0.40vh        markRect.w = 0.8870vh
         markRect.x = 0.5vw - 0.4435vh

     and this study's four ratios give

         left  = markRect.x + markRect.w - 0.023438 * markRect.h
               = 50vw + 43.462svh
         top   = markRect.y - 0.027344 * markRect.h  = 16.406svh
         h     = 0.91797 * markRect.h                = 36.7188svh
         w     = 0.27660 * h                         = 10.1565svh

     Checked against the live values at 1440 x 900: left 1110.7, top 147.7,
     91.4 x 330.5 — the same numbers the renderer computes, to a tenth of a
     pixel. Below aspect 1.198 the mark becomes width-limited and these drift;
     that is a no-JS page on an unusually tall desktop window, the object is
     still on screen and still clear of both primary actions, and the cost of
     covering it is a second set of constants nobody can check. */
  left: calc(50vw + 43.462svh);
  top: 16.406svh;
  width: 10.1565svh;
  height: 36.7188svh;
  right: auto;
}

/* The phone branch of the same derivation. The mark is the vertical asset,
   contain-fitted at ratio 0.48324 into 46% x 40% centred at 50% / 33.5%, and
   the object takes 0.60 of its height and sits in the clear night field
   outside its right edge:  top = 15.9svh, h = 24svh, w = 6.6384svh. */
@media (max-width: 767px) {
  .espanol {
    left: auto;
    right: max(14px, 4.1vw);
    top: 15.9svh;
    width: 6.6384svh;
    height: 24svh;
  }
}

/* THE AUTHORED STILL.
   Present from first paint and never removed — only faded once the BEACON has
   compiled and uploaded, which is not the same condition as the renderer being
   live and the difference is not academic. During this study a reserved
   identifier in the fragment shader killed the beacon on both directions while
   the scene carried on perfectly; keyed on canvas[data-live] the still layer
   faded out anyway and the object disappeared from the page entirely.

   So the selector requires [data-beacon="live"], which espanol-bootstrap.js
   sets only after a successful compile. The safe state is the DEFAULT: with no
   attribute — script blocked, file missing, exception thrown, WebGL absent,
   context lost, governor demoted, JavaScript switched off — the authored
   drawing stays and there is no state in which ESPAÑOL is missing. */
.espanol-still {
  position: absolute;
  left: var(--esp-inset-x, 0px); right: var(--esp-inset-x, 0px);
  top: 0; bottom: 0;
  transition: opacity .7s ease;
  pointer-events: none;
}
.espanol-still svg { display: block; width: 100%; height: 100%; overflow: visible; }
/* THE STILL'S SUPPORT, BY VIEWPORT. See the note in index.html: the desktop
   corner is corrected, the mobile one is Refinement 04's and is switched in
   here by the same breakpoint place() uses to choose its mobile branch. */
.esp-sup-m { display: none; }
@media (max-width: 767px) { .esp-sup-d { display: none; } .esp-sup-m { display: block; } }

/* THE FALLBACK'S LETTERS ARE MOVED TO WHERE THE RENDERER PUTS THEM.
   The authored still draws the object in the frame's plane. The live object's
   letters are not in that plane — they are on a surface recessed 0.55 world
   units behind it, so the projection lands them about 7 CSS px toward the
   vanishing point and 1.6% smaller at 1440 x 900. Without this the two
   representations of the same object disagree by half a stroke width, and the
   crossfade between them would visibly slide.

   espanol-bootstrap.js sets these three from place().projected, computed from
   the real geometry rather than from a constant, so they stay correct at a
   viewport nobody has looked at. The fallbacks make an un-scripted page draw
   the letters in the frame's plane, which is the right thing to do when there
   is no projection to agree with. */
.espanol-still .esp-letters {
  transform: translate(calc(var(--esp-let-dx, 0) * 1px), calc(var(--esp-let-dy, 0) * 1px))
             scale(var(--esp-let-k, 1));
  transform-origin: 50% 50%;
  transform-box: fill-box;
}
.stage-gl[data-live="1"] ~ .espanol[data-beacon="live"] .espanol-still { opacity: 0; }

/* ── HOVER AND KEYBOARD FOCUS ────────────────────────────────────────────────
   The live states are drawn in the shader: interior light up, edges defined.
   This block adds the two things the shader cannot be trusted with.

   FIRST, the focus ring. It is amber at 3px offset 3, which is not a new
   focus style — it is the SAME ring :where(a, button):focus-visible already
   gives every other control on this page, and this control is not special
   enough to deserve a different one. What makes it belong to the object is
   that the element it draws around IS the object's silhouette, kept registered
   to it every frame. Measured 10.6:1 against the night sky it sits in.

   SECOND, it is drawn whether or not WebGL is alive. A focus indicator that
   only exists inside the renderer is a focus indicator that disappears exactly
   when the page is already degraded. */
.espanol:focus-visible {
  outline: 3px solid var(--amber);
  outline-offset: 3px;
  border-radius: 2px;
}
/* the still layer answers hover and focus too, since on the fallback paths it
   is the only thing on screen — same two changes, no others */
.espanol[data-state="on"] .espanol-still,
.espanol:hover .espanol-still,
.espanol:focus-visible .espanol-still { filter: brightness(1.18) contrast(1.04); }

/* Pointers that cannot hover get no hover affordance, and lose nothing:
   discovery never depended on hover in the first place — the object is lit and
   legible at rest, which is the whole reason it is lit at rest. */
@media (hover: none) {
  .espanol:hover .espanol-still { filter: none; }
}

/* Reduced motion: the still layer's fade is the only transition here, and it
   is the same .7s the poster uses. Removed, not shortened — with reduced
   motion the final stable composition is what should be on screen, and a
   crossfade into it is still a motion. */
@media (prefers-reduced-motion: reduce) {
  .espanol-still { transition: none; }
}

