/* ------------------------------------------------------------------
   the void, landing
   colours and timings are lifted from the redesign doc, turn 2 (2a/2b)
   ------------------------------------------------------------------ */

:root {
  /* No hue anywhere: white, and greys on the way down to the dark.
     Anything that used to be cyan or violet is now a level of light. */
  --white: 255, 255, 255;
  --bone: 233, 228, 214;   /* the carved marks; matches the plates */
  --grey: 168, 168, 172;
  --grey-dim: 108, 108, 112;

  --ink-cold: #020204;
  --ink-lit: #030306;

  --mono: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
  --serif: "Cormorant Garamond", Georgia, serif;

  /* written by js/void.js. centre of the web, in px */
  --cx: 50vw;
  --cy: 50vh;
}

* { box-sizing: border-box; }

html, body {
  height: 100%;
  margin: 0;
  overflow: hidden;
}

body {
  background: var(--ink-cold);
  font-family: var(--mono);
  color: rgba(var(--bone), .8);
  transition: background 2.4s ease;
  -webkit-font-smoothing: antialiased;
}

body[data-state="lit"] { background: var(--ink-lit); }

.void {
  position: fixed;
  inset: 0;
  overflow: hidden;
  cursor: crosshair;
}

/* a return visit starts lit; don't let the reveal play on load */
body.is-preload,
body.is-preload * {
  transition: none !important;
  animation-duration: 0s !important;
}

/* ---------- ambient ---------- */

.field {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}

/* The two .smoke layers that used to sit here were full-viewport
   filter: blur(64px) / blur(78px) elements with mix-blend-mode: screen,
   animated forever. That combination forces the compositor to
   re-rasterise a huge blurred layer every frame and disables its fast
   paths outright in Firefox. The same swells are now painted into the
   field canvas at a third resolution; see swell() in js/field.js. */

/* the cursor is a lamp; nothing exists outside it */
.lamp {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: radial-gradient(circle var(--lamp-size, 230px) at var(--lx, 50%) var(--ly, 50%),
              var(--lamp-tint, rgba(var(--bone), .055)), transparent 72%);
}

/* ---------- the web ---------- */

.web {
  position: absolute;
  inset: 0;
  pointer-events: none;
  transform-origin: var(--cx) var(--cy);
  -webkit-mask-image: radial-gradient(circle at var(--cx) var(--cy), #000 62%, transparent 96%);
          mask-image: radial-gradient(circle at var(--cx) var(--cy), #000 62%, transparent 96%);
  transition: transform 1.9s cubic-bezier(.19, 1, .22, 1), opacity 1.7s ease .1s;
}

body[data-state="cold"] .web {
  opacity: 0;
  transform: scale(.55) rotate(-8deg);
}
body[data-state="lit"] .web {
  opacity: 1;
  transform: none;
}

.web__svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
  /* no filter here on purpose: a blur on this element made every
     pointer move re-rasterise a full-viewport filtered layer of 48
     paths, for a third of a pixel of softening */
}

.web__svg path { fill: none; stroke-linecap: round; }

.web__dots { position: absolute; inset: 0; }

.web__dot {
  position: absolute;
  width: 5px;
  height: 5px;
  transform: rotate(45deg);
  pointer-events: none;
}

/* ---------- the centre ---------- */

.centre {
  position: absolute;
  left: var(--cx);
  top: var(--cy);
  width: min(420px, 84vw);
  /* --cs is written by js/void.js and tracks the ring scale */
  transform: translate(-50%, -50%) scale(var(--cs, 1));
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  pointer-events: none;
  /* --op is proximity (fast); opacity is the reveal (slow) */
  filter: blur(calc((1 - var(--op, 1)) * 3.4px))
          drop-shadow(0 0 26px rgba(var(--white), calc(var(--op, 1) * .22)))
          opacity(var(--op, 1));
  transition: opacity 1.6s ease .55s,
              transform 1.6s cubic-bezier(.19, 1, .22, 1) .55s,
              filter .5s ease;
}

body[data-state="cold"] .centre {
  opacity: 0;
  transform: translate(-50%, -50%) scale(calc(var(--cs, 1) * .8));
}
body[data-state="lit"] .centre { opacity: 1; }

.centre__speak {
  background: none;
  border: none;
  padding: 8px 14px;
  cursor: pointer;
  pointer-events: auto;   /* the one live thing inside the centre */
  transition: filter .5s ease;
}
.centre__speak:hover,
.centre__speak:focus-visible {
  filter: brightness(1.45) drop-shadow(0 0 22px rgba(var(--white), .5));
}
.centre__speak:focus-visible { outline: 1px solid rgba(var(--white), .6); outline-offset: 6px; }

.centre__mark {
  display: block;
  width: 58px;
  height: 78px;
  fill: none;
  stroke: #FFFFFF;
  stroke-width: 2;
  stroke-linecap: round;
}

.centre__title {
  font-family: var(--serif);
  font-weight: 300;
  font-size: clamp(17px, 2.1vw, 23px);
  letter-spacing: .62em;
  /* letter-spacing pushes the last letter off-centre; pull it back */
  text-indent: .62em;
  color: rgba(var(--bone), .62);
  margin: 26px 0 0;
}

.centre__hint {
  font-size: 9px;
  letter-spacing: .3em;
  color: rgba(var(--bone), .24);
  margin: 20px 0 0;
  transition: opacity 1.4s ease 1.6s;
}

body[data-state="cold"] .centre__hint { opacity: 0; transition-delay: 0s; }

/* ---------- cold start: the first thing ---------- */

.seed {
  position: absolute;
  left: var(--sx, 70%);
  top: var(--sy, 29%);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  padding: 14px;
  background: none;
  border: none;
  font-family: var(--mono);
  color: rgba(var(--bone), var(--op, 0));
  transform: translate(-50%, -50%) scale(1);
  filter: blur(calc((1 - var(--op, 0)) * 5px))
          drop-shadow(0 0 calc(var(--op, 0) * 16px) rgba(var(--bone), calc(var(--op, 0) * .5)));
  transition: color .9s ease,
              transform 1.4s cubic-bezier(.19, 1, .22, 1),
              filter .45s ease,
              opacity .9s ease;
}

.seed[data-near="1"] { cursor: pointer; }

.seed__mark {
  width: 26px;
  height: 34px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.seed__label {
  font-size: 8.5px;
  letter-spacing: .3em;
  opacity: 0;
  transition: opacity .5s ease;
}

.seed[data-close="1"] .seed__label { opacity: .75; }

body[data-state="lit"] .seed {
  opacity: 0;
  transform: translate(-50%, -50%) scale(1.6);
  pointer-events: none;
}

/* ---------- the circles ---------- */

.orbit { position: absolute; inset: 0; pointer-events: none; }

.node {
  position: absolute;
  left: var(--nx);
  top: var(--ny);
  transform: translate(-50%, -50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 9px;
  padding: 10px 14px;
  background: none;
  border: none;
  font-family: var(--mono);
  text-align: center;
  transition: color .35s ease, filter .45s ease;
}

.node__mark { fill: none; stroke: currentColor; stroke-linecap: round; stroke-linejoin: round; }
.node__label { white-space: nowrap; }
.node__meta { white-space: nowrap; opacity: .55; }

/* second circle, reachable */
.node--2 {
  color: rgba(var(--white), var(--op, 0));
  filter: blur(calc((1 - var(--near, 0)) * 4.2px));
  text-shadow: 0 0 calc(10px + var(--near, 0) * 16px) rgba(var(--white), calc(var(--near, 0) * .3));
  pointer-events: none;
}
.node--2[data-reachable="1"] { pointer-events: auto; cursor: pointer; }
.node--2 .node__mark { width: 22px; height: 29px; stroke-width: 2; }
.node--2 .node__label { font-size: 9.5px; letter-spacing: .26em; }
.node--2 .node__meta { font-size: 8.5px; letter-spacing: .18em; }

/* third circle, shrouded, not yet */
.node--3 {
  color: rgba(var(--bone), var(--op, 0));
  opacity: .9;
  filter: blur(calc(1.6px + (1 - var(--near, 0)) * 4px));
  text-shadow: 0 0 calc(10px + var(--near, 0) * 18px) rgba(var(--white), calc(var(--near, 0) * .35));
  pointer-events: none;
}
.node--3 .node__mark { width: 20px; height: 26px; stroke-width: 1.6; }
.node--3 .node__label { font-size: 9px; letter-spacing: .26em; }
.node--3 .node__meta { font-size: 8px; letter-spacing: .2em; }

body[data-state="cold"] .node { opacity: 0; pointer-events: none; }

.whisper {
  position: absolute;
  left: 0;
  right: 0;
  top: calc(var(--cy) + var(--r2, 250px));
  margin: 0;
  text-align: center;
  font-family: var(--serif);
  font-style: italic;
  font-size: 16px;
  letter-spacing: .3em;
  color: rgba(var(--grey), 0);
  pointer-events: none;
  transition: color 1.6s ease 1.1s;
}

body[data-state="lit"] .whisper { color: rgba(var(--grey), .42); }

/* ---------- a page, cut out of the dark ---------- */

.page {
  position: absolute;
  inset: 0;
  overflow: hidden;
  /* a wash, not a lid. the field canvas keeps drifting underneath */
  /* The vignette is in the gradient's outer stop rather than in an
     inset box-shadow. Chrome does not composite a clip-path animation
     on a basic shape, so all 57 frames of the circle opening repaint
     the full viewport, and a 160px shadow blur around the perimeter was
     the expensive half of that repaint. This lands on the exact moment
     somebody is watching the circle expand. */
  background:
    radial-gradient(130% 105% at 50% -10%, rgba(11, 11, 20, .82) 0%, rgba(6, 6, 12, .9) 42%, rgba(2, 2, 5, .985) 100%);
  clip-path: circle(var(--clip, 26px) at var(--ox, 50%) var(--oy, 50%));
  opacity: .2;
  transition: clip-path .95s cubic-bezier(.22, 1, .36, 1), opacity .5s ease;
}

.page[data-open="1"] { --clip: 160vmax; opacity: 1; }

/* The wash lets the field canvas through for ambience. The web,
   the centre and the circles would come with it, and you'd be reading
   the bench through his sigil. Everything except the fog steps back. */
body[data-page-open="1"] .web,
body[data-page-open="1"] .centre,
body[data-page-open="1"] .orbit,
body[data-page-open="1"] .whisper,
body[data-page-open="1"] .lamp,
body[data-page-open="1"] .prefs {
  opacity: 0 !important;
  pointer-events: none !important;
  transition: opacity .45s ease !important;
}

/* Where you are and how you leave, in one mark. The lit ring is the
   circle you're standing in; pressing it takes you back to the centre. */
.depth {
  position: absolute;
  left: 26px;
  top: 22px;
  width: 40px;
  height: 40px;
  padding: 0;
  background: none;
  border: none;
  cursor: pointer;
  z-index: 2;
}

.depth__rings { width: 40px; height: 40px; overflow: visible; }

.depth__ring {
  fill: none;
  stroke: rgba(var(--white), .12);
  stroke-width: 1;
  transition: stroke .35s ease, stroke-width .35s ease;
}

.depth__core {
  fill: rgba(var(--white), .3);
  transition: fill .35s ease;
}

/* the circle you are in */
.depth[data-level="1"] .depth__ring[data-ring="1"],
.depth[data-level="2"] .depth__ring[data-ring="2"],
.depth[data-level="3"] .depth__ring[data-ring="3"] {
  stroke: rgba(var(--white), .92);
  stroke-width: 1.4;
}

.depth:hover .depth__ring { stroke: rgba(var(--white), .3); }
.depth:hover .depth__core { fill: rgba(var(--white), .85); }

/* on hover the innermost ring brightens; that is where you're going */
.depth:hover .depth__ring[data-ring="1"] { stroke: rgba(var(--white), .8); }

.page__body {
  position: absolute;
  left: 0;
  right: 0;
  top: 84px;
  bottom: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
}

.page__soon {
  display: block;
  margin: 22vh auto 0;
  text-align: center;
  font-family: var(--serif);
  font-style: italic;
  font-size: 21px;
  letter-spacing: .12em;
  color: rgba(var(--bone), .22);
}

/* ================= THE INDEX ================= */

.idx {
  display: grid;
  grid-template-columns: 118px 1fr;
  min-height: 100%;
  padding: 4px 40px 48px 26px;
  transition: padding-right .4s ease;
}

/* the entry panel is an overlay, so give the ladder room to get out
   from under it. Otherwise half the numbers sit behind the reading */
.idx[data-entry="1"] { padding-right: min(452px, 46vw); }

.idx__rail {
  font-size: 9px;
  letter-spacing: .18em;
  color: rgba(var(--bone), .2);
  line-height: 2.6;
  padding-top: 22px;
}

.idx__grid {
  display: grid;
  grid-template-columns: repeat(8, 1fr);
  gap: 9px;
  align-content: start;
  max-width: 820px;
  padding: 22px 0 0 20px;
}

.idx__cell,
.idx__dark {
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: .14em;
  padding: 12px 0;
  text-align: center;
  border: none;
  border-radius: 3px;
  background: none;
}

.idx__cell {
  color: rgba(var(--bone), .88);
  background: rgba(var(--white), .07);
  cursor: pointer;
  transition: background .25s ease, color .25s ease;
}
.idx__cell:hover { background: rgba(var(--white), .16); color: rgb(var(--white)); }
.idx__cell[aria-expanded="true"] { background: rgba(var(--white), .2); color: rgb(var(--white)); }
.idx__cell:focus-visible { outline: 1px solid rgba(var(--white), .7); outline-offset: 2px; }

.idx__dark { color: rgba(var(--bone), .08); }

.idx__entry {
  position: fixed;
  right: 0;
  top: 84px;
  bottom: 0;
  width: min(420px, 42vw);
  padding: 28px 30px;
  background: linear-gradient(90deg, rgba(6, 6, 11, 0), rgba(9, 9, 16, .97) 26%);
  box-shadow: -60px 0 90px rgba(0, 0, 0, .72);
  /* Affordable because js/void.js drops the field canvas to 12fps
     while a page is open. At 60 this re-blurred a 420x996 region on
     every canvas frame for as long as an entry stayed open. */
  backdrop-filter: blur(4px);
}

.idx__entry-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  padding-bottom: 13px;
  margin-bottom: 20px;
  background-image: linear-gradient(90deg, rgba(var(--bone), .16), transparent 88%);
  background-repeat: no-repeat;
  background-size: 100% 1px;
  background-position: 0 100%;
}

.idx__entry-num { font-size: 11px; letter-spacing: .22em; color: rgba(var(--bone), .85); }

.idx__entry-close {
  background: none;
  border: none;
  color: rgba(var(--bone), .4);
  font-family: inherit;
  font-size: 16px;
  cursor: pointer;
  padding: 0 4px;
}
.idx__entry-close:hover { color: rgb(var(--white)); }
.idx__entry-close:focus-visible { outline: 1px solid rgba(var(--white), .7); outline-offset: 3px; }

.idx__entry-text {
  font-family: var(--serif);
  font-size: 19px;
  line-height: 1.62;
  color: rgba(var(--bone), .84);
  margin: 0;
  /* single newlines inside one of her paragraphs are hers */
  white-space: pre-line;
}

/* Her blank lines become real paragraphs, so give them the gap she
   meant instead of a run-on block. */
.idx__entry-text + .idx__entry-text { margin-top: .75em; }

.idx__entry-text em { font-style: italic; color: rgba(var(--bone), .95); }
.idx__entry-text strong { font-weight: 400; color: rgb(var(--white)); }

/* ---------- asking the index for a row ----------
   Her log is a table and you can query it. Sits above the grid so it
   reads as part of the catalogue rather than as site chrome. */

.idx__ask {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  padding: 0 0 4px;
}

.idx__ask-label {
  font-size: 9px;
  letter-spacing: .24em;
  color: rgba(var(--bone), .42);
}

.idx__ask-input {
  width: 68px;
  padding: 5px 2px;
  background: none;
  border: none;
  border-bottom: 1px solid rgba(var(--bone), .22);
  font-family: var(--mono);
  font-size: 13px;
  letter-spacing: .2em;
  color: rgba(var(--bone), .88);
  text-align: center;
}
.idx__ask-input:focus { outline: none; border-bottom-color: rgba(var(--white), .7); }

.idx__ask-say {
  font-size: 9px;
  letter-spacing: .22em;
  color: rgba(var(--bone), .38);
}

.idx__entry-foot {
  position: absolute;
  left: 30px;
  bottom: 26px;
  font-size: 9px;
  letter-spacing: .2em;
  color: rgba(var(--bone), .2);
}

/* ================= THE BENCH ================= */

.bench {
  min-height: 100%;
  padding: 56px 56px 40px;
  display: flex;
  flex-direction: column;
  align-items: center;
  /* `safe` matters: once entry 024 is appended the column is taller than
     the viewport, and plain `center` puts the overflow above the scroll
     origin where it can never be reached */
  justify-content: center;
  justify-content: safe center;
  gap: 46px;
}

.bench__cells {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  gap: 18px 10px;
  width: 100%;
  max-width: 940px;
}

/* GlyphCell, to spec */
.bench__cell {
  padding: 4px 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 9px;
  background: none;
  border: none;
  border-radius: 3px;
  cursor: pointer;
  font-family: var(--mono);
  transition: filter .3s ease, background .25s ease;
}
.bench__cell:hover { filter: brightness(1.5); }
.bench__cell:focus-visible { outline: 1px solid rgba(var(--white), .7); outline-offset: 2px; }

/* one cell held, waiting for the one it trades with */
.bench__cell[data-picked="1"] {
  background: rgba(var(--white), .14);
  filter: brightness(1.6);
}

.bench[data-solved="1"] .bench__cell { cursor: default; }
.bench[data-solved="1"] .bench__readout { color: rgba(var(--bone), .88); }

.bench__note {
  max-width: 620px;
  padding-top: 26px;
  text-align: left;
  background-image: linear-gradient(90deg, transparent, rgba(var(--bone), .14) 22%, rgba(var(--bone), .14) 78%, transparent);
  background-repeat: no-repeat;
  background-size: 100% 1px;
  background-position: 0 0;
}
.bench__note-num { font-size: 11px; letter-spacing: .22em; color: rgba(var(--bone), .85); }
.bench__note-text {
  margin: 14px 0 0;
  font-family: var(--serif);
  font-size: 18px;
  line-height: 1.6;
  color: rgba(var(--bone), .84);
}
.bench__note-foot { margin-top: 14px; font-size: 9px; letter-spacing: .2em; color: rgba(var(--bone), .2); }

.bench__reset {
  background: none;
  border: none;
  padding: 8px 10px;
  font-family: var(--mono);
  font-size: 9px;
  letter-spacing: .2em;
  color: rgba(var(--bone), .22);
  cursor: pointer;
  transition: color .3s ease;
}
.bench__reset:hover { color: rgba(var(--bone), .6); }
.bench__reset:focus-visible { outline: 1px solid rgba(var(--white), .7); outline-offset: 2px; }

.idx__entry-sub {
  margin-top: 20px;
  font-size: 8.5px;
  letter-spacing: .22em;
  color: rgba(var(--bone), .45);
}

.idx__correct {
  display: block;
  margin-top: 16px;
  padding: 8px 0;
  background: none;
  border: none;
  font-family: var(--mono);
  font-size: 9px;
  letter-spacing: .2em;
  color: rgba(var(--bone), .42);
  cursor: pointer;
  text-align: left;
  transition: color .25s ease;
}
.idx__correct:hover { color: rgb(var(--white)); }
.idx__correct:focus-visible { outline: 1px solid rgba(var(--white), .7); outline-offset: 2px; }

.bench__glyph {
  /* RuneGlyph size=29 → 29 * 24/32 */
  width: 21.75px;
  height: 29px;
  fill: none;
  stroke: rgba(var(--bone), .8);
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.bench__label { font-size: 8.5px; letter-spacing: .12em; color: rgba(var(--bone), .32); }

.bench__readout {
  max-width: 940px;
  margin: 0;
  padding-top: 36px;
  text-align: center;
  font-family: var(--serif);
  font-size: 21px;
  line-height: 1.85;
  letter-spacing: .04em;
  word-spacing: .3em;
  overflow-wrap: break-word;
  color: rgba(var(--bone), .5);
  background-image: linear-gradient(90deg, transparent, rgba(var(--bone), .14) 22%, rgba(var(--bone), .14) 78%, transparent);
  background-repeat: no-repeat;
  background-size: 100% 1px;
  background-position: 0 0;
}

/* ================= THE RECOVERED =================
   Flat on purpose: no borders, no panel gradients, no mask fades.
   The images and the waveforms are the only things with edges. */

.rec {
  padding: 30px 56px 48px;
  display: flex;
  flex-direction: column;
  gap: 22px;
}

.rec__head {
  display: flex;
  align-items: baseline;
  gap: 18px;
  margin-top: 8px;
}
.rec__head-a { font-size: 9px; letter-spacing: .24em; color: rgba(var(--bone), .62); }
.rec__head-b { font-size: 9px; letter-spacing: .2em; color: rgba(var(--bone), .22); }

/* a sideways rail with a snap and its own thumb. Plates are tall and
   there are more of them now than a single row can carry */
.rec__rail { display: flex; flex-direction: column; }

.rec__plates {
  display: flex;
  gap: 22px;
  overflow-x: auto;
  overflow-y: hidden;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;
  -ms-overflow-style: none;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 2%, #000 96%, transparent);
          mask-image: linear-gradient(90deg, transparent, #000 2%, #000 96%, transparent);
}
.rec__plates::-webkit-scrollbar { display: none; }

.rec__track-bar {
  position: relative;
  height: 1px;
  margin-top: 22px;
  background: rgba(var(--bone), .08);
  opacity: 0;
  transition: opacity .4s ease;
}
.rec__track-bar[data-on="1"] { opacity: 1; }

.rec__thumb {
  position: absolute;
  top: -1px;
  height: 3px;
  background: rgba(var(--bone), .38);
  transition: left .12s linear;
}

.rec__plate {
  flex: 1;
  padding: 0;
  background: none;
  border: none;
  border-radius: 0;
  display: flex;
  flex-direction: column;
  cursor: pointer;
  font-family: var(--mono);
  transition: filter .3s ease;
}
.rec__plate:hover { filter: brightness(1.35); }
.rec__plate:focus-visible { outline: 1px solid rgba(var(--white), .7); outline-offset: 4px; }

.rec__plate img {
  width: 100%;
  height: 322px;
  object-fit: cover;
  object-position: top;
  filter: brightness(.6) contrast(1.06);
  /* the plate emerges from the dark rather than sitting in a frame */
  -webkit-mask-image: radial-gradient(72% 84% at 50% 46%, #000 26%, rgba(0, 0, 0, .6) 64%, transparent 93%);
          mask-image: radial-gradient(72% 84% at 50% 46%, #000 26%, rgba(0, 0, 0, .6) 64%, transparent 93%);
}

.rec__plate-cap {
  padding: 14px 2px 0;
  font-size: 9.5px;
  letter-spacing: .24em;
  color: rgba(var(--bone), .72);
  text-align: left;
  text-shadow: 0 0 7px rgba(var(--bone), .34), 0 0 20px rgba(var(--bone), .18), 0 0 44px rgba(var(--bone), .09);
}

/* grid, not flex-wrap: a wrapped fifth track was stretching to fill
   the whole row on its own */
/* ---------- a message and its door ---------- */

.msg {
  flex: none;
  width: min(545px, 78vw);
  scroll-snap-align: start;
  display: flex;
  flex-direction: column;
}

.msg__door { padding: 2px 2px 0; }

.msg__state {
  font-size: 9px;
  letter-spacing: .22em;
  color: rgba(var(--bone), .5);
}

.msg__reveal {
  /* her transcriptions carry rows and directions; do not collapse them */
  white-space: pre-wrap;
  margin: 12px 0 0;
  font-family: var(--serif);
  font-size: 16px;
  line-height: 1.55;
  color: rgba(var(--bone), .7);
}

.msg__form { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }

.msg__prompt {
  font-size: 9px;
  letter-spacing: .22em;
  color: rgba(var(--bone), .32);
  width: 100%;
}

.msg__input {
  flex: 1 1 180px;
  min-width: 0;
  padding: 9px 2px;
  background: none;
  border: none;
  border-bottom: 1px solid rgba(var(--bone), .18);
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: .24em;
  color: rgba(var(--bone), .9);
  transition: border-color .3s ease;
}
.msg__input:focus { outline: none; border-bottom-color: rgba(var(--white), .7); }

.msg__err {
  width: 100%;
  font-family: var(--serif);
  font-style: italic;
  font-size: 14px;
  color: rgba(var(--bone), .4);
  min-height: 1em;
}

.msg__door[data-state="working"] .msg__input { color: rgba(var(--bone), .3); }
.msg__door[data-state="working"] .msg__prompt::after {
  content: " · listening";
  color: rgba(var(--bone), .5);
}

.rec__tracks {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 14px 18px;
}

.rec__track {
  display: flex;
  flex-direction: column;
  gap: 11px;
  padding: 15px 2px 17px;
  background: none;
  border: none;
  font-family: var(--mono);
  text-align: left;
  cursor: pointer;
  transition: filter .3s ease;
}
.rec__track:hover { filter: brightness(1.4); }
.rec__track:focus-visible { outline: 1px solid rgba(var(--white), .7); outline-offset: 4px; }
/* A slot she has catalogued and does not have. Same face whether the
   config has no src at all or the file behind one has not landed. */
.msg[data-missing="1"] .rec__plate { cursor: default; opacity: .4; }
.msg[data-missing="1"] .rec__plate img { display: none; }
.msg[data-missing="1"] .rec__plate {
  min-height: 200px;
  justify-content: flex-end;
  background-image: repeating-linear-gradient(135deg, rgba(var(--bone), .03) 0 12px, transparent 12px 24px);
}

.rec__plate--empty { pointer-events: none; }
.rec__plate-mark {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 22px;
  color: rgba(var(--bone), .12);
}

/* ---------- her transcription of a carving ----------
   Public, and the accessibility path into a plate. Set in the mono
   face because it is a structural reading, not her prose. */

.msg__carving { padding: 12px 2px 0; }

.msg__carving-head {
  font-size: 8.5px;
  letter-spacing: .24em;
  color: rgba(var(--bone), .34);
  padding-bottom: 6px;
}

.msg__carving-text {
  margin: 0;
  font-family: var(--mono);
  font-size: 11.5px;
  line-height: 1.75;
  letter-spacing: .1em;
  color: rgba(var(--bone), .62);
  word-break: break-word;
}

.rec__track[data-state="held"] { cursor: default; opacity: .45; }
/* he handed this one over; there is no file behind it to play */
.rec__track[data-state="filed"] { cursor: default; }
.rec__track[data-state="filed"]:hover { filter: none; }

/* The envelope is being fetched and decrypted. 600k PBKDF2 rounds and
   a few megabytes take a beat, so the slot says so rather than looking
   dead under the press. Same register as the door's "listening". */
.rec__track[data-state="working"] { cursor: progress; }
.rec__track[data-state="working"] .rec__track-label { color: rgba(var(--bone), .4); }
.rec__track[data-state="working"] .rec__track-dur {
  color: rgba(var(--bone), .5);
  font-size: 8px;
}
.rec__track[data-state="working"] .rec__track-dur::after { content: " · recovering"; }

.rec__track-head { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
.rec__track-label { font-size: 9px; letter-spacing: .2em; color: rgba(var(--bone), .7); }
.rec__track[data-state="held"] .rec__track-label { color: rgba(var(--bone), .3); }
.rec__track-dur { font-size: 8.5px; letter-spacing: .14em; color: rgba(var(--bone), .24); }

.rec__wave { display: flex; align-items: flex-end; gap: 2px; height: 30px; }

.rec__bar {
  flex: 1;
  min-width: 1px;
  height: 20%;
  border-radius: 1px;
  background: rgba(var(--bone), .13);
  transition: background .2s ease;
}
.rec__bar[data-lit="1"] { background: rgba(var(--bone), .55); }

.rec__track-title {
  font-family: var(--serif);
  font-size: 14.5px;
  line-height: 1.35;
  color: rgba(var(--bone), .72);
}
.rec__track[data-state="held"] .rec__track-title { color: rgba(var(--bone), .18); }

/* ---------- overlays: lightbox + player ---------- */

.ov {
  position: fixed;
  inset: 0;
  z-index: 5;
  display: flex;
  align-items: center;
  justify-content: center;
  /* opaque enough that the page underneath stops competing with the
     plate. No backdrop-filter; that blur is the compositing cost we
     just spent effort removing. */
  background: rgba(2, 2, 5, .97);
  opacity: 0;
  transition: opacity .3s ease;
}
.ov[data-open="1"] { opacity: 1; }

.ov__box {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 18px;
  padding: 26px 30px 28px;
  max-width: min(1100px, 92vw);
  max-height: 88vh;
}
.ov__box--audio { width: min(720px, 92vw); background: rgba(8, 8, 14, .96); border-radius: 4px; }
.ov__box--plate { width: min(1100px, 94vw); height: 86vh; }

.ov__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 20px;
  padding-bottom: 14px;
  background-image: linear-gradient(90deg, rgba(var(--bone), .26), transparent 84%);
  background-repeat: no-repeat;
  background-size: 100% 1px;
  background-position: 0 100%;
}
.ov__title { font-size: 10px; letter-spacing: .26em; color: rgba(var(--bone), .8); }
.ov__close {
  background: none; border: none; padding: 0 4px;
  color: rgba(var(--bone), .4);
  font-family: inherit; font-size: 16px; cursor: pointer;
}
.ov__close:hover { color: rgb(var(--white)); }
.ov__close:focus-visible { outline: 1px solid rgba(var(--white), .7); outline-offset: 3px; }

.lb__stage { position: relative; flex: 1; min-height: 0; display: flex; align-items: center; justify-content: center; }
.lb__img { max-width: 100%; max-height: 100%; object-fit: contain; filter: brightness(.9); }

.lb__nav {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: 44px; height: 44px;
  background: none; border: none; border-radius: 50%;
  color: rgba(var(--bone), .5);
  font-family: inherit; font-size: 16px; cursor: pointer;
  transition: color .25s ease;
}
.lb__nav:hover { color: rgb(var(--white)); }
.lb__nav:focus-visible { outline: 1px solid rgba(var(--white), .7); }
.lb__nav--prev { left: -6px; }
.lb__nav--next { right: -6px; }

.lb__count { text-align: center; font-size: 9px; letter-spacing: .2em; color: rgba(var(--bone), .26); }

.au__title {
  margin: 0;
  font-family: var(--serif);
  font-size: 22px;
  line-height: 1.4;
  color: rgba(var(--bone), .86);
}

.au__wave {
  display: flex;
  align-items: flex-end;
  gap: 3px;
  height: 76px;
  cursor: pointer;   /* scrub */
}
.au__wave[data-flip="1"] { transform: scaleX(-1); }
.au__wave:focus-visible { outline: 1px solid rgba(var(--white), .7); outline-offset: 4px; }
.au__wave .rec__bar { pointer-events: none; }

/* the same seconds, seen as frequency */

.au__read {
  display: flex;
  justify-content: space-between;
  font-size: 8.5px;
  letter-spacing: .18em;
  color: rgba(var(--bone), .28);
}

.au__ctrls { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.au__rates { display: flex; border-radius: 4px; overflow: hidden; }

.chip {
  background: none;
  border: none;
  padding: 10px 14px;
  border-radius: 4px;
  font-family: var(--mono);
  font-size: 9px;
  letter-spacing: .2em;
  color: rgba(var(--bone), .42);
  cursor: pointer;
  transition: color .25s ease, background .25s ease;
}
.chip:hover { color: rgba(var(--bone), .8); }
.chip[data-on="1"] { color: rgb(var(--white)); background: rgba(var(--white), .12); }
.chip:focus-visible { outline: 1px solid rgba(var(--white), .7); outline-offset: 2px; }
.chip--rate { padding: 9px 12px; letter-spacing: .14em; border-radius: 0; }

/* ---------- the motion and sound toggles ----------
   Quiet enough not to break the dark, findable when you want them.
   Absent during the cold start, because nothing exists yet, and still
   reachable by keyboard there, same as the seed. */

.prefs {
  position: absolute;
  right: 22px;
  bottom: 20px;
  display: flex;
  gap: 4px;
  align-items: center;
}

.pref-toggle {
  padding: 8px 10px;
  background: none;
  border: none;
  font-family: var(--mono);
  font-size: 9px;
  letter-spacing: .22em;
  color: rgba(var(--bone), .16);
  cursor: pointer;
  transition: color .4s ease, opacity 1.2s ease 1.6s;
}

.pref-toggle:hover { color: rgba(var(--bone), .55); }

/* a thin rule between them, so two labels do not read as one long one */
.pref-toggle + .pref-toggle { border-left: 1px solid rgba(var(--bone), .12); }

body[data-state="cold"] .prefs {
  opacity: 0;
  pointer-events: none;
  transition-delay: 0s;
}

/* ================= THE TUBE =================
   Ported from the design's CRT stack, scoped to THE INDEX. Her catalogue
   is the one screen you are reading THROUGH something. Everywhere else
   the dark is the dark.

   Two dials, deliberately independent:

     --crt-amount    0.15, per the ask. Scanlines, grille, roll, tracking.
     --static-level  1 = the design's baseline grain.

   The design nests static INSIDE the CRT wrapper, so its opacity
   multiplies through. At 0.15 that would put the grain at 0.011, gone.
   Keeping them as siblings is what lets a 0.15 tube still carry
   noticeable snow.

   Two departures from the design, both for the compositor: no
   mix-blend-mode (over a near-black ground screen is within a hair of
   plain alpha, and blending a full-viewport animated layer was the cost
   that made the smoke unaffordable), and ±10% overscan instead of
   ±60%, enough to cover the drift, a quarter of the pixels. */

:root {
  --crt-amount: .15;
  --static-level: 1;
}

@keyframes crt-wobble {
  0%   { transform: translateX(-.7px) skewX(.05deg); }
  25%  { transform: translateX(.5px) skewX(-.04deg); }
  50%  { transform: translateX(-.3px) skewX(.07deg); }
  75%  { transform: translateX(.8px) skewX(-.03deg); }
  100% { transform: translateX(-.7px) skewX(.05deg); }
}
@keyframes crt-roll  { 0% { transform: translateY(-210px); } 100% { transform: translateY(110vh); } }
@keyframes crt-track {
  0%, 72%, 100% { opacity: .28; transform: translateY(0); }
  76% { opacity: .85; transform: translateY(-3px); }
  80% { opacity: .4; transform: translateY(1px); }
}
@keyframes grain-a { 0% { transform: translate(0, 0); } 25% { transform: translate(-3%, 2%); } 50% { transform: translate(2%, -3%); } 75% { transform: translate(-2%, -1%); } 100% { transform: translate(0, 0); } }
@keyframes grain-b { 0% { transform: translate(2%, -2%); } 33% { transform: translate(-3%, 1%); } 66% { transform: translate(1%, 3%); } 100% { transform: translate(2%, -2%); } }
@keyframes grain-c {
  0%   { transform: translate(1%, 2%) scale(1.04); }
  20%  { transform: translate(-2%, -1%) scale(1.01); }
  40%  { transform: translate(3%, -2%) scale(1.05); }
  60%  { transform: translate(-1%, 3%) scale(1.02); }
  80%  { transform: translate(2%, 1%) scale(1.03); }
  100% { transform: translate(1%, 2%) scale(1.04); }
}

.crt, .static {
  position: absolute;
  inset: 0;
  z-index: 4;
  pointer-events: none;
  overflow: hidden;
  opacity: 0;
  transition: opacity .6s ease;
}

/* The tube only exists over her catalogue, and so does every animation
   in it. These five used to be declared on the layers themselves, which
   meant three full-viewport SVG-noise textures kept animating on the
   bench and THE RECOVERED with opacity: 0 over them. That is pure
   transform, so it costs the compositor rather than the main thread,
   but it is still around 36MB of GPU texture held for pixels nobody
   receives, and on a phone that is the difference. */
.page[data-page="index"] .crt { opacity: var(--crt-amount); animation: crt-wobble 9s ease-in-out infinite; }
.page[data-page="index"] .static { opacity: 1; }

.page[data-page="index"] .crt__roll  { animation: crt-roll 9.5s linear infinite; }
.page[data-page="index"] .crt__track { animation: crt-track 7s ease-in-out infinite; }
.page[data-page="index"] .static__fine   { animation: grain-a .48s steps(1, end) infinite; }
.page[data-page="index"] .static__coarse { animation: grain-b .72s steps(1, end) infinite; }
.page[data-page="index"] .static__snow   { animation: grain-c .55s steps(1, end) infinite; }

.crt > *, .static > * { position: absolute; }

.crt__scan {
  inset: 0;
  background: repeating-linear-gradient(180deg, rgba(0, 0, 0, .42) 0 1px, transparent 1px 3px);
  opacity: .72;
}
.crt__grille {
  inset: 0;
  background: repeating-linear-gradient(90deg, rgba(0, 0, 0, .16) 0 1px, transparent 1px 3px);
  opacity: .55;
}
.crt__roll {
  left: 0; right: 0; top: 0;
  height: 210px;
  background: linear-gradient(180deg, transparent, rgba(var(--bone), .055) 42%, rgba(var(--bone), .022) 58%, transparent);
}
.crt__track {
  left: 0; right: 0; bottom: 0;
  height: 16px;
  background: repeating-linear-gradient(180deg, rgba(var(--bone), .075) 0 1px, transparent 1px 2px);
  filter: blur(.5px);
}

/* three grades of noise, each drifting on its own clock */
.static__fine, .static__coarse, .static__snow {
  inset: -10%;
  background-repeat: repeat;
}
.static__fine {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)'/%3E%3C/svg%3E");
  background-size: 160px 160px;
  opacity: calc(.075 * var(--static-level));
}
.static__coarse {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='220' height='220'%3E%3Cfilter id='c'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.42' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='220' height='220' filter='url(%23c)'/%3E%3C/svg%3E");
  background-size: 220px 220px;
  opacity: calc(.055 * var(--static-level));
}
.static__snow {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='300' height='300'%3E%3Cfilter id='s'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.62' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='300' height='300' filter='url(%23s)'/%3E%3C/svg%3E");
  background-size: 300px 300px;
  opacity: calc(.085 * var(--static-level));
}

/* a flickering tube is exactly what reduced motion is asking us to stop.
   The grain stays; it just holds still. */
body[data-motion="calm"] .crt,
body[data-motion="none"] .crt,
body[data-motion="calm"] .static > *,
body[data-motion="none"] .static > * { animation: none !important; }
body[data-motion="calm"] .crt__roll,
body[data-motion="none"] .crt__roll { display: none; }

/* ================= SPEAKING TO HIM =================
   The input he gives you when you touch his sigil, and the answer that
   takes the whole frame afterwards. See js/speak.js. */

@keyframes voidrise {
  from { opacity: 0; transform: translateY(14px); }
  to   { opacity: 1; transform: none; }
}

.speak {
  position: fixed;
  inset: 0;
  z-index: 6;
  display: flex;
  align-items: center;
  justify-content: center;
  background: radial-gradient(74% 66% at 50% 46%, rgba(1, 1, 3, .94), rgba(0, 0, 0, .99) 76%);
  backdrop-filter: blur(7px);
  opacity: 0;
  transition: opacity .5s ease;
}
.speak[data-open="1"] { opacity: 1; }

.speak__col {
  display: flex;
  flex-direction: column;
  align-items: center;
  width: min(660px, 88vw);
}

.speak__mark {
  width: 34px;
  height: 45px;
  fill: none;
  stroke: rgba(var(--bone), .5);
  stroke-width: 1.8;
  stroke-linecap: round;
  margin-bottom: 44px;
}

.speak__input {
  width: 100%;
  background: none;
  border: none;
  outline: none;
  text-align: center;
  font-family: var(--serif);
  font-weight: 300;
  font-size: clamp(24px, 4vw, 34px);
  letter-spacing: .06em;
  color: rgba(var(--bone), .92);
  caret-color: rgba(var(--bone), .6);
  text-shadow: 0 0 12px rgba(var(--bone), .3), 0 0 40px rgba(var(--bone), .14);
  padding: 6px 0;
}

.speak__rule {
  width: 100%;
  height: 1px;
  background: linear-gradient(90deg, transparent, rgba(var(--bone), .22) 26%, rgba(var(--bone), .22) 74%, transparent);
}

.speak__hint {
  margin-top: 22px;
  font-family: var(--serif);
  font-style: italic;
  font-size: 17px;
  letter-spacing: .16em;
  color: rgba(var(--bone), .34);
}

/* a crop of a plate, rising while you type */
.speak__found {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 16px;
  overflow: hidden;
  max-height: 0;
  opacity: 0;
  margin-top: 0;
  transition: max-height .95s cubic-bezier(.19, 1, .22, 1),
              opacity .8s ease,
              margin-top .95s cubic-bezier(.19, 1, .22, 1);
}
.speak__found[data-on="1"] { max-height: 360px; opacity: 1; margin-top: 44px; }

.speak__plate {
  filter: brightness(.68) contrast(1.06);
  -webkit-mask-image: radial-gradient(70% 82% at 50% 48%, #000 24%, rgba(0, 0, 0, .6) 62%, transparent 92%);
          mask-image: radial-gradient(70% 82% at 50% 48%, #000 24%, rgba(0, 0, 0, .6) 62%, transparent 92%);
}

.speak__cap {
  font-size: 8.5px;
  letter-spacing: .24em;
  color: rgba(var(--bone), .5);
  text-shadow: 0 0 8px rgba(var(--bone), .25);
}

/* addressed at length, and he said nothing */
.speak__null {
  margin-top: 0;
  opacity: 0;
  transition: opacity 1.2s ease .5s, margin-top .8s ease;
}
.speak__null[data-on="1"] { margin-top: 40px; opacity: 1; }
.speak__null svg { width: 16px; height: 21px; fill: none; stroke: rgba(var(--bone), .22); stroke-width: 1.6; stroke-linecap: round; }

.speak__close {
  position: absolute;
  right: 30px;
  top: 26px;
  background: none;
  border: none;
  color: rgba(var(--bone), .34);
  font-family: var(--mono);
  font-size: 15px;
  cursor: pointer;
  transition: color .45s ease, text-shadow .45s ease;
}
.speak__close:hover,
.speak__close:focus-visible {
  color: rgba(var(--bone), .95);
  text-shadow: 0 0 9px rgba(var(--bone), .5), 0 0 24px rgba(var(--bone), .3);
}

/* ---------- the cut: his answer ---------- */

.cut {
  position: fixed;
  inset: 0;
  z-index: 7;
  overflow: hidden;
  background: #000;
  cursor: pointer;
  opacity: 0;
  transition: opacity .7s ease;
}
.cut[data-open="1"] { opacity: 1; }
.cut[data-bare="1"] { cursor: default; background: var(--ink-lit); }

.cut__plate {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  animation: voidrise 2.6s cubic-bezier(.19, 1, .22, 1) both;
}
.cut[data-plate="0"] .cut__plate { display: none; }

.cut__body {
  position: absolute;
  left: 0;
  right: 0;
  top: 50%;
  transform: translateY(-50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 0 clamp(20px, 7vw, 90px);
}

.cut__speech {
  max-width: 820px;
  margin: 0;
  text-align: center;
  font-family: var(--serif);
  font-weight: 300;
  font-size: 33px;
  line-height: 1.66;
  letter-spacing: .035em;
  color: rgba(var(--bone), .9);
  text-shadow: 0 0 16px rgba(var(--bone), .22), 0 0 52px rgba(var(--bone), .1);
  text-wrap: pretty;
  animation: voidrise 1.9s cubic-bezier(.19, 1, .22, 1) .4s both;
}
.cut__speech[data-long="1"] { font-size: 23px; }

.cut__audio {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  animation: voidrise 1.9s cubic-bezier(.19, 1, .22, 1) .4s both;
}
.cut__track {
  font-size: 9.5px;
  letter-spacing: .28em;
  color: rgba(var(--bone), .9);
  text-shadow: 0 0 8px rgba(var(--bone), .4), 0 0 22px rgba(var(--bone), .2);
}
.cut__bars { display: flex; align-items: flex-end; gap: 3px; height: 64px; margin: 22px 0 14px; }
.cut__bar { width: 3px; border-radius: 1px; background: rgba(var(--bone), .18); }
.cut__bar[data-lit="1"] { background: rgba(var(--bone), .6); }
.cut__read { display: flex; gap: 26px; font-size: 8px; letter-spacing: .22em; color: rgba(var(--bone), .34); }
.cut__subs {
  max-width: 600px;
  margin: 26px 0 0;
  text-align: center;
  font-family: var(--serif);
  font-style: italic;
  font-size: 17px;
  line-height: 1.62;
  color: rgba(var(--bone), .6);
}
.cut__filed { margin-top: 26px; font-size: 8px; letter-spacing: .3em; color: rgba(var(--bone), .34); }

.cut__grant {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  margin-top: 34px;
  animation: voidrise 1.9s cubic-bezier(.19, 1, .22, 1) .5s both;
}
.cut__grant-line {
  font-family: var(--serif);
  font-weight: 300;
  font-size: 33px;
  letter-spacing: .06em;
  color: rgba(var(--bone), .9);
  text-shadow: 0 0 14px rgba(var(--bone), .35), 0 0 44px rgba(var(--bone), .16);
}
.cut__grant-where { margin-top: 20px; font-size: 8.5px; letter-spacing: .26em; color: rgba(var(--bone), .42); }

.cut__null {
  margin-top: 34px;
  font-size: 15px;
  letter-spacing: .3em;
  color: rgba(var(--bone), .2);
  animation: voidrise 2.4s cubic-bezier(.19, 1, .22, 1) 1.1s both;
}

.cut__withdraw {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 46px;
  text-align: center;
  font-size: 8px;
  letter-spacing: .3em;
  color: rgba(var(--bone), .16);
}

/* ---------- keyboard ---------- */

/* the whole design hides itself from anyone without a pointer.
   focus is the keyboard's lamp: it resolves whatever it lands on. */
.seed:focus-visible,
.node:focus-visible {
  --op: 1 !important;
  --near: 1 !important;
  outline: none;
  color: rgba(var(--bone), .95) !important;
  filter: drop-shadow(0 0 18px rgba(var(--white), .5)) !important;
  pointer-events: auto !important;
}

body[data-state="cold"] .node:focus-visible { opacity: 1; }

.depth:focus-visible {
  outline: 1px solid rgba(var(--white), .75);
  outline-offset: 5px;
  border-radius: 50%;
}
.depth:focus-visible .depth__ring[data-ring="1"] { stroke: rgba(var(--white), .8); }
.depth:focus-visible .depth__core { fill: rgba(var(--white), .85); }

/* Reachable even while the cold start is hiding them. The opacity now
   sits on the row, so tabbing to either button has to lift the row;
   :focus-within is what does that, and it is shorter than
   :has(:focus-visible). */
.prefs:focus-within {
  opacity: 1 !important;
  pointer-events: auto !important;
  transition-delay: 0s;
}

.pref-toggle:focus-visible {
  color: rgba(var(--bone), .9) !important;
  outline: 1px solid rgba(var(--white), .7);
  outline-offset: 3px;
}

.noscript {
  position: fixed;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--ink-cold);
  color: rgba(var(--bone), .5);
  font-family: var(--serif);
  font-size: 20px;
  letter-spacing: .16em;
  z-index: 10;
}

/* ---------- coarse pointers ---------- */

/* no cursor to sweep with: the lamp only costs paint and nothing would
   ever resolve, so the dark is lifted and everything sits still. */
body[data-pointer="coarse"] .lamp { display: none; }
body[data-pointer="coarse"] .void { cursor: default; }
body[data-pointer="coarse"] .node--2 { pointer-events: auto; }
/* there is no cursor to move */
body[data-pointer="coarse"] .centre__hint { display: none; }

/* narrow: the rings become rows (see placeNodes in js/void.js) */
@media (max-width: 700px) {
  .centre__mark { width: 46px; height: 62px; }
  .centre__title { letter-spacing: .46em; text-indent: .46em; }
  .centre__hint { font-size: 8px; letter-spacing: .22em; }
  .whisper { top: calc(var(--cy) + 150px); font-size: 13px; letter-spacing: .22em; }
  /* narrow uses the row layout, so the ring no longer governs this */

  .node { padding: 8px 4px; gap: 7px; }
  .node--2 .node__mark { width: 19px; height: 25px; }
  .node--2 .node__label { font-size: 8.5px; letter-spacing: .14em; }
  .node--2 .node__meta { font-size: 7.5px; letter-spacing: .1em; }
  .node--3 { flex-direction: row; gap: 8px; align-items: center; }
  .node--3 .node__mark { width: 13px; height: 17px; }
  .node--3 .node__label { font-size: 8px; letter-spacing: .18em; }
  .node--3 .node__meta { font-size: 7.5px; letter-spacing: .1em; }

  .depth { left: 16px; top: 16px; width: 34px; height: 34px; }
  .depth__rings { width: 34px; height: 34px; }

  .idx { grid-template-columns: 1fr; padding: 4px 16px 40px; }
  .idx__rail { display: none; }
  .idx__grid { grid-template-columns: repeat(4, 1fr); padding-left: 0; }
  .idx__entry { width: 100%; top: auto; height: 62vh; }

  .bench { padding: 28px 18px 36px; gap: 28px; }
  .bench__cells { grid-template-columns: repeat(6, 1fr); gap: 16px 8px; }
  .bench__readout { font-size: 17px; padding-top: 26px; }

  .rec { padding: 20px 18px 40px; }
  .rec__plates { flex-direction: column; }
  .rec__plate img { height: 200px; }
  .rec__tracks { grid-template-columns: 1fr; }
  .ov__box { padding: 18px 16px 22px; }

  /* the bottom edge belongs to the shrouded row down here */
  .prefs { bottom: auto; top: 14px; right: 12px; }
  .pref-toggle { font-size: 8px; padding: 8px 6px; }
}

/* ---------- reduced motion ----------
   Keyed off data-motion (set by js/void.js), not the media query
   directly, so ?motion=full can override the OS setting for testing.
   The media query still governs until the script sets the attribute. */

@media (prefers-reduced-motion: reduce) {
  body:not([data-motion="full"]) .web,
  body:not([data-motion="full"]) .centre,
  body:not([data-motion="full"]) .seed,
  body:not([data-motion="full"]) .node,
  body:not([data-motion="full"]) .page,
  body:not([data-motion="full"]) .whisper {
    transition-duration: .2s !important;
    transition-delay: 0s !important;
  }
  body:not([data-motion="full"])[data-state="cold"] .web { transform: none; }
}

body[data-motion="calm"] .web,
body[data-motion="calm"] .centre,
body[data-motion="calm"] .seed,
body[data-motion="calm"] .node,
body[data-motion="calm"] .page,
body[data-motion="calm"] .whisper,
body[data-motion="none"] .web,
body[data-motion="none"] .centre,
body[data-motion="none"] .seed,
body[data-motion="none"] .node,
body[data-motion="none"] .page,
body[data-motion="none"] .whisper {
  transition-duration: .2s !important;
  transition-delay: 0s !important;
}

body[data-motion="calm"][data-state="cold"] .web,
body[data-motion="none"][data-state="cold"] .web { transform: none; }

/* ---------- faint inscriptions ----------
   Writing carved at the luminance of the wall. js/lamp.js builds the
   lit layer and moves the light; everything here is paint.

   The dim copy is the real one; it is what a screen reader reads and
   what a keyboard lands on. The bright copy is a clone sitting exactly
   on top of it, masked down to the reach of the lamp. Because it is a
   clone of the same box, the two wrap identically at any width. */

.faint {
  position: relative;
  display: inline-block;
  color: rgba(var(--bone), var(--faint-base, .035));
  /* no cursor change: on a dark page the cursor would announce the
     inscription long before the light did */
  cursor: inherit;
  transition: color .6s ease;
}

.faint--block { display: block; }

.faint__lit {
  position: absolute;
  inset: 0;
  width: 100%;
  color: rgba(var(--bone), .84);
  pointer-events: none;
  opacity: var(--fo, 0);
  transition: opacity .35s ease;
  -webkit-mask-image: radial-gradient(circle var(--faint-r, 150px) at var(--fx, 50%) var(--fy, 50%),
                      #000 0 34%, transparent 74%);
          mask-image: radial-gradient(circle var(--faint-r, 150px) at var(--fx, 50%) var(--fy, 50%),
                      #000 0 34%, transparent 74%);
}

/* an inscription in the field carries a glyph as often as a letter */
.faint__lit svg,
.faint svg { display: block; overflow: visible; }
.faint svg path { fill: none; stroke: currentColor; stroke-width: 2.4; stroke-linecap: round; }

/* latched: held open by a tap or a press, for anyone who cannot sweep.
   the dim copy fades back so the two do not double-print. */
.faint[data-faint-lit="1"] { color: rgba(var(--bone), .06); }

.faint[data-faint-lit="1"] .faint__lit,
.faint:focus-visible .faint__lit {
  opacity: 1;
  -webkit-mask-image: none;
          mask-image: none;
}

/* focus is the keyboard's lamp, the same rule the rest of this file follows */
.faint:focus-visible {
  outline: 1px solid rgba(var(--white), .55);
  outline-offset: 5px;
}

/* Coarse pointers have no hover at all, so there is no sweep to give
   the inscription away. Mark it instead: a dotted rule says something
   is written here without saying what. Tap holds it open. */
body[data-pointer="coarse"] .faint {
  text-decoration: underline dotted rgba(var(--bone), .22);
  text-underline-offset: 7px;
  text-decoration-thickness: 1px;
}

body[data-motion="calm"] .faint,
body[data-motion="calm"] .faint__lit,
body[data-motion="none"] .faint,
body[data-motion="none"] .faint__lit {
  transition-duration: .12s !important;
}

/* the one on the landing: it sits out past the web, low and to the
   left, where the composition has nothing in it. deliberately not
   under the whisper; two faint lines on one baseline read as one
   mistake rather than two intentions. */
.inscription {
  position: absolute;
  left: 5vw;
  bottom: 5vh;
  max-width: 42ch;
  margin: 0;
  text-align: left;
  font-family: var(--serif);
  font-style: italic;
  font-size: 15px;
  letter-spacing: .16em;
  line-height: 1.7;
  opacity: 0;
  transition: opacity 1.4s ease 1.8s, visibility 0s linear 1.8s;
}

/* nothing out here exists before the circles do, and hidden means
   hidden from the tab order too, or a keyboard finds it first */
body[data-state="cold"] .inscription { visibility: hidden; }
body[data-state="lit"] .inscription { opacity: 1; visibility: visible; }

@media (max-width: 700px) {
  .inscription {
    left: 22px;
    right: 22px;
    bottom: 3.5vh;
    max-width: none;
    font-size: 14px;
  }
}
