/* screen: boarding, the analog embarkation ritual.
   Voxel pass (ui-fluide pass, D-022 pilot pattern copied from shop.css): the
   real HD scene (app/js/voxel/scenes/boarding.js) now mounts on .board-scene,
   behind the existing CSS hall diorama below, which doubles as the token
   placeholder while the bake runs and the D-013 rule 1 fallback. */

.board { display: flex; flex-direction: column; gap: 16px; }

/* ---------- header ---------- */

.board-head { display: flex; flex-direction: column; gap: 6px; }
.board-head h1 { font-size: 26px; margin-top: 2px; }
.board-sub { color: var(--ink-soft); font-weight: 600; font-size: 14.5px; max-width: 56ch; }
.board-context { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 4px; }

/* ---------- layout ---------- */

.board-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(280px, 340px);
  gap: 16px;
  align-items: start;
}
.board-main { display: flex; flex-direction: column; gap: 16px; min-width: 0; }

/* ================= boarding pass ================= */

.board-pass {
  position: relative;
  overflow: hidden;
  border-radius: var(--r-xl);
  background: var(--card);
  border: 1px solid var(--line-soft);
  box-shadow: var(--shadow-2);
  cursor: pointer;
  transition: transform .2s ease, box-shadow .2s ease;
}
.board-pass:hover { transform: translateY(-2px); box-shadow: var(--shadow-2), var(--shadow-1); }
.board-pass:active { transform: translateY(0); }
.board-pass:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

/* a slow sheen crossing the ticket: it reads as "tap me" without a label */
.board-pass::after {
  content: ''; position: absolute; inset: 0; z-index: 1;
  pointer-events: none;
  background: linear-gradient(112deg,
    transparent 42%, color-mix(in srgb, var(--gold-soft) 85%, transparent) 50%, transparent 58%);
  transform: translateX(-130%);
  animation: board-pass-shine 7s ease-in-out infinite;
}
@keyframes board-pass-shine {
  0%, 62% { transform: translateX(-130%); }
  92%, 100% { transform: translateX(130%); }
}

.board-pass-banner {
  display: flex; align-items: center; gap: 10px;
  padding: 13px 20px;
  font-weight: 900; font-size: 13.5px; letter-spacing: .08em; text-transform: uppercase;
  color: var(--accent-ink);
}
.board-pass-banner-ico { font-size: 17px; }

.board-pass--blue .board-pass-banner { background: var(--blue); }
.board-pass--warm .board-pass-banner { background: var(--warm); }
.board-pass--violet .board-pass-banner { background: var(--violet); }
.board-pass--gold .board-pass-banner { background: var(--gold); color: var(--ink); }

/* Night pass: a full-bleed band of a night accent at 100% saturation is the
   loudest surface on the screen and fights the softened indigo everything else
   sits on. Bleed each band toward the card so it stays clearly coloured but
   stops shouting (D-002: dark is the second voice, never the neon one). */
[data-theme="dark"] .board-pass--blue .board-pass-banner { background: color-mix(in srgb, var(--blue) 72%, var(--card) 28%); }
[data-theme="dark"] .board-pass--warm .board-pass-banner { background: color-mix(in srgb, var(--warm) 72%, var(--card) 28%); }
[data-theme="dark"] .board-pass--violet .board-pass-banner { background: color-mix(in srgb, var(--violet) 68%, var(--card) 32%); }
[data-theme="dark"] .board-pass--gold .board-pass-banner {
  background: color-mix(in srgb, var(--gold) 62%, var(--card) 38%);
  color: var(--bg);
}

.board-pass-body { padding: 18px 20px 20px; }

.board-pass-route { display: flex; align-items: center; gap: 10px; }
.board-pass-point { flex: 1; min-width: 0; }
.board-pass-point b { display: flex; align-items: center; gap: 6px; font-size: 19px; font-weight: 900; letter-spacing: -.2px; overflow-wrap: anywhere; }
.board-pass-point small { display: block; color: var(--muted); font-weight: 700; font-size: 11.5px; margin-top: 1px; }
.board-pass-point--to { text-align: right; }
.board-pass-point--to b { justify-content: flex-end; }
.board-pass-kicker {
  font-size: 10px; font-weight: 900; letter-spacing: .09em; text-transform: uppercase; color: var(--muted);
}

.board-pass-link {
  position: relative;
  display: flex; flex-direction: column; align-items: center; gap: 3px;
  width: 70px; flex: 0 0 auto;
}
.board-pass-link::before {
  content: ''; position: absolute; top: 50%; left: 0; right: 0;
  border-top: 2px dashed var(--line); z-index: 0;
}
.board-pass-plane { position: relative; z-index: 1; background: var(--card); padding: 0 6px; font-size: 18px; }
.board-pass-stop {
  position: relative; z-index: 1;
  font-size: 8.5px; font-weight: 900; text-transform: uppercase; letter-spacing: .04em;
  color: var(--muted); background: var(--card);
  padding: 2px 7px; border-radius: 999px; border: 1px solid var(--line-soft);
}

.board-pass-perf { position: relative; height: 0; margin: 12px -20px; border-top: 2px dashed var(--line); }
.board-pass-perf::before, .board-pass-perf::after {
  content: ''; position: absolute; top: 50%; transform: translateY(-50%);
  width: 20px; height: 20px; border-radius: 50%; background: var(--bg);
}
.board-pass-perf::before { left: -10px; }
.board-pass-perf::after { right: -10px; }

.board-pass-meta {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(66px, 1fr));
  gap: 10px 14px; padding: 4px 0;
}
.board-pass-meta-cell span {
  display: block; font-size: 9.5px; font-weight: 900; letter-spacing: .08em; text-transform: uppercase;
  color: var(--muted); margin-bottom: 2px;
}
.board-pass-meta-cell b { font-size: 15px; font-weight: 800; color: var(--ink); }

.board-pass-foot { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding-top: 4px; }
.board-pass-pax { display: flex; align-items: center; gap: 9px; }
.board-pass-pax b { display: block; font-size: 13px; font-weight: 800; }
.board-pass-pax small { display: block; font-size: 10.5px; color: var(--muted); font-weight: 700; }
.board-pass-barcode {
  width: 108px; height: 32px; border-radius: 3px; opacity: .85;
  background: repeating-linear-gradient(90deg, var(--ink) 0 2px, transparent 2px 5px);
}

.board-pass-stamp {
  position: absolute; top: 16px; right: 18px;
  width: 84px; height: 84px; border-radius: 50%;
  display: grid; place-items: center; text-align: center;
  border: 3px solid var(--danger); color: var(--danger);
  font-size: 10.5px; font-weight: 900; letter-spacing: .05em; text-transform: uppercase; line-height: 1.3;
  transform: rotate(-16deg) scale(0); opacity: 0; pointer-events: none;
  transition: transform .35s cubic-bezier(.34,1.56,.64,1), opacity .25s ease;
}
.board-pass-stamp span { display: block; font-size: 15px; }
.board-pass-stamp.is-stamped { transform: rotate(-16deg) scale(1); opacity: .92; }

/* ================= ritual ================= */

/* minmax(0, 1fr), not 1fr: the step cards carry a seat grid, whose min-content
   was pushing the row past the column at ~800px.
   align-items: start, because a CSS grid row stretches every cell to match its
   tallest sibling by default. Only the "choose seat" step has real body
   content (the seat grid); with stretch, the other three simple icon+title
   steps were forced up to that same ~180px height and sat with a big empty
   gap below their title. Starting each card at its own content height fixes
   that without touching the seat-grid card's needed room. */
.board-ritual { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 12px 20px; align-items: start; }
.board-ritual--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }

.board-step {
  position: relative;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px;
  background: var(--card);
  border: 1.5px solid var(--line-soft);
  border-radius: var(--r-lg);
  padding: 13px 10px 11px;
  text-align: center;
  box-shadow: var(--shadow-1);
  transition: border-color .2s ease, background .2s ease, opacity .2s ease, transform .2s ease;
}
.board-step-num {
  position: absolute; top: 8px; right: 8px;
  width: 20px; height: 20px; border-radius: 50%;
  display: grid; place-items: center;
  font-size: 10.5px; font-weight: 900;
  background: var(--bg-2); color: var(--ink-soft);
  transition: background .2s ease, color .2s ease;
}
.board-step-ico { font-size: 24px; line-height: 1; }
.board-step h4 { font-size: 12.5px; margin-top: 2px; }

.board-step--todo { opacity: .55; }
.board-step--active {
  border-color: var(--accent); background: var(--accent-soft);
  animation: board-step-pulse 2.2s ease-in-out infinite;
}
.board-step--active .board-step-num { background: var(--accent); color: var(--accent-ink); }
.board-step--done { border-color: var(--success); opacity: 1; }
.board-step--done .board-step-num { background: var(--success); color: var(--accent-ink); }
.board-step--clickable { cursor: pointer; }

@keyframes board-step-pulse {
  0%, 100% { box-shadow: var(--shadow-1); transform: translateY(0); }
  50% { box-shadow: var(--shadow-1), 0 0 0 5px color-mix(in srgb, var(--accent) 18%, transparent); transform: translateY(-1px); }
}

.board-step:not(:last-child)::after {
  content: '→'; position: absolute; top: 26px; right: -16px;
  font-size: 15px; font-weight: 900; color: var(--muted);
}

.board-seats {
  display: grid; grid-template-columns: repeat(4, 1fr); gap: 3px;
  margin: 6px auto 0; max-width: 96px;
}
.board-seat {
  aspect-ratio: 1; border-radius: 4px;
  background: var(--card-2); border: 1px solid var(--line-soft);
  font-size: 8px; font-weight: 900; color: var(--accent-ink);
  display: grid; place-items: center;
  transition: background .15s ease, transform .15s ease, border-color .15s ease;
}
.board-seat:hover { transform: scale(1.12); }
.board-seat.is-window { background: var(--blue-soft); }
.board-seat.is-selected { background: var(--accent); border-color: var(--accent); }

/* ================= scene: airport hall ================= */
/* 4 depth layers: sky (clouds + crossing flight) → distant skyline →
   glass terminal wall → ground (seats, plant, traveler, luggage). */

.board-scene {
  --scene-sky-a: #CFE3F7;
  --scene-sky-b: #FFF3DC;
  --scene-ground: #E7DCC5;
  --scene-cloud: #FFFFFF;
  --scene-trail: rgba(255, 255, 255, .8);
  --scene-building: #B9AA82;
  --scene-mullion: rgba(255, 255, 255, .65);
  --scene-glass: rgba(255, 255, 255, .38);
  --scene-seat-a: #6E93E8;
  --scene-seat-b: #3F6FD8;
  --scene-figure-skin: #E8B48C;
  --scene-figure-hair: #6B4226;
  --scene-board-a: #1C2030;
  --scene-board-b: #10131C;
  --scene-board-text: #F5C84A;
  --scene-board-label: #FFFFFF;
  position: relative;
  min-height: 380px;
  background:
    linear-gradient(180deg, var(--scene-sky-a) 0%, var(--scene-sky-b) 58%, var(--scene-ground) 58%, var(--scene-ground) 100%);
}
:root[data-theme="dark"] .board-scene {
  --scene-sky-a: #211d47;
  --scene-sky-b: #2b2758;
  --scene-ground: #1a1738;
  --scene-cloud: rgba(255, 255, 255, .5);
  --scene-trail: rgba(210, 200, 255, .45);
  --scene-building: #362F5E;
  --scene-mullion: rgba(200, 190, 255, .32);
  --scene-glass: rgba(160, 150, 255, .16);
  --scene-seat-a: #9384FF;
  --scene-seat-b: #7A6CFF;
  --scene-figure-skin: #F0C199;
  --scene-figure-hair: #4A2F1F;
  --scene-board-a: #14111F;
  --scene-board-b: #0B0916;
  --scene-board-text: #F2C14E;
  --scene-board-label: #E8E3FF;
}

/* ---- voxel canvas mount (ui-fluide pass layer 1, D-022 dérogation) ----
   boarding.js mounts the real HD scene on this same .board-scene element,
   directly (not inside #board-hall-zone below): mountVoxel appends the
   canvas as .board-scene's own last child. The canvas starts invisible and
   only fades in once boarding.js adds .is-baked to .board-scene itself, by
   which point mountVoxel's synchronous rebuild()+start() has already run
   (engine.js), so the canvas holds real pixels the instant the class lands.
   Until then the CSS hall diorama below shows through underneath it. */
.board-scene .scene-canvas {
  opacity: 0;
  transition: opacity .22s cubic-bezier(0, 0, 0.2, 1);
}
.board-scene.is-baked .scene-canvas { opacity: 1; }
/* Same pattern as vote.css's .vote-scene-art.is-baked rule: once the canvas
   holds real pixels, the CSS hall diorama (#board-hall-zone, this element's
   only other direct child) is dropped from rendering entirely, not just
   covered. Left visible-but-hidden-behind-canvas, its animations kept
   running forever for zero visual benefit. */
.board-scene.is-baked > *:not(.scene-canvas) { display: none; }

/* D-013 rule 2: the canvas is appended after #board-hall-zone, so without an
   explicit z-index it paints over .board-hall-gate and .board-hall-status
   (both .scene-float, position:absolute with z-index:auto). Same motif as
   trip-train.css's `.trip-train-scene > .scene-float { z-index: 2; }`. */
.board-scene .scene-float { z-index: 2; }

.board-hall { position: absolute; inset: 0; }

/* ---- layer 1: sky ---- */
.board-hall-sky { position: absolute; inset: 0 0 42% 0; overflow: hidden; }

/* This whole sky sits under the .board-hall-zone diorama, which is the
   token placeholder shown only for the ~300ms bake and dropped from
   rendering the instant .is-baked lands (see .board-scene.is-baked above):
   never a permanent decor. Sun glow and cloud drift were purely decorative
   background flourishes outside the reading area, so they read as fixed
   set pieces rather than looping animations (doctrine: a decorative loop
   hors du champ de l'attention devient un etat statique). */
.board-hall-sun {
  position: absolute; top: -14%; right: 8%;
  width: 48%; aspect-ratio: 1; border-radius: 50%;
  background: radial-gradient(circle,
    color-mix(in srgb, var(--gold) 55%, transparent) 0%, transparent 66%);
  opacity: .85;
}

.board-hall-cloud {
  position: absolute; height: 13px; border-radius: 999px;
  background: color-mix(in srgb, var(--scene-cloud) 88%, var(--scene-sky-b));
  opacity: .85;
}
.board-hall-cloud.c1 { top: 16%; left: 8%; width: 56px; }
.board-hall-cloud.c2 { top: 40%; left: 46%; width: 38px; height: 10px; }
.board-hall-cloud.c3 { top: 6%; left: 68%; width: 32px; height: 9px; }

/* the transport icon takes off across the sky, trailing a fading contrail:
   works for the plane and reads as a whimsical departure motif for the rest. */
.board-hall-flight { position: absolute; left: -6%; bottom: 55%; font-size: 19px; animation: board-hall-flight-path 13s ease-in-out infinite; }
.board-hall-flight-ico { display: block; filter: drop-shadow(var(--shadow-1)); }
/* The trail rides the same transform as its .board-hall-flight parent (one
   animation, not two): a fixed dash behind the icon, not its own loop. */
.board-hall-flight::before {
  content: ''; position: absolute; right: 100%; top: 50%; width: 48px; height: 0;
  border-top: 2px dashed var(--scene-trail);
  transform-origin: right center; opacity: .6;
}
@keyframes board-hall-flight-path {
  0% { transform: translate(0, 0) rotate(6deg); opacity: 0; }
  10% { opacity: 1; }
  85% { opacity: 1; }
  100% { transform: translate(300%, -125%) rotate(-8deg); opacity: 0; }
}

/* ---- layer 2: distant terminal silhouette ---- */
.board-hall-skyline {
  position: absolute; left: -4%; right: -4%; bottom: 42%; height: 26%;
  background: color-mix(in srgb, var(--scene-ground) 62%, var(--ink) 12%);
  opacity: .5;
  clip-path: polygon(0 100%, 0 62%, 12% 62%, 12% 34%, 27% 34%, 27% 58%,
    44% 58%, 44% 22%, 60% 22%, 60% 50%, 76% 50%, 76% 30%, 90% 30%, 90% 66%, 100% 66%, 100% 100%);
}

/* ---- layer 3: the terminal's glass wall, a real bay window (3 panes x 2
   rows) the sky and crossing flight sit behind, not a pale even quadrille. ---- */
.board-hall-glass {
  position: absolute; inset: 6% 5% 39% 5%;
  overflow: hidden;
  border-radius: var(--r-lg) var(--r-lg) 0 0;
  border: 3px solid var(--scene-building);
  border-bottom: none;
  background: linear-gradient(200deg, var(--scene-glass), transparent 72%);
}
.board-hall-glass-bar { position: absolute; background: var(--scene-building); }
.board-hall-glass-bar--v1 { top: 0; bottom: 0; left: 33%; width: 3px; }
.board-hall-glass-bar--v2 { top: 0; bottom: 0; left: 66%; width: 3px; }
.board-hall-glass-bar--h { left: 0; right: 0; top: 54%; height: 3px; }

.board-hall-panel {
  position: absolute;
  display: flex; flex-direction: column; gap: 3px;
  padding: 9px 13px;
}
.board-hall-label { font-size: 9.5px; font-weight: 900; letter-spacing: .08em; text-transform: uppercase; color: var(--muted); }
.board-hall-panel b { font-size: 15px; font-weight: 900; }

/* the gate board reads as its own lit sign: dark glass, glowing digits,
   regardless of theme, like a real departure display. */
.board-hall-gate {
  /* anchored well inside the window's top-left pane (which starts at 6%/5%),
     not at the frame corner, so it reads as mounted on the wall, never
     straddling the glass edge above a sliver of open sky. */
  top: 15%; left: 11%;
  background: linear-gradient(160deg, var(--scene-board-a), var(--scene-board-b));
  border-color: color-mix(in srgb, var(--scene-board-text) 35%, transparent);
  backdrop-filter: none;
}
.board-hall-gate .board-hall-label { color: color-mix(in srgb, var(--scene-board-text) 65%, var(--scene-board-label) 35%); }
.board-hall-gate b {
  color: var(--scene-board-text);
  animation: board-hall-gate-blink 3.4s ease-in-out infinite;
}
@keyframes board-hall-gate-blink {
  0%, 100% { opacity: 1; text-shadow: 0 0 8px color-mix(in srgb, var(--scene-board-text) 55%, transparent); }
  50% { opacity: .78; text-shadow: 0 0 0 transparent; }
}

.board-hall-status { top: 15%; right: 10%; align-items: flex-end; text-align: right; max-width: 60%; }
.board-hall-status b { font-size: 12.5px; display: flex; align-items: center; gap: 5px; justify-content: flex-end; }

/* ---- layer 4: ground, a floor that recedes to the window's vanishing
   point, with the bench and the traveler grouped as two small foreground
   clusters instead of six seats and two bags marching in one even line. ---- */
.board-hall-ground { position: absolute; left: 0; right: 0; bottom: 0; height: 42%; }

.board-hall-floor {
  position: absolute; inset: 0; overflow: hidden;
  clip-path: polygon(35% 0%, 65% 0%, 100% 100%, 0% 100%);
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--scene-ground) 55%, var(--scene-sky-b) 35%) 0%,
    var(--scene-ground) 62%);
}
.board-hall-floor-line {
  position: absolute; left: 50%; height: 2px; border-radius: 2px;
  background: color-mix(in srgb, var(--scene-mullion) 70%, transparent);
  transform: translateX(-50%);
}
.board-hall-floor-line.l1 { top: 16%; width: 40%; opacity: .55; }
.board-hall-floor-line.l2 { top: 45%; width: 64%; opacity: .4; }
.board-hall-floor-line.l3 { top: 76%; width: 86%; opacity: .28; }

.board-hall-bench {
  position: absolute; left: 6%; bottom: 10%;
  display: flex; align-items: flex-end; gap: 6px;
  transform: scale(.9); transform-origin: bottom left;
}
.board-hall-plant { font-size: 26px; margin-bottom: 2px; filter: drop-shadow(var(--shadow-1)); }

.board-hall-seats { display: flex; gap: 2px; margin-bottom: 2px; padding-bottom: 3px; border-bottom: 3px solid color-mix(in srgb, var(--scene-building) 55%, transparent); }
.board-hall-seat {
  width: 26px; height: 21px; border-radius: 7px 7px 3px 3px;
  background: linear-gradient(180deg, var(--scene-seat-a), var(--scene-seat-b));
  box-shadow: var(--shadow-1);
}

.board-hall-traveler {
  position: absolute; right: 9%; bottom: 0;
  display: flex; align-items: flex-end; gap: 6px;
}

/* a small readable person: cap + head + jacket + legs, stacked so the head
   reads as ~35% of the silhouette, plus a backpack peeking from behind. */
.board-hall-figure { position: relative; display: flex; flex-direction: column; align-items: center; margin-bottom: 2px; }
.board-hall-figure-pack {
  position: absolute; z-index: -1; top: 15px; left: 50%; transform: translateX(-50%);
  width: 26px; height: 21px; border-radius: 8px;
  background: var(--accent);
}
.board-hall-figure-head {
  position: relative;
  width: 16px; height: 16px; border-radius: 50%;
  background: var(--scene-figure-skin);
}
.board-hall-figure-head::before {
  content: ''; position: absolute; top: -2px; left: -1px; right: -1px; height: 8px;
  border-radius: 8px 8px 2px 2px;
  background: var(--scene-figure-hair);
}
.board-hall-figure-body {
  width: 20px; height: 20px;
  clip-path: polygon(18% 0, 82% 0, 100% 100%, 0% 100%);
  background: var(--warm);
}
.board-hall-figure-legs { position: relative; width: 20px; height: 10px; }
.board-hall-figure-legs::before, .board-hall-figure-legs::after {
  content: ''; position: absolute; top: 0; width: 7px; height: 10px;
  border-radius: 0 0 4px 4px;
  background: var(--blue);
}
.board-hall-figure-legs::before { left: 1px; }
.board-hall-figure-legs::after { right: 1px; }

.board-hall-luggage { display: flex; align-items: flex-end; gap: 4px; }
.board-hall-bag { filter: drop-shadow(var(--shadow-1)); }
.board-hall-bag--big { font-size: 24px; }
.board-hall-bag--small { font-size: 16px; }

/* ================= footer ================= */

.board-footer {
  display: flex; flex-direction: column; align-items: center; gap: 6px;
  margin: 4px auto 0; width: 100%; max-width: 360px;
}
.board-footer-hint { font-size: 12px; font-weight: 700; }

/* ================= responsive ================= */

/* the pass is dense (two place names + 4 meta cells): it needs the full width
   well before the 860px the sidebar-less layout suggests. */
@media (max-width: 1000px) {
  .board-layout { grid-template-columns: 1fr; }
  .board-scene { min-height: 260px; }
}

/* between one and four across: two per row reads better than four 150px slivers */
@media (max-width: 880px) {
  .board-ritual, .board-ritual--3 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .board-step:not(:last-child)::after { display: none; }
}

@media (max-width: 720px) {
  .board-ritual, .board-ritual--3 { grid-template-columns: 1fr; gap: 10px; }
  .board-step:not(:last-child)::after {
    display: block;
    content: '↓'; top: auto; bottom: -14px; left: 50%; right: auto; transform: translateX(-50%);
  }
}

/* Only 4 infinite animations remain in this file: the pass sheen, the
   active-step pulse, the gate blink and the flight path (the sun, the 3
   clouds and the trail were turned into a static decor above, so they need
   no entry here at all). Each selector below must out-specificity (or at
   least match) the rule that actually sets that element's animation, or
   the reduce-motion override loses the cascade and the loop keeps running
   for good, exactly the bug this replaces (.board-hall-cloud alone used to
   lose against the more specific .board-hall-cloud.c1/.c2/.c3). */
@media (prefers-reduced-motion: reduce) {
  .board-pass::after,
  .board-step--active,
  .board-hall-gate b,
  .board-hall-flight,
  .board-hall-flight::before { animation: none; }
  .board-pass::after { opacity: 0; }
  .board-hall-flight, .board-hall-flight::before { opacity: 0; }
  .board-hall-gate b { opacity: 1; text-shadow: 0 0 8px color-mix(in srgb, var(--scene-board-text) 55%, transparent); }
  /* the reveal is opacity-only already (no transform, no spatial motion), but
     the global net (base.css) only clamps animation-duration, never a plain
     transition-duration: drop it explicitly so the canvas simply appears. */
  .board-scene .scene-canvas { transition: none; }
}

@media (max-width: 480px) {
  .board-head h1 { font-size: 21px; }
  .board-pass-point b { font-size: 16.5px; }
  .board-pass-banner { padding: 11px 16px; }
  .board-pass-body { padding: 15px 16px 17px; }
  .board-pass-stamp { width: 68px; height: 68px; font-size: 9px; top: 12px; right: 12px; }
  .board-pass-barcode { width: 88px; }
  .board-hall-status { max-width: 66%; }
  .board-hall-bench { left: 4%; transform: scale(.8); transform-origin: bottom left; }
  .board-hall-traveler { right: 6%; }
  .board-hall-seats { gap: 1px; }
  .board-hall-seat { width: 20px; height: 16px; }
  .board-hall-plant, .board-hall-bag--small { display: none; }
}
