/*
 * JARVIS · COGNITIVE ENVIRONMENT
 *
 * The other seven views are documents: a heading, some cards, a table. This
 * one is a place, so it takes the whole viewport and brings its own chrome.
 * That is the one structural departure in this file and it is deliberate —
 * § 2 of the brief asks for 100vw/100vh with no page furniture, and a scene
 * framed inside the shell's header, nav and 1rem padding is a picture of an
 * environment rather than an environment.
 *
 * Everything else here obeys the shell: no build step, no framework, no
 * `style=` attribute anywhere in the markup, and no @import or remote font,
 * because the server sends `default-src 'none'` with `font-src 'self'` and
 * there is no font file in this repository to serve.
 */

/* ┌───────────────────────────────────────────────────────────────────────────┐
 * │ B14-D154 · ⌘9 CORE COVERED THE NAVIGATION AND SWALLOWED ITS CLICKS.       │
 * │                                                                           │
 * │ `.jv-stage` is `position: absolute; inset: 0`, so it fills its nearest     │
 * │ POSITIONED ancestor. ⌘8 Cognition has one — the rule below makes the view  │
 * │ itself `position: fixed; inset: 0`, which is its deliberate full-screen    │
 * │ takeover, left with Escape. ⌘9 Core, added later as the landing view, was  │
 * │ given the same markup and NO such rule, so its stage had no positioned     │
 * │ ancestor at all and resolved against the viewport.                         │
 * │                                                                           │
 * │ `[MEASURED]` 2026-09-20, signed in, ⌘9 Core showing, hit-testing the       │
 * │ centre of the ⌘4 Activity link:                                           │
 * │     elementFromPoint(568, 69) → CANVAS.jv-canvas                          │
 * │     locator.click → TimeoutError after 5000ms                             │
 * │ The canvas was lying over the header and the whole navigation bar. Not a   │
 * │ z-order subtlety: the stage's box genuinely overlapped the chrome, so a    │
 * │ person with a mouse could not reach ⌘1–⌘8 from the landing view either.    │
 * │ This is what made fifteen `ui` specs time out at 30s apiece — every one    │
 * │ that navigates by clicking a nav link — and it was invisible for as long   │
 * │ as the suite could not launch a browser (B14-D146/D150).                   │
 * │                                                                           │
 * │ `position: relative` gives Core's stage the containing block it always     │
 * │ needed: the stage now fills the VIEW instead of the page, and the chrome   │
 * │ above it is reachable again. `min-height` is a floor, not a design — the   │
 * │ stage is out of flow, so without one the view collapses to nothing and     │
 * │ the scene has no box to fill. The property under test is not the number;   │
 * │ it is that the navigation is hit-testable, which the `ui` suite now        │
 * │ asserts directly rather than discovering through a timeout.                │
 * │                                                                           │
 * │ The palette moves to the shared selector for the same reason the markup    │
 * │ is shared: `--jv-void` and its siblings were defined ONLY under            │
 * │ `#view-cognition`, so Core's stage was drawing its gradients against       │
 * │ undefined custom properties. Both views are mounted by one module; they    │
 * │ now take their colours from one rule.                                      │
 * └───────────────────────────────────────────────────────────────────────────┘ */
/* Both stage hosts, one palette. One family — deep navy through cyan to white
   — with violet and a single warm accent held in reserve for the agents that
   use them. */
#view-core:not([hidden]),
#view-cognition:not([hidden]) {
  background: #04070c;
  --jv-void: #04070c;
  --jv-deep: #071626;
  --jv-cyan: 108 216 255;
  --jv-ink: #cfe4f2;
  --jv-dim: #5d7d96;
  --jv-hair: rgb(108 216 255 / 0.16);
  --jv-sans: ui-sans-serif, -apple-system, "Segoe UI", Inter, Roboto, "Helvetica Neue", Arial, sans-serif;
}

/* ┌───────────────────────────────────────────────────────────────────────────┐
 * │ B14-D161 · ⌘9 CORE IS FULL-BLEED *BELOW* THE CHROME, NOT A PANEL IN IT.   │
 * │                                                                           │
 * │ D154 above gave Core's stage a containing block by making the view         │
 * │ `position: relative`. That reached the defect — the navigation became      │
 * │ hit-testable — and overshot the design: `#main` is a centred 72rem column  │
 * │ with 1.5rem/3rem padding, so the scene was letterboxed into it.            │
 * │ `[MEASURED]` at 1440×900 the canvas came back 1121.76 × 630.99 at          │
 * │ (159.12, 114.44) — a panel in a text column, not an environment.           │
 * │                                                                           │
 * │ That is not what the rest of this stylesheet is built for. `.jv-rail`      │
 * │ already carries `bottom: 68px`, annotated "clears the shell footer, which  │
 * │ overlays the stage" — the footer is `position: fixed` and is EXPECTED to   │
 * │ lie over the scene, which is only true of a stage that runs to the         │
 * │ viewport edges. `.jv-notice` pins itself with `100vw`, and the responsive  │
 * │ block says "adapt the composition, do not shrink it".                      │
 * │                                                                           │
 * │ So the view keeps its own containing block — the chrome above it stays     │
 * │ reachable, which is the whole point of D154 — and `#main` stops being a    │
 * │ column *only while Core is showing*: it drops its width cap and padding    │
 * │ and becomes the flex child that takes the height the header and the nav    │
 * │ leave behind. No magic viewport fraction, nothing to re-tune when the nav  │
 * │ wraps to a second row, and every other view is untouched because the       │
 * │ `:has()` guard is the view's own `hidden` attribute.                       │
 * └───────────────────────────────────────────────────────────────────────────┘ */
body:has(#view-core:not([hidden])) {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}
body:has(#view-core:not([hidden])) #main {
  flex: 1 1 auto;
  display: flex;
  max-width: none;
  padding: 0;
  margin: 0;
  min-height: 0;
}
#view-core:not([hidden]) {
  position: relative;
  flex: 1 1 auto;
  min-height: 0;
  overflow: hidden;
}

/* ⌘8 Cognition — the deliberate full-screen takeover, left with Escape. */
#view-cognition:not([hidden]) {
  position: fixed;
  inset: 0;
  z-index: 40;
  margin: 0;
  padding: 0;
  overflow: hidden;
}

/* ── the stage ─────────────────────────────────────────────────────────── */

.jv-stage {
  position: absolute;
  inset: 0;
  overflow: hidden;
  /* A static wash under the canvas. It carries the composition for the two
     frames before GL is live, and it keeps the corners from going flat black
     once bloom is running. */
  background:
    radial-gradient(120% 90% at 50% 46%, rgb(19 71 116 / 0.42) 0%, rgb(8 26 48 / 0.34) 38%, rgb(4 7 12 / 0) 72%),
    radial-gradient(70% 60% at 76% 22%, rgb(96 78 190 / 0.14) 0%, rgb(4 7 12 / 0) 68%),
    radial-gradient(60% 55% at 16% 84%, rgb(20 120 140 / 0.10) 0%, rgb(4 7 12 / 0) 70%),
    var(--jv-void);
}

.jv-canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  touch-action: none;
}

/* A faint technical grid, well below the core in the hierarchy. Masked to the
   edges so it never competes with the centre. */
.jv-grid {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background-image:
    linear-gradient(to right, var(--jv-hair) 1px, transparent 1px),
    linear-gradient(to bottom, var(--jv-hair) 1px, transparent 1px);
  background-size: 92px 92px;
  opacity: 0.30;
  mask-image: radial-gradient(85% 75% at 50% 50%, transparent 32%, #000 88%);
  -webkit-mask-image: radial-gradient(85% 75% at 50% 50%, transparent 32%, #000 88%);
}

/* Scanline veil — 3% opacity. Invisible as an effect, present as a texture. */
.jv-veil {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: repeating-linear-gradient(to bottom, rgb(255 255 255 / 0.03) 0 1px, transparent 1px 3px);
  opacity: 0.5;
  mix-blend-mode: overlay;
}

/* ── arrival ───────────────────────────────────────────────────────────── */

/*
 * The transition the brief describes as BEOCORE → depth → environment. It is a
 * single CSS timeline on the stage so it cannot contend with the render loop
 * for the main thread, and it runs on transform/opacity/filter only, so it
 * stays on the compositor.
 */
/*
 * The class is applied to the stage ITSELF by index.js (mount() is handed the
 * stage element), so these are compound selectors, not descendant ones. Worth
 * stating because the descendant form silently matches nothing and leaves the
 * whole scene at opacity 0 while the render loop happily runs behind it —
 * which is exactly how this was found.
 */
.jv-stage { opacity: 0; transform: scale(1.14); filter: blur(14px) brightness(0.4); }

.jv-stage.jv-arrived {
  opacity: 1;
  transform: scale(1);
  filter: blur(0) brightness(1);
  transition:
    opacity 1150ms cubic-bezier(0.16, 0.9, 0.24, 1),
    transform 1650ms cubic-bezier(0.16, 0.9, 0.24, 1),
    filter 1250ms cubic-bezier(0.16, 0.9, 0.24, 1);
}

/* The HUD arrives after the scene, never with it. */
.jv-chrome { opacity: 0; transition: opacity 700ms ease 900ms; }
.jv-stage.jv-arrived .jv-chrome { opacity: 1; }

/* ── top chrome · BEOCORE ▸ JARVIS ─────────────────────────────────────── */

.jv-chrome { position: absolute; inset: 0; pointer-events: none; }
.jv-chrome > * { pointer-events: auto; }

.jv-top {
  position: absolute;
  top: 0; left: 0; right: 0;
  display: flex;
  align-items: center;
  gap: 1rem;
  padding: 1.15rem 1.5rem;
  padding-top: max(1.15rem, env(safe-area-inset-top, 0px));
}

.jv-brand {
  display: flex;
  align-items: baseline;
  gap: 0.6rem;
  font-family: var(--jv-sans);
  letter-spacing: 0.34em;
  font-size: 0.66rem;
  text-transform: uppercase;
  color: var(--jv-dim);
}
.jv-brand b {
  font-weight: 600;
  letter-spacing: 0.30em;
  font-size: 0.82rem;
  color: #eaf6ff;
  text-shadow: 0 0 22px rgb(var(--jv-cyan) / 0.55);
}
.jv-brand .jv-sep { color: rgb(var(--jv-cyan) / 0.55); letter-spacing: 0; }

.jv-exit {
  margin-left: auto;
  appearance: none;
  background: rgb(8 22 38 / 0.5);
  border: 1px solid var(--jv-hair);
  color: var(--jv-ink);
  font-family: var(--jv-sans);
  font-size: 0.64rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  padding: 0.5rem 0.95rem;
  border-radius: 2px;
  cursor: pointer;
  backdrop-filter: blur(8px);
  transition: border-color 200ms, color 200ms, background 200ms;
}
.jv-exit:hover, .jv-exit:focus-visible {
  border-color: rgb(var(--jv-cyan) / 0.5);
  background: rgb(14 42 68 / 0.6);
  color: #fff;
}

/* ── bottom HUD ────────────────────────────────────────────────────────── */

.jv-hud {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
  padding: 1.25rem 1.5rem;
  padding-bottom: max(1.25rem, env(safe-area-inset-bottom, 0px));
  font-family: var(--jv-sans);
  font-size: 0.6rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--jv-dim);
}

.jv-readout { display: flex; gap: 1.4rem; flex-wrap: wrap; align-items: center; }
.jv-readout span { position: relative; padding-left: 0.85rem; white-space: nowrap; }
.jv-readout span::before {
  content: "";
  position: absolute; left: 0; top: 0.42em;
  width: 4px; height: 4px; border-radius: 50%;
  background: rgb(var(--jv-cyan) / 0.75);
  box-shadow: 0 0 8px rgb(var(--jv-cyan) / 0.8);
}
.jv-readout .jv-src::before { background: #f0b954; box-shadow: 0 0 8px rgb(240 185 84 / 0.8); }

.jv-status { display: flex; align-items: center; gap: 0.55rem; color: var(--jv-ink); }
.jv-status .jv-pip {
  width: 6px; height: 6px; border-radius: 50%;
  background: rgb(var(--jv-cyan) / 1);
  box-shadow: 0 0 10px rgb(var(--jv-cyan) / 0.9);
  animation: jv-breathe 3.4s ease-in-out infinite;
}
@keyframes jv-breathe { 0%, 100% { opacity: 0.45; } 50% { opacity: 1; } }

/* ── agent labels · holographic tags ───────────────────────────────────── */

.jv-labels { position: absolute; inset: 0; pointer-events: none; }

.jv-label {
  --x: 50%; --y: 50%; --s: 1; --f: 0;
  --accent: 108 216 255;
  position: absolute;
  left: 0; top: 0;
  /* Pushed further below the orb than the first attempt: at 2.15rem the tag
     sat on top of the particle halo and both became unreadable. */
  transform: translate3d(calc(var(--x) - 50%), calc(var(--y) + 2.9rem * var(--s)), 0) scale(var(--s));
  transform-origin: 50% 0;
  pointer-events: auto;
  display: grid;
  grid-template-columns: auto auto;
  grid-template-rows: auto auto;
  column-gap: 0.5rem;
  row-gap: 0.18rem;
  align-items: center;
  justify-items: start;
  padding: 0;
  margin: 0;
  border: 0;
  background: none;
  cursor: pointer;
  font-family: var(--jv-sans);
  white-space: nowrap;
  /* Subtle until addressed (§ 12) — but 0.42 was not subtle, it was illegible
     against the bloom, which is a different thing. 0.66 reads at a glance and
     still recedes clearly next to a focused unit at 1.0. */
  opacity: calc(0.66 + 0.34 * var(--f));
  transition: opacity 260ms ease;
}

.jv-label-dot {
  grid-row: 1; grid-column: 1;
  width: 5px; height: 5px; border-radius: 50%;
  background: rgb(var(--accent) / 0.95);
  box-shadow: 0 0 calc(6px + 10px * var(--f)) rgb(var(--accent) / 0.9);
}
/* B20 · § 5 · THE DOT SPEAKS THE DATABASE'S VOCABULARY, NOT A SIMPLIFIED ONE.
 * It used to have two values, "active" and "idle", because data.js collapsed
 * the lifecycle column into a boolean before this file ever saw it. The real
 * domain has four (db/migrations/0080…sql line 114) and a suspended agent is
 * not an idle one, so each renders at its own weight and none of them is
 * invisible — § 4's floor applies to the label as much as to the sphere. */
.jv-label-dot[data-status="registered"] { background: rgb(var(--accent) / 0.52); }
.jv-label-dot[data-status="suspended"]  { background: rgb(var(--accent) / 0.38); }
.jv-label-dot[data-status="retired"]    { background: rgb(var(--accent) / 0.30); }
.jv-label-dot[data-status="unknown"]    { background: rgb(var(--accent) / 0.40); }
.jv-label-dot[data-status="registered"],
.jv-label-dot[data-status="suspended"],
.jv-label-dot[data-status="retired"],
.jv-label-dot[data-status="unknown"] { box-shadow: 0 0 calc(4px + 6px * var(--f)) rgb(var(--accent) / 0.5); }

.jv-label-name {
  grid-row: 1; grid-column: 2;
  /* 0.66rem scaled by --s (down to 0.55) rendered at ~6px on a distant unit,
     which is not a label, it is a smudge. Larger type with tighter tracking
     reads at every depth the camera reaches. */
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.2em;
  color: #eaf6ff;
  /* Two shadows, and the dark one is load-bearing. A unit whose orbit carries
     it in front of the core has its label sitting on the brightest part of the
     frame, where pale type on pale bloom disappears. The tight dark halo keeps
     the name readable at every depth; the accent glow is the holographic part. */
  text-shadow:
    0 0 3px rgb(2 8 16 / 0.95),
    0 0 8px rgb(2 8 16 / 0.85),
    0 0 calc(10px + 16px * var(--f)) rgb(var(--accent) / calc(0.35 + 0.5 * var(--f)));
}

/* The rule grows out of the dot as attention arrives. */
.jv-label-rule {
  grid-row: 2; grid-column: 1 / -1;
  height: 1px;
  width: calc(14px + 58px * var(--f));
  background: linear-gradient(to right, rgb(var(--accent) / 0.85), rgb(var(--accent) / 0));
  transition: width 320ms cubic-bezier(0.16, 0.9, 0.24, 1);
}

.jv-label-role {
  grid-row: 3; grid-column: 1 / -1;
  font-size: 0.5rem;
  letter-spacing: 0.24em;
  color: rgb(var(--accent) / 0.85);
  opacity: var(--f);
  transform: translateY(calc(-3px + 3px * var(--f)));
  transition: opacity 260ms ease, transform 260ms ease;
}

/* When one unit has attention, the others recede rather than vanish. */
.jv-labels[data-focused="true"] .jv-label[data-state="dim"] { opacity: 0.16; }

.jv-label:focus-visible {
  outline: 1px solid rgb(var(--accent) / 0.9);
  outline-offset: 6px;
  border-radius: 2px;
}

/* ── context-lost notice ───────────────────────────────────────────────── */

.jv-stage[data-mode="lost"]::after,
#view-cognition [data-mode="lost"] .jv-canvas + .jv-lost { display: block; }

/* ── responsive · adapt the composition, do not shrink it ──────────────── */

@media (max-width: 900px) {
  .jv-top, .jv-hud { padding-left: 1rem; padding-right: 1rem; }
  .jv-brand { letter-spacing: 0.24em; font-size: 0.58rem; }
  .jv-brand b { font-size: 0.74rem; }
  .jv-label-name { font-size: 0.6rem; letter-spacing: 0.2em; }
}

@media (max-width: 620px) {
  /* The readout would wrap into two rows and crowd the core; the identity
     line and the live pip survive, the diagnostics do not. */
  .jv-readout .jv-fps, .jv-readout .jv-mode { display: none; }
  .jv-label-role { display: none; }
  .jv-hud { font-size: 0.55rem; }
}

/* ── reduced motion ────────────────────────────────────────────────────── */

@media (prefers-reduced-motion: reduce) {
  /* The scene itself slows down in JS (motion = 0.12). Here the chrome simply
     stops moving: the structure, the labels and every name stay exactly as
     legible as they were. */
  .jv-stage,
  .jv-stage.jv-arrived {
    transition: none;
    opacity: 1;
    transform: none;
    filter: none;
  }
  .jv-chrome, .jv-stage.jv-arrived .jv-chrome { transition: none; opacity: 1; }
  .jv-status .jv-pip { animation: none; opacity: 0.9; }
  .jv-label, .jv-label-rule, .jv-label-role { transition: none; }
}

/* ── B18-D5 · the empty and error states ───────────────────────────────────
 *
 * The scene is one of three outcomes and the other two get real design rather
 * than a console message. They share a plate so the view never looks broken;
 * only the accent and the wording distinguish "initialised and empty" from
 * "could not ask". Centred on the stage rather than in the HUD's corner,
 * because when there is no scene there is nothing for a corner to sit beside.
 */
.jv-notice {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  max-width: min(46ch, calc(100vw - 3rem));
  padding: 1.6rem 1.8rem;
  border-radius: 14px;
  border: 1px solid rgba(120, 170, 220, 0.18);
  background: linear-gradient(180deg, rgba(14, 22, 34, 0.92), rgba(9, 14, 22, 0.94));
  box-shadow: 0 24px 70px rgba(0, 0, 0, 0.55);
  text-align: left;
  pointer-events: auto;
}
.jv-notice-title {
  margin: 0 0 0.55rem;
  font-size: 0.98rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: #dbe9fb;
}
.jv-notice-body {
  margin: 0;
  font-size: 0.85rem;
  line-height: 1.55;
  color: #8ea6c4;
}
.jv-notice-detail {
  margin: 0.7rem 0 0;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.72rem;
  color: #6b8099;
  word-break: break-word;
}
/* Empty is not a failure: a calm cyan rule, the same family as the core. */
.jv-notice-empty  { border-left: 2px solid rgba(90, 200, 245, 0.55); }
/* Error is: amber, and deliberately a different colour from anything in the scene. */
.jv-notice-error  { border-left: 2px solid rgba(240, 175, 90, 0.75); }
.jv-notice-error .jv-notice-title { color: #f2d2a4; }

/* ┌───────────────────────────────────────────────────────────────────────────┐
 * │ B22-D1 · WITH A SCENE BEHIND IT, THE NOTICE GETS OUT OF THE CENTRE.       │
 * │                                                                           │
 * │ The rule above centres the card, and the comment over it says why: "when  │
 * │ there is no scene there is nothing for a corner to sit beside". That was  │
 * │ true while an inspection failure returned before `createScene()` — the    │
 * │ stage really was empty, so the middle was the only sensible place.        │
 * │                                                                           │
 * │ Now the core renders in every one of these states, and dead centre is     │
 * │ exactly where it is drawn. Leaving the card there would have fixed the    │
 * │ globe and then covered it with the message about why the globe was not    │
 * │ there — visually indistinguishable, to the person reporting it, from not  │
 * │ having fixed it at all.                                                   │
 * │                                                                           │
 * │ Keyed on `data-mode`, which index.js sets to `webgl` or `fallback` only   │
 * │ once an engine exists. So the centred placement survives for the one case │
 * │ that still has no scene — no GL context and no 2D fallback either.        │
 * └───────────────────────────────────────────────────────────────────────────┘ */
.jv-stage[data-mode="webgl"] .jv-notice,
.jv-stage[data-mode="fallback"] .jv-notice {
  left: auto;
  top: auto;
  right: 1.25rem;
  bottom: 1.25rem;
  transform: none;
  max-width: min(38ch, calc(100vw - 2.5rem));
  padding: 1.1rem 1.25rem;
}
/* On a phone the corner is the full width, because 38ch beside a 375px stage
 * is the whole stage anyway and a half-width card there just looks broken. */
@media (max-width: 640px) {
  .jv-stage[data-mode="webgl"] .jv-notice,
  .jv-stage[data-mode="fallback"] .jv-notice {
    left: 0.75rem;
    right: 0.75rem;
    bottom: 0.75rem;
    max-width: none;
  }
}


/* ┌───────────────────────────────────────────────────────────────────────────┐
 * │ B19-D1 / B19-D3 · ⌘9 JARVIS · Core                                        │
 * │                                                                           │
 * │ The stage, the HUD and the arrival timeline are shared with ⌘8 and are    │
 * │ defined above. What follows is only what Core adds: a control contract on │
 * │ the canvas, one status rail, and the CORE tag.                            │
 * └───────────────────────────────────────────────────────────────────────────┘ */

/* THE SINGLE MOST IMPORTANT LINE HERE. Without `touch-action: none` a one-
 * finger drag scrolls the page instead of orbiting the scene, and a pinch
 * zooms the document rather than the camera — so on a phone the environment
 * would look interactive and refuse to be interacted with. */
.jv-canvas {
  touch-action: none;
  cursor: grab;
}
.jv-stage[data-grab="on"] .jv-canvas { cursor: grabbing; }
/* No pointer control exists in the 2D fallback, so it must not advertise one. */
.jv-stage[data-mode="fallback"] .jv-canvas,
.jv-stage[data-mode="lost"] .jv-canvas { cursor: default; touch-action: auto; }

.jv-coretag {
  font-size: 10px;
  letter-spacing: .28em;
  color: rgba(190, 220, 255, .62);
  border: 1px solid rgba(120, 170, 255, .22);
  border-radius: 999px;
  padding: 3px 10px;
}

/* ── the one rail ─────────────────────────────────────────────────────────
 * Anchored bottom-left, above the HUD line, and deliberately narrow: the brief
 * for this screen is that the environment is the interface and the panel
 * supports it. `pointer-events: none` on the text with `auto` on nothing at
 * all — the rail is a readout, and a readout that swallows drags turns a
 * quarter of the stage into dead space for the control that matters most. */
.jv-rail {
  position: absolute;
  left: 22px;
  bottom: 74px;
  width: 226px;
  padding: 16px 18px 14px;
  border: 1px solid rgba(120, 170, 255, .16);
  border-radius: 14px;
  background: linear-gradient(180deg, rgba(10, 16, 30, .72), rgba(6, 10, 20, .80));
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  box-shadow: 0 24px 70px -34px rgba(0, 0, 0, .9);
  pointer-events: none;
  opacity: 0;
  transform: translateY(8px);
  transition: opacity .7s ease .45s, transform .7s ease .45s;
}
.jv-stage.jv-arrived .jv-rail { opacity: 1; transform: none; }

.jv-rail-h {
  margin: 0 0 8px;
  font-size: 9px;
  font-weight: 600;
  letter-spacing: .26em;
  color: rgba(150, 185, 235, .58);
}
/* app.css already gives `dl` a two-column grid, right-aligned `dd`, and the
 * dd[data-ok] health colours. Only the scale and the palette are restated here
 * — re-declaring the layout would be a second definition of a thing the shell
 * already decides, and the two would drift. */
.jv-rail-h + .jv-rail-list { margin-bottom: 14px; }
.jv-rail-list { gap: 4px 12px; }
.jv-rail-list dt { font-size: 11px; color: rgba(198, 216, 242, .56); }
.jv-rail-list dd {
  font-size: 11px;
  font-variant-numeric: tabular-nums;   /* so a changing number does not jitter */
  color: rgba(226, 238, 255, .90);
  white-space: nowrap;                  /* "3 ms" is one value, not two lines */
}

.jv-rail-hint {
  margin: 12px 0 0;
  font-size: 10px;
  line-height: 1.5;
  color: rgba(160, 190, 225, .42);
}
/* Nothing to hint at when there is nothing to drag. */
.jv-stage[data-mode="fallback"] .jv-rail-hint,
.jv-stage[data-mode="lost"] .jv-rail-hint { display: none; }

/* ── small screens ────────────────────────────────────────────────────────
 * The Core stays; the rail stops being a floating panel and becomes a strip
 * under the HUD, because 226px of overlay on a 390px screen is a curtain. */
/* ── small screens ────────────────────────────────────────────────────────
 * The brief for this screen is that the environment is the interface, and a
 * panel that eats a third of a phone is not supporting it. `[MEASURED]` at
 * 390×844 the full rail ran 538→784px and sat on top of two agent labels.
 * So the phone gets the SYSTEM block and the agent count and nothing else:
 * the four zero-valued cognitive rows are the least useful thing on a screen
 * where space is the scarce resource, and ⌘8 still has all of them. */
@media (max-width: 720px) {
  .jv-rail {
    left: 12px;
    right: 12px;
    bottom: 68px;        /* clears the shell footer, which overlays the stage */
    width: auto;
    padding: 10px 14px;
  }
  /* Everything from the Experiences row down, plus the pointer hint — there is
   * no wheel to scroll and no cursor to grab with. */
  .jv-rail-list dd[data-rail="experiences"] ~ *,
  .jv-rail-list dt:has(+ dd[data-rail="experiences"]),
  .jv-rail-list dd[data-rail="experiences"],
  .jv-rail-hint { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  .jv-rail { transition: none; opacity: 1; transform: none; }
}


/* B19-D7 · the name steps aside; the orb does not. Opacity rather than
 * `display:none` so a node drifting past the core fades instead of blinking,
 * and the transition is short enough that it never reads as a delay. */
.jv-label[data-occluded="true"] .jv-label-name,
.jv-label[data-occluded="true"] .jv-label-role,
.jv-label[data-occluded="true"] .jv-label-rule { opacity: 0; }
.jv-label .jv-label-name,
.jv-label .jv-label-role,
.jv-label .jv-label-rule { transition: opacity .28s ease; }

/* And when a name IS over bright geometry it stays legible on its own terms,
 * rather than relying on whatever happens to be behind it. */
.jv-label-name { text-shadow: 0 1px 10px rgba(2, 6, 16, .92), 0 0 2px rgba(2, 6, 16, .8); }

@media (prefers-reduced-motion: reduce) {
  .jv-label .jv-label-name,
  .jv-label .jv-label-role,
  .jv-label .jv-label-rule { transition: none; }
}
