/* ==========================================================================
   4ewdsr5y — HUD
   The live field canvas, corner frame, telemetry blocks, and the power-on
   transition.

   Everything in here is decoration: it is aria-hidden, pointer-events:none,
   and nothing a customer needs is expressed only through it.
   ========================================================================== */

/* --------------------------------------------------------- 1. FIELD CANVAS */

.field-canvas {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: var(--z-base);
  pointer-events: none;
  /* Held back behind reading matter on the content pages... */
  opacity: 0.5;
}

/* A section room is the place you are standing in, not a backdrop, so it
   is carried much stronger than the sphere — but not to full, because
   unlike the landing these pages are full of text that has to stay
   comfortably readable on top of it. */
.field-canvas[data-mode="room"] { opacity: 0.62; }

/* ...and given full strength on the landing, where it is the subject.
   .stage only exists on index.html. */
body:has(.stage) .field-canvas[data-mode="room"] { opacity: 1; }


/* ------------------------------------------- 1c. THE ROOM BEHIND THE DOOR

   A page a corridor door leads to shows the room behind that door: a still
   rendered in the corridor's own materials, light and fog
   (tools/rooms/build_corridor.py -- backdrop <room>), in place of the 2D
   line room. Wide for landscape windows, tall for portrait ones; only the
   one that matches is downloaded. Light theme only: the dark theme, which
   is switched off, keeps its 2D rooms. */
.room-still {
  position: fixed;
  inset: -4%;              /* room to move (js/backdrop.js) without showing an edge */
  z-index: var(--z-base);
  pointer-events: none;
  background: center / cover no-repeat;
  will-change: transform;
}
.room-still[data-room-still="shop"]     { background-image: url("../assets/rooms/backdrops/shop-wide.webp"); }
.room-still[data-room-still="lookbook"] { background-image: url("../assets/rooms/backdrops/lookbook-wide.webp"); }
.room-still[data-room-still="about"]    { background-image: url("../assets/rooms/backdrops/about-wide.webp"); }
@media (max-aspect-ratio: 4/5) {
  .room-still[data-room-still="shop"]     { background-image: url("../assets/rooms/backdrops/shop-tall.webp"); }
  .room-still[data-room-still="lookbook"] { background-image: url("../assets/rooms/backdrops/lookbook-tall.webp"); }
  .room-still[data-room-still="about"]    { background-image: url("../assets/rooms/backdrops/about-tall.webp"); }
}
:root[data-theme="light"] body:has(.room-still) .field-canvas { visibility: hidden; }
:root:not([data-theme="light"]) .room-still { display: none; }
/* Held back a little, as the 2D room was, so it reads as the place rather
   than a picture. */
.room-still { opacity: 0.8; }
/* Text that stands on the room gets the ground behind it: every colour on
   these pages was measured against --bg, the tightest near 4.7:1, so the
   room's darker parts behind it would fail them. Solid where it is a
   control, feathered where it is a line of text. */
:root[data-theme="light"] body:has(.room-still) .chip:not([aria-pressed="true"]),
:root[data-theme="light"] body:has(.room-still) .search { background: var(--bg); }
:root[data-theme="light"] body:has(.room-still) .pcard-meta,
:root[data-theme="light"] body:has(.room-still) .pcard-blurb {
  background: var(--bg);
  box-shadow: 0 0 12px 6px var(--bg);
}
/* The product page reads in blocks — the trail, the name band, the info
   column with its specs, sizes and chart — so each is laid on the ground
   whole, edges feathered, and the room shows round them. */
:root[data-theme="light"] body:has(.room-still) .crumb,
:root[data-theme="light"] body:has(.room-still) .pdp-masthead,
:root[data-theme="light"] body:has(.room-still) .pdp-info {
  background: var(--bg);
  box-shadow: 0 0 0 12px var(--bg), 0 0 20px 16px var(--bg);
}
/* A page's title band is backed word by word, not as a band: across the
   top is where these pages show their room. */
:root[data-theme="light"] body:has(.room-still) .page-head .crumb,
:root[data-theme="light"] body:has(.room-still) .page-head .page-title,
:root[data-theme="light"] body:has(.room-still) .page-head .t-telemetry {
  background: var(--bg);
  box-shadow: 0 0 12px 6px var(--bg);
}
/* The about page is prose: each block reads on the ground, like paper laid
   on the office's desk, and the room shows to its right and between. */
:root[data-theme="light"] body:has(.room-still) .prose,
:root[data-theme="light"] body:has(.room-still) .rule-label span {
  background: var(--bg);
  box-shadow: 0 0 0 12px var(--bg), 0 0 20px 16px var(--bg);
}
/* The footer stands on the room too: its words on the ground, feathered,
   as the title bands' are (1.9-3.4:1 on the room, 6.7:1 on the ground;
   missed when the stills came in, shipped 2026-10-09). */
:root[data-theme="light"] body:has(.room-still) .ftr-links a,
:root[data-theme="light"] body:has(.room-still) .ftr .flow-tight > div {
  background: var(--bg);
  box-shadow: 0 0 12px 6px var(--bg);
}
/* The product page's not-found message, laid on the ground as a block, as
   the page's other blocks are (1.7:1 on the room, 5.3:1 on the ground). */
:root[data-theme="light"] body:has(.room-still) [data-pdp] .empty {
  background: var(--bg);
  box-shadow: 0 0 0 12px var(--bg), 0 0 20px 16px var(--bg);
}
/* An outlined button standing on the room: filled with the ground, so its
   label keeps its contrast. Solid buttons fill themselves. */
:root[data-theme="light"] body:has(.room-still) .btn:not(.btn-solid):not(.btn-acquire):not(:hover) {
  background: var(--bg);
}


/* ------------------------------------------------- 1b. DAMAGE VIGNETTE

   A wine bloom at the screen edges: always faintly present, and pulsed on a
   dead end (a sold-out unit, a size that is gone, a control that cannot fire
   yet).

   It sits at z-index 1 — above the field canvas, BELOW everything readable.
   A game would paint this over the whole screen, but over text it would eat
   contrast at exactly the edges where the status bar lives, and that is not
   a trade worth making. The background is most of the screen out there
   anyway, so the effect still reads.

   The pulse is one slow beat, never a strobe: 90ms in, 620ms out. Nothing
   here comes close to the three-flashes-per-second seizure threshold.
   ------------------------------------------------------------------------ */

.field-damage {
  position: fixed;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background: radial-gradient(
    ellipse 74% 66% at 50% 50%,
    transparent 40%,
    color-mix(in srgb, var(--damage) 62%, transparent) 82%,
    var(--damage) 100%
  );
  opacity: var(--damage-idle);
  transition: opacity 620ms var(--ease);
}

/* Sharp in, slow out — the shape of a hit. */
.field-damage[data-hit="true"] {
  opacity: var(--damage-hit);
  transition-duration: 90ms;
}

/* The ambient heartbeat: same shape, far shallower, every 10s. */
.field-damage[data-beat="true"] {
  opacity: var(--damage-beat);
  transition-duration: 260ms;
}

/* While healing, the wine clears out entirely before returning to rest. */
.field-damage[data-heal="true"] {
  opacity: 0;
  transition-duration: 140ms;
}

/* ---------------------------------------------------------- 1c. HEAL

   The counterpart to damage: a cool bloom from the edges when something
   goes right. Same layer geometry, same stacking rules — below everything
   readable.
   ------------------------------------------------------------------------ */

.field-heal {
  position: fixed;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background: radial-gradient(
    ellipse 74% 66% at 50% 50%,
    transparent 38%,
    color-mix(in srgb, var(--heal) 58%, transparent) 80%,
    var(--heal) 100%
  );
  opacity: 0;
  transition: opacity 900ms var(--ease);
}
.field-heal[data-on="true"] {
  opacity: var(--heal-peak);
  transition-duration: 130ms;
}

@media (prefers-reduced-motion: reduce) {
  /* Keep the resting atmosphere; drop every pulse, beat and bloom. */
  .field-damage, .field-heal { transition: none; }
  .field-damage[data-hit="true"],
  .field-damage[data-beat="true"],
  .field-damage[data-heal="true"] { opacity: var(--damage-idle); }
  .field-heal[data-on="true"] { opacity: 0; }
}

/* ------------------------------------------------- 1d. GRAIN AND FOG

   Film grain and drifting fog. Both sit at z-index 1, below everything
   readable, for the same reason the damage layer does: a 10% grain over live
   text costs about 0.3 of a contrast ratio, and the tightest pairs on this
   site sit at 4.7. Below the content it still reads, because the background
   is most of the screen.

   To put grain over everything instead, move .field-grain to z-index 6 and
   lift --fg-faint and --fg-dim until nothing measures below 5.0 first.
   ------------------------------------------------------------------------ */

.field-grain {
  position: fixed;
  /* Oversized so the drift never exposes an edge. */
  inset: -60px;
  z-index: 1;
  pointer-events: none;
  background-image: url("../assets/noise.png");
  background-repeat: repeat;
  opacity: var(--grain-opacity);
  animation: grain-drift 620ms steps(1) infinite;
  will-change: transform;
}

/* Stepped, not smooth — grain should jump between frames like film. */
@keyframes grain-drift {
  0%   { transform: translate(0, 0); }
  17%  { transform: translate(-14px, 8px); }
  34%  { transform: translate(9px, -12px); }
  51%  { transform: translate(-7px, -6px); }
  68%  { transform: translate(12px, 10px); }
  85%  { transform: translate(-11px, 4px); }
  100% { transform: translate(0, 0); }
}

.field-fog {
  position: fixed;
  inset: -25%;
  z-index: 1;
  pointer-events: none;
  opacity: var(--fog-opacity);
  background:
    radial-gradient(closest-side at 28% 38%, var(--fg) 0%, transparent 70%),
    radial-gradient(closest-side at 74% 62%, var(--fg) 0%, transparent 72%),
    radial-gradient(closest-side at 52% 84%, var(--fg) 0%, transparent 68%);
  background-size: 62% 58%, 54% 62%, 70% 46%;
  background-repeat: no-repeat;
  filter: blur(28px);
  animation: fog-drift 64s ease-in-out infinite alternate;
  will-change: transform;
}

@keyframes fog-drift {
  from { transform: translate3d(-3%, 1%, 0) scale(1.04); }
  to   { transform: translate3d(4%, -2%, 0) scale(1.12); }
}

@media (prefers-reduced-motion: reduce) {
  .field-grain { animation: none; }
  .field-fog   { animation: none; }
}

/* ---------------------------------------------------------- 2. HUD LAYER */

.hud {
  position: fixed;
  inset: 0;
  z-index: 4;
  pointer-events: none;
  user-select: none;
}

/* --- corner brackets --- */
.hud-corner {
  position: absolute;
  width: 34px;
  height: 34px;
  border: 0 solid var(--rule-mid);
}
.hud-corner[data-c="tl"] { top: 64px;    left: var(--gutter);  border-top-width: 1px; border-left-width: 1px; }
.hud-corner[data-c="tr"] { top: 64px;    right: var(--gutter); border-top-width: 1px; border-right-width: 1px; }
.hud-corner[data-c="bl"] { bottom: 56px; left: var(--gutter);  border-bottom-width: 1px; border-left-width: 1px; }
.hud-corner[data-c="br"] { bottom: 56px; right: var(--gutter); border-bottom-width: 1px; border-right-width: 1px; }

/* --- telemetry blocks --- */
.hud-block {
  position: absolute;
  font-size: var(--fs-2xs);
  letter-spacing: var(--tr-wide);
  color: var(--fg-dim);
  text-transform: uppercase;
  line-height: 1.7;
  font-variant-numeric: tabular-nums;
}
.hud-block[data-pos="tl"] { top: 80px;    left: calc(var(--gutter) + 18px); }
.hud-block[data-pos="tr"] { top: 80px;    right: calc(var(--gutter) + 18px); text-align: right; }
.hud-block[data-pos="bl"] { bottom: 72px; left: calc(var(--gutter) + 18px); }
.hud-block[data-pos="br"] { bottom: 72px; right: calc(var(--gutter) + 18px); text-align: right; }

.hud-row { display: flex; gap: var(--sp-3); }
.hud-block[data-pos="tr"] .hud-row,
.hud-block[data-pos="br"] .hud-row { justify-content: flex-end; }

.hud-k { color: var(--fg-faint); min-width: 3.4em; }
.hud-v { color: var(--fg-dim); }

/* ---- HP bar --------------------------------------------------------------
   Cosmetic; its lit segments follow the visit's HP, built in js/hud.js.

   The fill is --signal-fill, the deep blood meant for solid surfaces rather
   than for text — it measures about 1.6:1 on the ground, which is right for
   a graphic and would be a failure for a word. */
.hud-hp {
  display: flex;
  gap: 2px;
  width: 108px;
  margin: 5px 0 6px;
}

.hud-hp-seg {
  flex: 1 1 auto;
  height: 7px;
  background: var(--rule-mid);
}

.hud-hp-seg[data-on="true"] { background: var(--signal-fill); }

/* A bar this low ought to breathe, and it does it on the same cycle as the
   damage vignette's heartbeat, so the two read as one system even though
   nothing connects them. Small amplitude on purpose: this is chrome at the
   corner of the eye, not something to look at. Opt-in rather than a reduce
   override, so a visitor who has asked for less motion is never handed an
   animation to override in the first place. */
@media (prefers-reduced-motion: no-preference) {
  .hud-hp-seg[data-on="true"] {
    animation: hp-breathe var(--beat-every, 10s) ease-in-out infinite;
  }
}

@keyframes hp-breathe {
  0%, 86%, 100% { opacity: 1; }
  93%           { opacity: 0.4; }
}

/* -------------------------------------------------------- 4b. THE PULL */

/* The blowout that covers the screen as a door drags you through, and
   clears again once the next room has painted. It is --bg, so it goes black
   in the dark theme and white in the light one without needing to know
   which is which.

   The literal z-index fallback is deliberate: an unresolved custom property
   invalidates the whole declaration, and a blowout stuck under the content
   would leave a visitor staring at a half-covered page. */
.warp-flash {
  position: fixed;
  inset: 0;
  background: var(--bg);
  opacity: 0;
  pointer-events: none;
  z-index: var(--z-warp, 95);
}

@media (prefers-reduced-motion: reduce) {
  /* The pull never runs, so this must never be able to show. */
  .warp-flash { display: none !important; }
}

/* ------------------------------------------------------ 5. DOOR PLATES */

/* The doorways are drawn on the canvas; the things you click are real
   anchors positioned over them each frame. Canvas hit-testing would have
   cost keyboard access, the accessibility tree and anything a crawler can
   see — too much to trade for a hallway. */

.stage[data-stage="corridor"] {
  display: block;
  min-height: min(66vh, 520px);
}

.doors {
  position: fixed;
  inset: 0;
  pointer-events: none;      /* only the plates themselves take the pointer */
  /* Above .wrap (--z-raised), which would otherwise paint the stage over the
     plates and swallow every click, and below --z-header so the status bar
     and the inventory trigger stay on top.

     The literal fallback is load-bearing, not belt-and-braces. --z-doors is
     defined in theme.css; if that file is stale or fails to load, an
     unresolved var() makes the whole declaration invalid and z-index falls
     back to auto — which silently drops this layer under .wrap and makes
     every door unclickable, with no error anywhere. A cross-file token
     holding up interaction must always carry its own fallback. */
  z-index: var(--z-doors, 20);
}

.door {
  position: absolute;
  left: 0;
  top: 0;
  display: flex;
  /* The label starts at the door HEAD and is lifted clear of it below. It
     used to be flex-end, pinned 63-84% of the way down its doorway, which put
     every door's name on the floor like a sticker and at three unrelated
     heights. */
  align-items: flex-start;
  justify-content: center;
  pointer-events: auto;
  text-decoration: none;
  outline: none;
}

/* A lit sign mounted above the doorway, the way a room name is in a real
   corridor, rather than a bordered box floating in front of the scene.

   Still an opaque plate underneath, so it never has to win a contrast
   argument with whatever the corridor is doing behind it — on the dark
   ground --bg-panel barely reads as a box at all. Rules top and bottom
   only, and a lit pip: the doors that take you somewhere glow red, and the
   pip is that same red.

   Sized at --fs-base: these signs are the whole navigation of the landing,
   and anything a customer reads or clicks is 13px or larger. The sign is
   part of the link, but it is no longer what makes the target big enough —
   the doorway box under it is grown to MIN_HIT in js/room.js, so the whole
   door stays clickable as well as its sign. */
.door-label {
  display: flex;
  align-items: center;
  gap: 8px;
  transform: translateY(calc(-100% - 8px));
  font-size: var(--fs-base);
  letter-spacing: var(--tr-wide);
  color: var(--fg);
  background: var(--bg-panel);
  border: 0 solid var(--rule-strong);
  border-width: 1px 0;
  padding: 6px 12px;
  white-space: nowrap;
  transition: color var(--dur-fast) var(--ease),
              border-color var(--dur-fast) var(--ease);
}

/* Blade mode. The canvas draws the sign and its bracket in the room; this is
   only the lettering, laid onto the sign's face by js/room.js every frame.
   No plate and no rules of its own, because the plate is the canvas's. No
   transition either: the position and colour are written per frame, and a
   transition would smear them behind the room. Every lit door in a box room
   is in blade mode at every window size; without data-blade — before the
   first measurement, or with JavaScript that has not run — the plain sign
   above the doorway stands in. */
.door[data-blade] .door-label {
  position: absolute;
  left: 0;
  top: 0;
  transform-origin: 50% 50%;
  background: transparent;
  border-width: 0;
  /* Tighter than the plain sign's 6px 12px: the plate is fitted to this box,
     and on a small screen every pixel of it is corridor a far sign cannot
     have. js/room.js measures it with a real .door[data-blade] probe, so
     this is the only place the size is defined. */
  padding: 4px 6px;
  gap: 5px;
  transition: none;
}
.door[data-blade] .door-label::before { width: 5px; height: 5px; }

.door-label::before {
  content: "";
  width: 6px;
  height: 6px;
  flex: none;
  background: var(--signal);
}

.door:hover .door-label,
.door:focus-visible .door-label {
  color: var(--fg);
  border-color: var(--signal-edge);
}
.door:focus-visible .door-label {
  outline: 1px solid var(--signal);
  outline-offset: 2px;
}

/* The way out of a section room. It hangs above its doorway instead of
   sitting at the foot of it, the way an exit sign is mounted over a door
   rather than painted on the floor, and it is lit rather than merely
   labelled — brighter ink, a firmer edge and a soft bloom, so it reads as
   the one thing in the room that is switched on.

   Bone, not the corridor's blood: the doors that take you somewhere glow
   red, and the one that lets you leave should not look like another of
   them. */
/* Every door hangs its sign above it now; an exit differs only in being
   bone rather than red, and in its bloom. No room currently has one. */
.door--exit .door-label {
  box-shadow: 0 0 20px -6px var(--fg-dim);
}
.door--exit .door-label::before { background: var(--fg); }

.door--exit:hover .door-label,
.door--exit:focus-visible .door-label {
  border-color: var(--fg-dim);
  box-shadow: 0 0 26px -4px var(--fg-dim);
}

/* JavaScript off: the corridor never gets built, so the links stand alone. */
.doors-fallback {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-4);
  padding: 0;
  margin-top: var(--sp-5);
}
.doors-fallback a { color: var(--signal); }

/* ------------------------------------------------------- 6. POWER-ON */

/* A short CRT strike on first paint. 420ms, and it animates only transform
   and filter, so it never withholds content — the markup is painted the
   whole time. Scoped to <main> so the sticky header and status bar are not
   inside a transformed ancestor. */
main { animation: crt-on 420ms var(--ease) both; }

@keyframes crt-on {
  0%   { transform: scaleY(0.006); filter: brightness(2.6) contrast(1.4); }
  55%  { transform: scaleY(1);     filter: brightness(1.22); }
  100% { transform: none;          filter: none; }
}

/* --------------------------------------------------- 7. LANDING STAGE */

.stage {
  display: grid;
  grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.1fr);
  align-items: center;
  gap: clamp(24px, 5vw, 72px);
  width: 100%;
}

/* The terminal stays in the right column and the left is left empty, so the
   corridor is the subject and the panel reads as a HUD laid over it. */
.stage > .boot-frame { grid-column: 2; }

/* ------------------------------------------------------ 8. RESPONSIVE */

@media (max-width: 1100px) {
  .hud-block[data-pos="tl"],
  .hud-block[data-pos="tr"] { top: 76px; }
}

@media (max-width: 940px) {
  .stage { grid-template-columns: 1fr; gap: var(--sp-6); }
  .stage > .boot-frame { grid-column: auto; }
}

@media (max-width: 860px) {
  /* The corner telemetry has nowhere to live next to content on a phone.
     Keep the corner brackets, drop the readout blocks. */
  .hud-block[data-pos="tl"],
  .hud-block[data-pos="tr"],
  .hud-block[data-pos="bl"],
  .hud-block[data-pos="br"] { display: none; }

  .hud-corner { width: 22px; height: 22px; }
  .hud-corner[data-c="tl"], .hud-corner[data-c="tr"] { top: 60px; }
  .hud-corner[data-c="bl"], .hud-corner[data-c="br"] { bottom: 52px; }
}

/* -------------------------------------------------- 9. REDUCED MOTION */

@media (prefers-reduced-motion: reduce) {
  main { animation: none; }
}
