/* SoloNova — the stage, shared by both mockups.

   Layer order matters and is the whole trick:
     2  the WebGL canvas (world + devices)     — pointer-events: none
     3  the opening scrim                       — dims the world, nothing else
     4  the CSS3D layer (live app DOM)          — the only thing in the stage
                                                  that receives clicks
     5  the mark scene: the logo and the wordmark, laid into the depth
     6  the glass panes and copy
     7  the EVM field (2D point clouds)         — pointer-events: none
     8  the chapter index
     9  HUD chrome
   The CSS3D layer sits ABOVE the canvas because this compositor never yields a
   transparent pixel from a WebGL canvas, so the depth-punched-aperture trick
   cannot work here; and the canvas is click-through so the app underneath can
   still be used. Both are load-bearing. */

:root {
  --ground: #04060b;
  --hud-rgb: 122, 190, 255;
  --hot-rgb: 255, 107, 74;
  --ok-rgb: 140, 255, 214;
  --hud: rgb(var(--hud-rgb));
  /* the status colours, under the names theme.css gives them */
  --hot: rgb(var(--hot-rgb));
  --ok: rgb(var(--ok-rgb));
  --line: rgba(var(--hud-rgb), .22);
  --line-strong: rgba(var(--hud-rgb), .42);
  --text-hi: #E9F1FB;
  --text: #C3CEDD;
  --text-mid: #94A1B4;
  --text-low: #6B8098;
  --font-ui: 'Chakra Petch', ui-sans-serif, system-ui, sans-serif;
  --font-data: 'Chivo Mono', ui-monospace, 'SF Mono', monospace;
  --ease: cubic-bezier(.16, 1, .3, 1);
  --gutter: clamp(1.15rem, 3.6vw, 2.6rem);
  /* the halo under any text that sits on clear glass: readability is the
     text's job, never the pane's (no darker floor, no frost) */
  --halo: 0 1px 2px rgba(0, 0, 0, .92), 0 0 12px rgba(0, 0, 0, .60);
  /* the header bar's height: the copy column, the framing solver (world.js
     reads the bar itself) and the phone's chip row all clear this */
  --bar-h: 72px;
}

* , *::before, *::after { box-sizing: border-box; }
html, body { margin: 0; height: 100%; }
body {
  background: var(--ground);
  color: var(--text);
  font-family: var(--font-ui);
  font-weight: 300;
  line-height: 1.6;
  overflow-x: clip;
}
a { color: var(--hud); text-decoration: none; }
:focus-visible { outline: 2px solid var(--hud); outline-offset: 3px; }

/* ---- the stage ---------------------------------------------------------- */
.stage { position: fixed; inset: 0; overflow: hidden; background: var(--ground); }
.stage-gl  { position: absolute; inset: 0; z-index: 2; width: 100%; height: 100%; display: block; pointer-events: none; }
/* Above the canvas, not behind it. The depth-punch approach (canvas on top,
   transparent where the display is) is the textbook one and it does not
   survive this compositor — the canvas never yields a transparent pixel. With
   the DOM on top the bezel no longer draws over the screen's edge, which
   costs nothing here because the device is inset well inside its aperture and
   never rotates far enough to need the occlusion. */
.stage-css { position: absolute; inset: 0; z-index: 4; pointer-events: none; }

.stage-css .dev-screen { pointer-events: auto; }

/* the scroll track: the page scrolls exactly this far and nothing is hijacked */
.track { position: relative; z-index: 0; pointer-events: none; }

/* ---- the EVM field ------------------------------------------------------ */
.evm-field { position: fixed; inset: 0; z-index: 7; pointer-events: none; }
/* An element the field owns must not paint its own background while it is
   still noise — only its ink dissolves. */
.evm-el { will-change: opacity; }

/* body copy takes the same law without being rasterised: it softens and
   drifts rather than scattering, because a paragraph of dots is unreadable
   and unselectable and the point is that this text can be read */
[data-evm="soft"] {
  opacity: var(--f, 1);
  filter: blur(calc((1 - var(--f, 1)) * 5px));
  transform: translateY(calc((1 - var(--f, 1)) * 16px));
  will-change: opacity, filter, transform;
}

/* ---- glass ------------------------------------------------------------- */
.panes {
  position: fixed;
  z-index: 6;
  left: var(--gutter);
  /* Pinned between the rack and the index rather than centred on the viewport.
     Centred, a tall column ran up behind the instruments and off the bottom of
     the screen — the deck must never touch the header. */
  /* Clear of the instrument rack by a full band, not a hairline. The column
     header was sitting close enough to the site header that the two read as
     one confused strip. */
  top: calc(var(--bar-h) + 26px);
  bottom: calc(var(--gutter) + 108px);
  width: min(34rem, 46vw);
  display: grid;
  gap: .9rem;
  pointer-events: none;
}
.panes > * { pointer-events: auto; }
/* a short desktop window: the column has less height than the copy wants, so
   the type comes down a step rather than the pane running under the rack */
@media (min-width: 861px) and (max-height: 780px) {
  /* the rack ends about 100px down; the prompt pill starts about 110px up */
  .panes { top: calc(var(--bar-h) + 14px); bottom: calc(var(--gutter) + 72px); }
  .pane-set { gap: .4rem; }
  .pane-head { padding-bottom: .3rem; }
  .ph-n { font-size: 1.25rem; }
  .pane { padding: .7rem .85rem .75rem; }
  .pane h2 { font-size: clamp(1.2rem, 2.2vw, 1.55rem); margin-bottom: .3rem; }
  .pane p { font-size: .86rem; line-height: 1.42; }
  .vals { margin-top: .5rem; }
  .vals .v { font-size: .9rem; }
  .pane-cta { margin-top: .5rem; }
  .pane-avail { padding: .55rem .85rem .6rem; }
  .pane-avail p { font-size: .7rem; margin-bottom: .4rem; }
  .av-badge img { height: 30px; }
  .av-badge--play img { height: 42px; margin: -6px -5px; }
  .av-cta { margin-top: .45rem; }
  /* the closing stop, compacted: one-line headline, tighter rows */
  .pane-set[data-station="outro"] { top: calc(var(--bar-h) + 10px); }
  .pane-set[data-station="outro"] .pane h2 br { display: none; }
  .ex-rule { margin: .6rem 0 .5rem; }
  .ex-grid { gap: .3rem .8rem; }
  .av-chip--wide { margin-top: .55rem; }
}
/* Every station's copy occupies the SAME cell, so they cross-fade in place
   rather than stacking down the page. Only the one the camera is holding is
   ever opaque or clickable. */
.pane-set {
  grid-area: 1 / 1;
  display: grid;
  grid-template-rows: auto auto auto;   /* head, the one pane, where to get it */
  gap: .55rem;
  align-content: center;
  min-height: 0;
}

/* The pane is the overview tag at reading size: the same slip of glass in
   the product's own colour — a 1px hue border, a 2px lit left edge, a light
   hue gradient over a thin dark floor, the same ring and the same 7px
   softening. One material from the establishing shot to the hold. The text
   carries its own legibility (the halo); the glass is never darkened for it. */
.pane {
  position: relative;
  padding: 1rem 1.15rem 1.05rem;
  border: 1px solid rgba(var(--hud-rgb), .40);
  border-left: 2px solid rgba(var(--hud-rgb), .85);
  background:
    linear-gradient(180deg, rgba(var(--hud-rgb), .13), rgba(var(--hud-rgb), .04)),
    rgba(6, 11, 18, .40);
  box-shadow: 0 0 0 1px rgba(6, 11, 18, .5), 0 10px 30px rgba(0, 0, 0, .45),
              inset 0 1px 0 rgba(var(--hud-rgb), .22);
  color: var(--text-hi);
}
@supports (backdrop-filter: blur(6px)) {
  .pane {
    -webkit-backdrop-filter: blur(7px) saturate(1.15);
    backdrop-filter: blur(7px) saturate(1.15);
  }
}

/* ---- the pane's finish: corners, a glisten, a light on the rim -----------
   Three things every pane gets, on the bench and in the case studies alike.
   The corners are soft. A faint diagonal band of light drifts across the
   glass — the wordmark's glisten, far quieter, so the glass reads as glass
   rather than as a tinted rectangle. And a short bright run in the
   product's own colour travels the rim, following the border round the
   corners: a motion path along the pane's own rounded outline, so it moves
   at one speed whatever the pane's proportions (a rotating gradient would
   race along the long edges and crawl round the corners). Both are the
   page's clock, not the visitor's: nothing here answers the pointer, so
   nothing competes with the copy for attention. Reduced motion keeps the
   corners and the tint and stills the rest. */
:root { --pane-r: 12px; }
.pane { border-radius: var(--pane-r); isolation: isolate; }
/* The glisten, between the glass and the type: a band the width of the
   pane, carried across it by a transform, inside a clip that keeps the
   pane's corners. rims.js puts the two elements in place. It was a
   background-position animation on ::before — the same band, the same
   sweep — which WebKit repaints on the main thread every frame; with every
   pane's running at once on a phone that held an iPhone at five frames a
   second. A transform lives on the compositor and costs the main thread
   nothing. The numbers: the old image was three panes wide and slid six
   widths per period; the band is the middle 32% of it (0.96 of the pane),
   so it travels from -310% to +315% of its own width in the same time. */
.pane > .glisten, .hud-top > .glisten {
  position: absolute; inset: 0; z-index: -1; overflow: hidden;
  border-radius: inherit; pointer-events: none; display: block;
}
.glisten > b {
  position: absolute; top: 0; bottom: 0; left: 0; width: 96%; display: block;
  background-image: linear-gradient(112deg,
    rgba(255, 255, 255, 0) 0%,
    rgba(255, 255, 255, .035) 31.25%,
    rgba(var(--hud-rgb), .085) 50%,
    rgba(255, 255, 255, .035) 68.75%,
    rgba(255, 255, 255, 0) 100%);
  --g0: -310%; --g1: 315%;
  transform: translate3d(var(--g0), 0, 0);
  animation: pane-glisten var(--glisten-t, 13s) linear infinite;
  will-change: transform;
}
@keyframes pane-glisten {
  from { transform: translate3d(var(--g0), 0, 0); }
  to   { transform: translate3d(var(--g1), 0, 0); }
}
/* The light on the rim (rims.js puts the element in place, fits it and
   moves it): an SVG rounded rectangle laid exactly on the border — same
   box, same radius, concentric with the CSS corner — whose stroke is a
   long dash sliding along the path, painted with a radial gradient whose
   centre rides along with it, so the run is brightest at its middle and
   fades out at both ends. Three strokes on one path: a crisp core, and the
   same line blurred at two widths beneath it — the glow is the rim itself,
   softened, not a shadow spilling into the glass. The widths and the blur
   live here; the geometry, the dash and the motion are the script's. */
.pane > .rim {
  position: absolute; z-index: -1; pointer-events: none; overflow: visible;
  display: block;
}
.rim rect { fill: none; stroke-linecap: round; }
.rim-core { stroke-width: 1.5px; opacity: .95; }
.rim-glow { stroke-width: 3.5px; opacity: .62; filter: blur(1.4px); }
.rim-halo { stroke-width: 6px; opacity: .22; filter: blur(3px); }
/* On a phone the two soft strokes are drawn without their blur: an SVG
   filter is rasterised in software on every frame the light moves, and
   at three times the screen's pixels that was a cost on each visible pane.
   Wider and fainter instead, so the glow is still a glow. */
html.lite .rim-glow { filter: none; opacity: .30; stroke-width: 4px; }
html.lite .rim-halo { filter: none; opacity: .09; stroke-width: 8px; }
/* so the lights on stacked panes are never in step (rims.js reads --rim-d) */
.pane-avail { --rim-d: -2.6s; --glisten-t: 11s; }
.pane-avail .glisten > b { animation-delay: -5s; }
/* on the bench only the set on stage glistens; the others are held — and a
   panel the phone has below the screen (boot.js marks it is-off) has none */
.pane-set:not(.is-live) .glisten > b { animation-play-state: paused; }
.pane-set.is-off .glisten, .pane.is-off .glisten { display: none; }
@media (prefers-reduced-motion: reduce) {
  .glisten > b { animation: none; transform: translate3d(2%, 0, 0); }
  .pane > .rim { display: none; }
}

.pane-k {
  display: block;
  font: 600 .68rem/1 var(--font-data);
  letter-spacing: .22em; text-transform: uppercase;
  color: var(--hud);
  margin-bottom: .6rem;
}
/* Readable over a lit lake by the type alone: a heavier face, near-white ink
   and the halo. Nothing here touches the glass. */
.pane h2 {
  margin: 0 0 .5rem;
  font-size: clamp(1.5rem, 3.2vw, 2.3rem);
  font-weight: 500; line-height: 1.06; letter-spacing: -.01em;
  color: #F4F8FD; text-transform: uppercase;
  text-shadow: var(--halo);
}
.pane p {
  margin: 0; color: #E3EBF5; font-weight: 500;
  font-size: 1rem; line-height: 1.5; max-width: 48ch;
  text-shadow: var(--halo);
}
.pane p + p { margin-top: .6rem; }

.vals { display: flex; flex-wrap: wrap; gap: .45rem 1.5rem; margin-top: .85rem; }
.vals div { display: grid; gap: .12rem; }
.vals .k { font: 500 .6rem/1.4 var(--font-data); letter-spacing: .18em; text-transform: uppercase; color: var(--text-mid); text-shadow: var(--halo); }
.vals .v { font: 600 1rem/1.15 var(--font-data); color: var(--text-hi); font-variant-numeric: tabular-nums; text-shadow: var(--halo); }
/* the closing stop's rows are links rather than readouts */
.vals-links .v { font-size: .78rem; line-height: 1.5; letter-spacing: .02em; }
.vals-links .v a { color: var(--text-hi); text-decoration: none; border-bottom: 1px solid rgba(var(--hud-rgb), .45); }
.vals-links .v a:hover { color: rgb(var(--hud-rgb)); border-bottom-color: rgb(var(--hud-rgb)); }

.pane-cta {
  display: inline-flex; align-items: center; gap: .5rem; margin-top: .9rem;
  font: 600 .7rem/1 var(--font-data); letter-spacing: .18em; text-transform: uppercase;
  color: var(--hud);
  text-shadow: var(--halo);
}
.pane-cta::after { content: "→"; transition: transform .3s var(--ease); }
.pane-cta:hover::after { transform: translateX(4px); }

/* ---- HUD ---------------------------------------------------------------- */
/* ---- the header bar ------------------------------------------------------
   One strip across the top, as tall as the instruments and no taller: the
   brand, the site's five pages, the receiver's front panel, and a hard rule
   under all of it. Lightly frosted, so the view reads through it as a bar
   rather than a hole. Its height is --bar-h, and everything that has to stay
   clear of it (the copy column, the framing solver in world.js, the chips on
   a phone) reads that one number rather than guessing. */
.hud { position: fixed; inset: 0; z-index: 12; pointer-events: none; padding: var(--bar-h) var(--gutter) var(--gutter); display: grid; grid-template-rows: 1fr auto; }
.hud a, .hud button { pointer-events: auto; }
.hud-top {
  position: fixed; top: 0; left: 0; right: 0; height: var(--bar-h);
  display: flex; align-items: center; gap: clamp(.8rem, 1.6vw, 1.6rem);
  padding: 0 var(--gutter);
  background: linear-gradient(180deg, rgba(4, 7, 12, .68), rgba(4, 7, 12, .52));
  border-bottom: 1px solid rgba(var(--hud-rgb), .34);
  box-shadow: 0 1px 0 rgba(0, 0, 0, .4);
}
@supports (backdrop-filter: blur(6px)) {
  .hud-top { -webkit-backdrop-filter: blur(9px) saturate(1.1); backdrop-filter: blur(9px) saturate(1.1); }
}
/* The bar's glisten: the band of light that drifts across every pane
   crosses the bar too, between its glass and its controls — slower, for a
   strip the width of the window, and on its own clock so it is never in
   step with the panes beneath it. The pseudo-element is the bar's own box,
   so nothing is clipped and the phone's menu (a fixed child) is untouched.
   Reduced motion holds it at the middle, as the panes do. */
.hud-top { isolation: isolate; }
/* the bar's band: a shade brighter and a little wider than a pane's (the
   bar's glass is darker and more opaque, and the same band vanished into
   it) — the middle 36% of the old image, 1.08 of the bar wide */
.hud-top > .glisten > b {
  width: 108%;
  background-image: linear-gradient(112deg,
    rgba(255, 255, 255, 0) 0%,
    rgba(255, 255, 255, .055) 27.8%,
    rgba(var(--hud-rgb), .13) 50%,
    rgba(255, 255, 255, .055) 72.2%,
    rgba(255, 255, 255, 0) 100%);
  --g0: -281%; --g1: 274%;
  animation-duration: 21s;
  animation-delay: -9s;
}
/* the site's pages, in the bar */
.hud-nav { display: flex; align-items: center; gap: .1rem; }
.hud-nav a {
  display: inline-block; padding: .5rem .6rem; border-radius: 4px;
  font: 600 .62rem/1 var(--font-data); letter-spacing: .16em; text-transform: uppercase;
  color: var(--text-mid); white-space: nowrap;
  transition: color .18s ease, background-color .18s ease;
}
.hud-nav a:hover { color: var(--text-hi); background: rgba(var(--hud-rgb), .10); }
/* the four pages are the case studies, and the bar says so */
.hud-nav .nav-k {
  margin: 0 .1rem 0 .5rem; padding-left: .7rem; border-left: 1px solid rgba(var(--hud-rgb), .3);
  font: 600 .52rem/1 var(--font-data); letter-spacing: .2em; text-transform: uppercase; color: var(--text-low);
  white-space: nowrap;
}
/* arriving with a destination (a hash): black until the world stands on it
   and the view's photograph is in, then a short fade in (boot.js) */
html.is-arriving body { opacity: 0; }
html.is-arrived body { transition: opacity .5s ease; }
/* leaving the bench for a case study: a short fade, then the page changes */
html.is-leaving body { opacity: 0; transition: opacity .26s ease; }
.hud-nav a[aria-current="page"] { color: var(--text-hi); box-shadow: inset 0 -1px 0 rgba(var(--hud-rgb), .8); border-radius: 0; }
.hud-menu { display: none; }
.brand { display: inline-flex; align-items: center; gap: .6rem; color: var(--text-hi); }
.brand .logo {
  width: 22px; height: 22px; display: block; background: var(--hud);
  -webkit-mask: url('/assets/img/solonova-logo.svg') center / contain no-repeat;
  mask: url('/assets/img/solonova-logo.svg') center / contain no-repeat;
}
.brand .wordmark { font: 500 .84rem/1 var(--font-ui); letter-spacing: .3em; }
.hud-meta { margin-left: auto; font: 600 .7rem/1 var(--font-data); letter-spacing: .2em; text-transform: uppercase; color: var(--text-low); }
.hud-meta b { color: var(--hud); font-weight: 600; }

/* ---- the receiver's front panel -----------------------------------------
   Five cells, one grid, equal width and equal gap. Instruments and numbers
   share a body height of 46px and a common label rule, so the row reads as one
   piece of equipment rather than five things that happened to be put next to
   each other. Every value is tabular so nothing shifts as digits change. */
.hud-rack {
  flex: 1 1 auto; min-width: 0;
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: clamp(.75rem, 1.5vw, 1.75rem);
  padding-left: clamp(.9rem, 3vw, 3rem);
}
.rk { display: grid; grid-template-rows: auto 34px; gap: .26rem; min-width: 0; }
.rk-lab {
  display: flex; align-items: baseline; justify-content: space-between; gap: .5rem;
  font: 600 .53rem/1 var(--font-data); letter-spacing: .2em; text-transform: uppercase;
  color: var(--text-low); white-space: nowrap; overflow: hidden;
  border-bottom: 1px solid rgba(var(--hud-rgb), .14); padding-bottom: .24rem;
}
.rk-lab b { color: var(--hud); font-weight: 600; letter-spacing: .1em; }
.rk-cv {
  width: 100%; height: 34px; display: block;
  border: 1px solid rgba(var(--hud-rgb), .16);
  background: linear-gradient(180deg, rgba(6,10,17,.62), rgba(6,10,17,.34));
}
.rk-num {
  display: grid; grid-template-columns: max-content 1fr;
  align-content: space-between; column-gap: .6rem; height: 34px;
  font: 400 .54rem/1 var(--font-data); letter-spacing: .1em;
  color: var(--text-low); font-variant-numeric: tabular-nums;
}
.rk-num i { font-style: normal; }
.rk-num b { color: var(--hud); font-weight: 600; text-align: right; }
.rk-num u { text-decoration: none; color: var(--text-low); }

/* shed cells from the right as the bar narrows, never squeeze them: the
   pages come first, the instruments take what is left */
@media (max-width: 1600px) { .hud-rack { grid-template-columns: repeat(4, minmax(0, 1fr)); }
                             .hud-rack .rk:nth-child(5) { display: none; } }
@media (max-width: 1360px) { .hud-rack { grid-template-columns: repeat(3, minmax(0, 1fr)); }
                             .hud-rack .rk:nth-child(4) { display: none; } }
@media (max-width: 1160px) { .hud-rack { grid-template-columns: repeat(2, minmax(0, 1fr)); }
                             .hud-rack .rk:nth-child(3) { display: none; } }
@media (max-width: 1000px) { .hud-rack { grid-template-columns: repeat(1, minmax(0, 1fr)); }
                             .hud-rack .rk:nth-child(2) { display: none; } }
@media (max-width: 860px)  { .hud-rack { display: none; } }

.hud-hint {
  justify-self: center; align-self: end;
  margin-bottom: 2.2rem;
  transition: margin-bottom .4s var(--ease);
  font: 600 .66rem/1 var(--font-data); letter-spacing: .3em; text-transform: uppercase;
  color: var(--text-low); transition: opacity .5s var(--ease);
}
.hud-hint::after { content: ""; display: block; width: 1px; height: 30px; margin: .8rem auto 0; background: linear-gradient(rgba(var(--hud-rgb), .7), transparent); }

/* At a hold the same slot stops saying "scroll" and says what this device
   will let you do. Without it the screens are indistinguishable from a very
   good picture of a screen, which is the one thing they must not be. */
.hud-hint.is-prompt {
  display: inline-flex; align-items: center; gap: .6rem;
  padding: .55rem 1rem;
  border: 1px solid rgba(var(--hud-rgb), .34);
  border-radius: 100px;
  background: rgba(6, 10, 17, .72);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  color: var(--text-hi);
  letter-spacing: .16em;
  text-transform: none;
  font: 600 .72rem/1 var(--font-data);
}
.hud-hint.is-prompt::after { content: none; }
/* During the establishing shot the chapter index owns the bottom of the frame,
   so the invitation to scroll sits above it; at a hold the index is gone and
   it drops back down beside the device. */
.hud-hint.is-open { margin-bottom: 7.4rem; }
.hud-hint.is-prompt::before {
  content: ""; width: 7px; height: 7px; border-radius: 50%;
  background: var(--hud);
  box-shadow: 0 0 0 0 rgba(var(--hud-rgb), .65);
  animation: prompt-pulse 2s var(--ease) infinite;
}
@keyframes prompt-pulse {
  0%   { box-shadow: 0 0 0 0 rgba(var(--hud-rgb), .55); }
  70%  { box-shadow: 0 0 0 11px rgba(var(--hud-rgb), 0); }
  100% { box-shadow: 0 0 0 0 rgba(var(--hud-rgb), 0); }
}
@media (prefers-reduced-motion: reduce) {
  .hud-hint.is-prompt::before { animation: none; }
}

/* ---- the chapter index, on the establishing shot ------------------------ */
.index { position: fixed; z-index: 8; left: var(--gutter); right: var(--gutter); bottom: calc(var(--gutter) + 1.6rem); display: grid; grid-template-columns: repeat(4, 1fr); gap: 1.4rem; pointer-events: none; }
/* The container's pointer-events are switched off once the establishing shot
   is gone, but this rule used to override that and the chapter links went on
   intercepting the pointer over the product panes below them. The state is a
   class now, so the two cannot disagree. */
.index.is-off a { pointer-events: none; }
/* and, like the mark, not painted before the page has said it is in play */
html.js .index:not(.is-on) { visibility: hidden; }
.index a { pointer-events: auto; display: block; padding-top: .7rem; border-top: 1px solid rgba(var(--hud-rgb), .22); }
.index .n { display: block; font: 300 1.9rem/1 var(--font-ui); color: var(--text-hi); }
.index .t { display: block; font: 600 .66rem/1.4 var(--font-data); letter-spacing: .18em; text-transform: uppercase; color: var(--hud); margin-top: .3rem; }
.index .d { display: block; font-size: .8rem; color: var(--text-low); margin-top: .2rem; }

/* ---- the mark scene: the logo and the wordmark, laid into the depth -----
   Both are acquired on load (boot.js, "the opening") and both are live
   afterwards: the cursor, or a finger drawn across them, knocks a hole in
   whichever it is over. The scene itself takes no pointer; its two artefacts
   do, and only while they are on screen (.is-on, set by boot.js). */
.mark-scene {
  position: fixed; z-index: 5; left: 0; right: 0;
  top: var(--bar-h); bottom: 20vh;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: clamp(.5rem, 2.2vh, 1.5rem);
  pointer-events: none;
  user-select: none; -webkit-user-select: none;
}
.mark-scene .bigmark, .mark-scene .biglogo {
  pointer-events: auto;
  cursor: crosshair;
  /* a finger drawn ACROSS them paints the hole; drawn up or down it scrolls */
  touch-action: pan-y;
}
/* Shown only while the mark is in play (.is-on, boot.js), and never before
   the page has said so: a page arriving on a hash lands away from the mark,
   and the type must not be painted once before the script gets to it. The
   field owns each artefact's opacity within that. */
html.js .mark-scene { visibility: hidden; }
html.js .mark-scene.is-on { visibility: visible; }
.mark-scene:not(.is-on) .bigmark, .mark-scene:not(.is-on) .biglogo { pointer-events: none; }

/* The glisten: a band of light that travels across the letters and the
   mark, clipped to the glyphs. At the establishing shot the room behind is
   deliberately dark, so this is the brightest thing on screen and has to
   carry the frame. Both artefacts run the one animation, so the band crosses
   them together. */
.bigmark {
  margin: 0; width: 100%;
  text-align: center;
  font: 300 clamp(4.5rem, 19vw, 17rem)/0.88 var(--font-ui);
  letter-spacing: .07em;
  text-indent: .07em;              /* tracked text is not optically centred */
  color: transparent;
  -webkit-text-fill-color: transparent;
  background-image: linear-gradient(100deg,
    rgba(233, 241, 251, .26) 0%,
    rgba(233, 241, 251, .26) 36%,
    rgba(255, 255, 255, .92) 47%,
    rgba(var(--hud-rgb), .82) 53%,
    rgba(233, 241, 251, .26) 64%,
    rgba(233, 241, 251, .26) 100%);
  background-size: 280% 100%;
  -webkit-background-clip: text;
  background-clip: text;
  animation: bigmark-sheen 5.833s linear infinite;
}
/* its two halves: one word on a wide screen, one over the other on a phone */
.bigmark span { display: inline; }

/* The logo. Its shape is a mask on an inner layer, which leaves the element
   itself free to take the field's mask — the hole under the pointer. The box
   is square because the SVG's viewBox is: that is what lets the field's
   raster of the same file and this mask land on the same pixels. */
.biglogo {
  position: relative; flex: 0 0 auto;
  width: clamp(88px, 34vh, 330px); aspect-ratio: 1;
}
.biglogo::before {
  content: ""; position: absolute; inset: 0;
  background-image: linear-gradient(100deg,
    rgba(var(--hud-rgb), .66) 0%,
    rgba(var(--hud-rgb), .66) 36%,
    rgba(255, 255, 255, 1) 47%,
    rgba(var(--hud-rgb), 1) 53%,
    rgba(var(--hud-rgb), .66) 64%,
    rgba(var(--hud-rgb), .66) 100%);
  background-size: 280% 100%;
  -webkit-mask: url('/assets/img/solonova-logo.svg') center / contain no-repeat;
  mask: url('/assets/img/solonova-logo.svg') center / contain no-repeat;
  animation: bigmark-sheen 5.833s linear infinite;
}
/* Not before its cloud exists. The field hides its artefacts itself from the
   first frame, but the logo registers a beat after the page (its file is
   fetched), and it must not stand there crisp while the wordmark condenses. */
html.js .biglogo:not(.is-live) { opacity: 0; }
/* On a phone the sheen moves in steps: a background-position animation is
   repainted on the main thread — with background-clip: text, that is the
   whole wordmark re-rasterised at three times the screen — and at sixty
   times a second it was most of a frame. Twenty a second is a step of
   a few pixels on a band this soft, and a third of the cost. */
html.lite .bigmark, html.lite .biglogo::before { animation-timing-function: steps(117, end); }
/* the bisect switches (boot.js): each takes one per-frame cost away */
html.nosheen .bigmark, html.nosheen .biglogo::before { animation: none; background-position: 50% 0; }
html.noglisten .glisten { display: none; }
html.noblur *, html.noblur *::before, html.noblur *::after { -webkit-backdrop-filter: none !important; backdrop-filter: none !important; }
/* The band repeats: the image is 2.8 widths wide and tiles, so a band
   crosses every time the tiling has moved one tile. The loop moves EXACTLY
   one tile per cycle (a background-position of 155.556% is an offset of
   one tile at this size), so its end is its start and the wrap is
   invisible; at the original speed — 0.48 widths a second — that is a pass
   every 5.8 s, the cadence the mark always had. It used to move 1.54 tiles
   per 9 s cycle: one clean pass, then one caught a third of the way across
   when the loop wrapped. */
@keyframes bigmark-sheen {
  from { background-position: 155.556% 0; }
  to   { background-position: 0% 0; }
}
@media (prefers-reduced-motion: reduce) {
  .bigmark, .biglogo::before { animation: none; background-position: 50% 0; }
}
/* a short desktop window: less room above the index */
@media (min-width: 861px) and (max-height: 780px) {
  .mark-scene { bottom: 22vh; }
  .biglogo { width: clamp(80px, 28vh, 240px); }
}

.skip { position: absolute; left: -999px; top: .5rem; z-index: 40; padding: .6rem 1.1rem; background: #0e1520; color: var(--text-hi); border: 1px solid rgba(var(--hud-rgb), .42); font-family: var(--font-data); font-size: .78rem; }
.skip:focus { left: .5rem; }

.fallback { position: fixed; inset: 0; z-index: 40; display: grid; place-items: center; background: var(--ground); padding: 2rem; text-align: center; }
.fallback[hidden] { display: none; }

@media (max-width: 1180px) {
  .hud-tuner { width: 168px; height: 64px; }
  .hud-evm { min-width: 104px; }
}
@media (max-width: 820px) {
  .hud-instr { display: none; }
  .panes { width: auto; right: var(--gutter); top: auto; bottom: 6.5rem; transform: none; }
  .index { grid-template-columns: repeat(2, 1fr); gap: .8rem 1.2rem; }
}
@media (prefers-reduced-motion: reduce) {
  [data-evm="soft"] { filter: none; transform: none; opacity: 1; }
}


/* ---- the overview tags --------------------------------------------------
   Four holographic slips, one per product, hanging above the bench while the
   whole bench is in view. They are the answer to "what am I looking at" at the
   moment the four devices first appear, and they get out of the way the
   instant you commit to one of them — a label, not a panel, and never
   something to read while you are trying to use a phone.

   Colour is the only thing distinguishing them, so it comes from the station's
   own hue rather than a palette repeated here. */
.tags { position: fixed; inset: 0; z-index: 6; pointer-events: none; }

.tag {
  position: absolute; left: 0; top: 0;
  transform-origin: 50% 100%;
  will-change: transform, opacity;
  min-width: 148px; max-width: 232px;
  padding: .5rem .68rem .58rem;
  border: 1px solid rgba(var(--tag-rgb), .40);
  border-left: 2px solid rgba(var(--tag-rgb), .85);
  border-radius: 6px;               /* the panes' corners, at a slip's size */
  background:
    linear-gradient(180deg, rgba(var(--tag-rgb), .13), rgba(var(--tag-rgb), .04)),
    rgba(6, 11, 18, .40);
  box-shadow: 0 0 0 1px rgba(6, 11, 18, .5), 0 10px 30px rgba(0, 0, 0, .45),
              inset 0 1px 0 rgba(var(--tag-rgb), .22);
  color: var(--text-hi);
  opacity: 0;
}
@supports (backdrop-filter: blur(6px)) {
  .tag { backdrop-filter: blur(7px) saturate(1.15); }
}
.tag-n {
  font: 600 .58rem/1 var(--font-data); letter-spacing: .26em;
  color: rgb(var(--tag-rgb)); display: block; margin-bottom: .28rem;
  text-shadow: var(--halo);
}
.tag-t { font: 600 .82rem/1.1 var(--font-ui); letter-spacing: .02em; display: block; text-shadow: var(--halo); }
/* the line that was going under: the description, over the lit lake */
.tag-d {
  font: 500 .7rem/1.35 var(--font-ui); color: #D5DFEC;
  display: block; margin-top: .22rem;
  text-shadow: var(--halo);
}
/* the thread down to the thing it is labelling */
.tag::after {
  content: ''; position: absolute; left: 12px; top: 100%;
  width: 1px; height: 18px;
  background: linear-gradient(180deg, rgba(var(--tag-rgb), .7), rgba(var(--tag-rgb), 0));
}
@media (prefers-reduced-motion: reduce) { .tag { transition: none; } }


/* ---- showing the one on stage ------------------------------------------ */
/* Hidden until the frame loop raises it. Under html.js only: with script off
   the page must still read, and this is the difference between a broken frame
   loop showing nothing and showing every station at once. */
html.js .pane-set { opacity: 0; }

/* All five sets share one grid cell, so without this the LAST one in the
   document is the one the pointer finds — which is why the wheel never
   reached the pane it was over. The live set comes to the front. */
.pane-set { position: relative; z-index: 0; }
.pane-set.is-live { z-index: 3; }



/* the product under the cursor, on the landing */
.tag { transition: border-color .18s ease, box-shadow .22s ease, background-color .2s ease; }
.tag.is-hot {
  border-color: rgba(var(--tag-rgb), .95);
  box-shadow:
    0 0 0 1px rgba(var(--tag-rgb), .55),
    0 0 22px rgba(var(--tag-rgb), .38),
    0 10px 30px rgba(0, 0, 0, .5);
}
.tag.is-hot .tag-t { color: rgb(var(--tag-rgb)); }
.tag { cursor: pointer; }

/* ---- the column's own header ------------------------------------------- */
.pane-head {
  display: flex; align-items: baseline; gap: .6rem;
  padding: 0 .1rem .4rem;
  border-bottom: 1px solid rgba(var(--hud-rgb), .28);
}
.ph-k {
  font: 600 .52rem/1 var(--font-data); letter-spacing: .24em; text-transform: uppercase;
  color: rgb(var(--hud-rgb)); text-shadow: var(--halo); margin-right: .1rem;
}
.ph-n {
  font: 300 1.5rem/1 var(--font-ui); color: rgb(var(--hud-rgb));
  letter-spacing: .02em; text-shadow: var(--halo);
}
.ph-t {
  font: 600 .95rem/1 var(--font-ui); color: var(--text-hi); letter-spacing: .01em;
  text-shadow: var(--halo);
}
.ph-s {
  margin-left: auto;
  font: 600 .56rem/1 var(--font-data); letter-spacing: .2em; text-transform: uppercase;
  color: rgb(var(--hud-rgb));
  padding: .28rem .5rem; border-radius: 3px;
  background: rgba(var(--hud-rgb), .14);
  border: 1px solid rgba(var(--hud-rgb), .30);
}

/* ---- the closing stop: front and centre, over the view ------------------
   The products' copy lives in the column beside the device; the studio's
   sits in the middle of the frame, because by then the lens is through the
   glazing and there is nothing beside it but the view. */
@media (min-width: 861px) {
  .pane-set[data-station="outro"] {
    position: fixed; left: 50%; transform: translateX(-50%);
    /* between the rack and the foot of the frame, centred in that band;
       a window too short for it lets the panel scroll, which is safe here
       because nothing comes after it */
    top: calc(var(--bar-h) + 18px); bottom: calc(var(--gutter) + 8px);
    width: min(46rem, 88vw); padding: 4px 0;
    /* a column with auto margins at both ends: centred when there is room,
       and start-aligned (so nothing is clipped) when there is not */
    display: flex; flex-direction: column; gap: .55rem;
    overflow-y: auto; overscroll-behavior-y: auto;
  }
  .pane-set[data-station="outro"] > :first-child { margin-top: auto; }
  .pane-set[data-station="outro"] > :last-child { margin-bottom: auto; }
}
/* the exhibits, each in its own colour, with its two ways in */
.ex-rule { height: 1px; margin: .9rem 0 .75rem; background: rgba(var(--hud-rgb), .28); }
.ex-grid { display: grid; grid-template-columns: 1fr 1fr; gap: .5rem .9rem; margin-top: .45rem; }
.ex-row { display: flex; align-items: center; gap: .4rem; flex-wrap: wrap; }
.ex-row b {
  margin-right: auto; padding-right: .5rem;
  font: 600 .82rem/1.2 var(--font-ui); color: var(--text-hi); text-shadow: var(--halo);
}
.ex-row b i {
  font: 600 .56rem/1 var(--font-data); font-style: normal; letter-spacing: .2em;
  color: rgb(var(--hud-rgb)); margin-right: .45rem;
}
button.av-chip { appearance: none; -webkit-appearance: none; cursor: pointer; font-family: var(--font-data); }
button.av-chip:hover { background: rgba(var(--hud-rgb), .22); border-color: rgba(var(--hud-rgb), .7); }
.av-chip--wide { display: inline-flex; align-items: center; gap: .4rem; margin-top: .9rem; padding: .5rem .85rem; }

/* ---- where to get it: the second pane, always on screen ---------------- */
.pane-avail { padding: .75rem .95rem .8rem; }
.av-k {
  display: block; margin-bottom: .4rem;
  font: 600 .56rem/1 var(--font-data); letter-spacing: .22em; text-transform: uppercase;
  color: rgb(var(--hud-rgb));
  text-shadow: var(--halo);
}
.pane-avail p {
  margin: 0 0 .6rem;
  font: 500 .78rem/1.45 var(--font-ui); color: #D5DFEC;
  text-shadow: var(--halo);
}
/* the closing stop's copyright line, beside its one chip */
.av-copy { font: 500 .6rem/1 var(--font-data); letter-spacing: .12em; color: var(--text-mid); text-shadow: var(--halo); }
.av-row { display: flex; flex-wrap: wrap; gap: .38rem; align-items: center; }
.av-chip {
  font: 600 .58rem/1 var(--font-data); letter-spacing: .14em; text-transform: uppercase;
  padding: .38rem .55rem; border-radius: 4px;
  border: 1px solid rgba(var(--hud-rgb), .38);
  background: rgba(var(--hud-rgb), .12);
  color: var(--text-hi); text-decoration: none;
}
a.av-chip:hover { background: rgba(var(--hud-rgb), .22); border-color: rgba(var(--hud-rgb), .7); }
/* a platform we ship on, whose store link does not exist yet */
.av-chip.is-soon { opacity: .58; border-style: dashed; }
.av-cta {
  display: inline-block; margin-top: .6rem;
  font: 600 .66rem/1 var(--font-data); letter-spacing: .16em; text-transform: uppercase;
  color: rgb(var(--hud-rgb)); text-decoration: none;
  text-shadow: var(--halo);
}
.av-cta:hover { text-decoration: underline; text-underline-offset: 3px; }


/* ---- the real store badges ---------------------------------------------
   Apple's and Google's own artwork, copied from the SquadPT build rather than
   redrawn: both have brand rules about their badges, and an approximation of
   someone else's logo is the wrong thing to ship. The Play badge carries its
   own padding inside the image, so it is set taller to sit level with
   Apple's. */
.av-stores {
  display: flex; align-items: center; gap: .45rem;
  margin-top: .55rem; flex-wrap: nowrap;
}
.av-badge { display: inline-flex; border-radius: 7px; }
.av-badge img { display: block; height: 34px; width: auto; }
.av-badge--play img { height: 48px; margin: -7px -6px; }
.av-badge { transition: opacity .18s ease, transform .18s ease; }
.av-badge:hover { opacity: .86; transform: translateY(-1px); }
.av-badge:focus-visible { outline: 2px solid rgb(var(--hud-rgb)); outline-offset: 3px; }


/* ==========================================================================
   PORTRAIT
   On a phone the page is a page. The scene keeps the whole screen at a hold,
   with the device large and live; scroll on and the device rides up out of
   the frame while the product's PANEL rises from below: the copy as one
   pane and where to get it, held under the brand while it is read, then on
   to the next product. boot.js builds
   the scroll map and moves the panel; this block gives it its shape. The
   navigation is one line of chips under the brand, and it steps aside at a
   hold.
   ========================================================================== */
@media (max-width: 860px) {
  :root { --safe-b: env(safe-area-inset-bottom, 0px); --bar-h: calc(52px + env(safe-area-inset-top, 0px)); }
  /* The stage is sized to the LARGE viewport, not the visual one: the
     browser's toolbar sliding and the keyboard rising change innerHeight
     many times a minute on a phone, and a canvas that followed them was
     re-solved and re-laid-out every time — the "constant resizing". Sized to
     100lvh it never moves; the bottom of it simply sits under the toolbar. */
  @supports (height: 100lvh) {
    .stage { bottom: auto; height: 100lvh; }
  }
  /* the bar: brand and a menu button; the pages drop down under it */
  .hud-top { padding-top: env(safe-area-inset-top, 0px); gap: .6rem; }
  .hud-meta { display: none; }
  .hud-menu {
    display: inline-grid; place-items: center; align-content: center; gap: 4px;
    margin-left: auto; width: 40px; height: 36px; padding: 0;
    border: 1px solid rgba(var(--hud-rgb), .34); border-radius: 6px;
    background: rgba(6, 10, 17, .5); cursor: pointer; -webkit-tap-highlight-color: transparent;
  }
  .hud-menu span { display: block; width: 16px; height: 1.5px; background: var(--text-hi); transition: transform .2s ease, opacity .2s ease; }
  .hud-menu.is-open span:nth-child(1) { transform: translateY(5.5px) rotate(45deg); }
  .hud-menu.is-open span:nth-child(2) { opacity: 0; }
  .hud-menu.is-open span:nth-child(3) { transform: translateY(-5.5px) rotate(-45deg); }
  .hud-nav {
    position: fixed; top: var(--bar-h); left: 0; right: 0; display: none;
    flex-direction: column; align-items: stretch; gap: 0;
    padding: .3rem var(--gutter) .7rem;
    background: rgba(4, 7, 12, .88);
    border-bottom: 1px solid rgba(var(--hud-rgb), .34);
  }
  @supports (backdrop-filter: blur(6px)) {
    .hud-nav { -webkit-backdrop-filter: blur(12px); backdrop-filter: blur(12px); }
  }
  .hud-nav.is-open { display: flex; }
  .hud-nav a { padding: .85rem .2rem; border-radius: 0; border-top: 1px solid rgba(var(--hud-rgb), .12); font-size: .74rem; }
  .hud-nav .nav-k { display: block; margin: .6rem 0 0; padding: .4rem 0 .2rem; border: 0; }
  .hud-nav a:first-child { border-top: 0; }
  .hud-nav a[aria-current="page"] { box-shadow: none; color: rgb(var(--hud-rgb)); }

  /* the panels: one per product, fixed, moved by boot.js; above the header's
     gradient, which would otherwise dim the panel's own header */
  .panes {
    position: fixed; inset: 0; z-index: 10;
    width: auto; height: auto; padding: 0; display: block; gap: 0;
    pointer-events: none;
  }
  .pane-set {
    position: absolute; left: 12px; right: 12px;
    top: calc(var(--bar-h) + 10px);
    bottom: calc(12px + env(safe-area-inset-bottom, 0px));
    display: grid;
    grid-template-columns: 1fr;
    grid-template-rows: auto auto auto;
    grid-template-areas: "head" "main" "avail";
    gap: .5rem; align-content: start;
    z-index: auto; opacity: 0; pointer-events: none;
    will-change: transform, opacity;
  }
  :root { --pane-r: 10px; }
  /* the same glass as the panes, so the panel is one material */
  .pane-head {
    grid-area: head; padding: .45rem .65rem .5rem;
    border-radius: var(--pane-r);
    border: 1px solid rgba(var(--hud-rgb), .40);
    border-left: 2px solid rgba(var(--hud-rgb), .85);
    background:
      linear-gradient(180deg, rgba(var(--hud-rgb), .13), rgba(var(--hud-rgb), .04)),
      rgba(6, 11, 18, .40);
    box-shadow: 0 0 0 1px rgba(6, 11, 18, .5), 0 10px 30px rgba(0, 0, 0, .45),
                inset 0 1px 0 rgba(var(--hud-rgb), .22);
  }
  @supports (backdrop-filter: blur(6px)) {
    .pane-head {
      -webkit-backdrop-filter: blur(7px) saturate(1.15);
      backdrop-filter: blur(7px) saturate(1.15);
    }
  }
  .pane-main { grid-area: main; }
  .pane { padding: .85rem .95rem; }
  .pane h2 { font-size: 1.35rem; }
  .pane p { font-size: .9rem; line-height: 1.5; }
  /* the closing stop carries more than a product does: tighter type, one
     exhibit per line, and — on a phone too short to hold it — the panel
     itself scrolls, which is safe here because nothing comes after it */
  .pane-set[data-station="outro"] { overflow-y: auto; overscroll-behavior-y: auto; }
  .pane-set[data-station="outro"] .pane h2 { font-size: 1.2rem; }
  .pane-set[data-station="outro"] .pane h2 br { display: none; }
  .pane-set[data-station="outro"] .pane p { font-size: .88rem; }
  .pane-set[data-station="outro"] .pane-avail p { display: none; }
  .ex-grid { grid-template-columns: 1fr; gap: .4rem; }


  .pane-avail { grid-area: avail; padding: .6rem .9rem .65rem; }
  .pane-avail p { margin-bottom: .35rem; }
  .av-stores { flex-wrap: wrap; }

  /* the photograph under the panel: the live frame at a sixth of its size
     with the product lifted off the bench (boot.js takeStill), softened a
     little by the stylesheet (the panes are clear glass, so the scene has
     to survive behind them), lightly scrimmed, faded in with the panel */
  .still {
    position: absolute; inset: 0; z-index: 5; pointer-events: none;
    opacity: 0; overflow: hidden;
  }
  .still canvas {
    position: absolute; inset: -6%; width: 112%; height: 112%; display: block;
    filter: blur(6px) saturate(.96);
  }
  .still i {
    position: absolute; inset: 0; display: block;
    background: linear-gradient(180deg, rgba(4, 8, 14, .16), rgba(4, 8, 14, .42));
  }

  /* the chapter index competes with the scene for the same pixels; the
     chips do its job, and they are tappable */
  .index { display: none; }

  /* the mark: held under a finger, or drawn across, it comes apart — so no
     callout, no selection; the wordmark breaks into its two halves, one over
     the other, and grows into the width that gives it */
  .mark-scene { bottom: 14vh; gap: 1.1rem; -webkit-touch-callout: none; }
  /* by the width, but never more than the height allows: a phone on its side */
  .bigmark { font-size: clamp(3rem, min(30vw, 16vh), 12rem); line-height: .92; }
  .bigmark span { display: block; }
  .biglogo { width: clamp(72px, min(44vw, 26vh), 220px); }

  /* Every prompt sits at the very bottom. The bottom of the screen is free at
     a hold now, because the copy comes after the device rather than beside
     it, and the framing solver keeps the device clear of this slot. */
  .hud-hint, .hud-hint.is-open, .hud-hint.is-landing {
    margin-bottom: calc(2.2rem + env(safe-area-inset-bottom, 0px));
  }

  /* no instruments on a phone */
  .hud-rack { display: none; }

  /* the navigation: one line of chips under the brand, above the header's
     own gradient so it is not dimmed by it; boot.js lays the row out */
  .tags { z-index: 10; }
  .tag {
    display: flex; align-items: baseline; gap: .35rem;
    min-width: 0; width: auto; max-width: none;
    padding: .42rem .6rem .46rem;
    white-space: nowrap;
    transform-origin: 50% 0;
    background:
      linear-gradient(180deg, rgba(var(--tag-rgb), .18), rgba(var(--tag-rgb), .06)),
      rgba(6, 11, 18, .74);
  }
  .tag::after { display: none; }   /* no thread: there is nothing to point at */
  .tag-n, .tag-d { display: none; }
  .tag-t { font-size: .74rem; }
  .tags.is-tight .tag { padding: .38rem .45rem .42rem; }
  .tags.is-tight .tag-t { font-size: .66rem; }
}

/* ---- mobile Belle camera arrows ---------------------------------------- */
.belle-nav { position: fixed; inset: 0; z-index: 11; pointer-events: none; opacity: 0; transition: opacity .3s ease; }
.belle-nav.is-on { opacity: 1; }
.belle-arrow {
  position: absolute; top: 52%; transform: translateY(-50%);
  width: 46px; height: 62px; display: grid; place-items: center;
  border: 1px solid rgba(122, 190, 255, .5); border-radius: 12px;
  background: rgba(6, 10, 17, .82); color: #cfe4ff;
  font: 300 30px/1 var(--font-ui); cursor: pointer; pointer-events: none;
  -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px);
  -webkit-tap-highlight-color: transparent;
  transition: opacity .2s ease;
}
.belle-nav.is-on .belle-arrow { pointer-events: auto; }
.belle-arrow.belle-l { left: 12px; }
.belle-arrow.belle-r { right: 12px; }
.belle-arrow.is-dim { opacity: .32; }
@media (min-width: 861px) { .belle-nav { display: none; } }

/* ---- the walkthrough --------------------------------------------------------
   One offer over the blurred bench, then a tour per exhibit: callouts in the
   product's colour with a ring on the real control, a shield so the exhibit
   is not pressed while it is being explained, and a hand-back card with
   Continue. On a phone the callout is a sheet along the foot of the screen,
   clear of whatever the ring is pointing at. */
.tour { position: fixed; inset: 0; z-index: 11; pointer-events: none; }
.tour > * { pointer-events: auto; }
.tour [hidden] { display: none !important; }
.tour-scrim {
  position: fixed; inset: 0; z-index: 13;
  background: rgba(4, 7, 12, .46);
}
@supports (backdrop-filter: blur(6px)) {
  .tour-scrim { -webkit-backdrop-filter: blur(14px) saturate(.9); backdrop-filter: blur(14px) saturate(.9); }
}
.tour-shield { position: fixed; inset: 0; z-index: 10; background: transparent; cursor: default; }
/* The three cards are .pane (tour.js): the glass, the 1px hue border and
   lit left edge, the corners, the glisten and the rim light all come from
   the pane rules above, and rims.js wires them like any other pane. Only
   their placement and their tighter padding are their own. */
.tour-modal, .tour-callout, .tour-explore {
  position: fixed;
  padding: .85rem 1rem .9rem;
}
.tour-modal {
  z-index: 14; left: 50%; top: 50%; transform: translate(-50%, -50%);
  width: min(32rem, calc(100vw - 32px)); padding: 1.2rem 1.3rem 1.25rem;
}
.tour-modal .tour-lead { font-size: 1.08rem; }
.tour-lead { margin: 0; font: 500 .92rem/1.45 var(--font-ui); color: #F0F5FB; text-shadow: var(--halo); }
.tour-row { display: flex; flex-wrap: wrap; align-items: center; gap: .4rem; margin-top: .75rem; }
/* Every control on the walkthrough is a chip with words on it — Back, Next,
   Minimize, Exit walkthrough — a size up from the page's chips, because these
   are pressed with a thumb. The two that move through the tour sit as one
   group; the two that put it away sit as another, at the far side, and the
   groups wrap as groups on a narrow card. */
.tour-row button.av-chip {
  appearance: none; -webkit-appearance: none; cursor: pointer; font-family: var(--font-data);
  font-size: .62rem; padding: .5rem .7rem;
}
button.tour-back[disabled] { opacity: .35; cursor: default; }
.tour-grp { display: inline-flex; align-items: center; gap: .4rem; }
.tour-grp--away { margin-left: auto; }
.tour-row .tour-end { color: var(--text-mid); }
.tour-row .tour-end:hover { color: var(--text-hi); background: rgba(var(--hud-rgb), .22); }
/* minimized: one pill at the foot of the frame, in the product's colour */
.tour-pill {
  position: fixed; z-index: 12; left: 50%; bottom: calc(var(--gutter) + 1.2rem); transform: translateX(-50%);
  appearance: none; -webkit-appearance: none; cursor: pointer;
  padding: .6rem .95rem; border-radius: 999px;
  font: 600 .6rem/1 var(--font-data); letter-spacing: .16em; text-transform: uppercase;
  color: var(--text-hi); border: 1px solid rgba(var(--hud-rgb), .6);
  background: rgba(6, 10, 17, .82); box-shadow: 0 8px 24px rgba(0, 0, 0, .45);
  white-space: nowrap;
}
.tour-pill:hover { background: rgba(var(--hud-rgb), .18); }
.tour-pill[hidden] { display: none; }
.tour-callout { z-index: 12; left: 0; top: 0; width: min(22rem, calc(100vw - 24px)); will-change: transform; }
.tour-explore {
  z-index: 12; left: 50%; bottom: calc(var(--gutter) + 1.2rem); transform: translateX(-50%);
  width: min(30rem, calc(100vw - 24px));
}
.tour-n { display: block; font: 600 .56rem/1 var(--font-data); letter-spacing: .2em; color: rgb(var(--hud-rgb)); text-shadow: var(--halo); }
.tour-t { margin: .35rem 0 .25rem; font: 600 .95rem/1.2 var(--font-ui); color: #F4F8FD; text-shadow: var(--halo); }
.tour-p { margin: 0; font: 500 .84rem/1.45 var(--font-ui); color: #E3EBF5; text-shadow: var(--halo); max-width: none; }
/* the arrow: a solid triangle in the card's edge colour, on whichever edge faces the target */
.tour-arrow { position: absolute; width: 0; height: 0; border: 8px solid transparent; }
.tour-callout.is-up .tour-arrow    { top: -16px;  left: var(--ax); transform: translateX(-50%); border-bottom-color: rgba(var(--hud-rgb), .95); }
.tour-callout.is-down .tour-arrow  { bottom: -16px; left: var(--ax); transform: translateX(-50%); border-top-color: rgba(var(--hud-rgb), .95); }
.tour-callout.is-left .tour-arrow  { left: -16px;  top: var(--ay); transform: translateY(-50%); border-right-color: rgba(var(--hud-rgb), .95); }
.tour-callout.is-right .tour-arrow { right: -16px; top: var(--ay); transform: translateY(-50%); border-left-color: rgba(var(--hud-rgb), .95); }
.tour-callout.is-lost .tour-arrow, .tour-callout.is-sheet .tour-arrow { display: none; }
/* the phone's sheet: along the foot — or the head, when the ring is in the
   foot (tour.js sets the head's top inline) — never over the ring */
.tour-callout.is-sheet {
  left: 12px; right: 12px; top: auto; bottom: calc(12px + env(safe-area-inset-bottom, 0px));
  width: auto; transform: none !important;
}
.tour-callout.is-sheet.is-head { bottom: auto; }
/* the ring around what is being pointed at */
.tour-ring {
  position: fixed; left: 0; top: 0; pointer-events: none;
  border: 2px solid rgba(var(--hud-rgb), .95); border-radius: 8px;
  box-shadow: 0 0 0 4px rgba(var(--hud-rgb), .22), 0 0 18px rgba(var(--hud-rgb), .55);
  will-change: transform;
}
/* the bar's button */
.hud-tour {
  appearance: none; -webkit-appearance: none; cursor: pointer;
  margin-left: .2rem; padding: .45rem .65rem; border-radius: 4px;
  font: 600 .58rem/1 var(--font-data); letter-spacing: .14em; text-transform: uppercase;
  color: var(--text-mid); border: 1px solid rgba(var(--hud-rgb), .34); background: rgba(6, 10, 17, .4);
  white-space: nowrap; transition: color .18s ease, background-color .18s ease;
}
.hud-tour:hover { color: var(--text-hi); background: rgba(var(--hud-rgb), .12); }
.hud-tour.is-on { color: var(--text-hi); background: rgba(var(--hud-rgb), .22); border-color: rgba(var(--hud-rgb), .7); }
@media (max-width: 860px) {
  .tour-modal { width: calc(100vw - 24px); padding: 1rem 1rem 1.05rem; }
  .tour-modal .tour-lead { font-size: .98rem; }
  .tour-explore { bottom: calc(1rem + env(safe-area-inset-bottom, 0px)); width: calc(100vw - 24px); }
  .tour-pill { bottom: calc(1.1rem + env(safe-area-inset-bottom, 0px)); }
  .tour-callout { padding: .7rem .85rem .75rem; }
  /* brand … Walkthrough, menu: the button takes the free space, the menu
     stays at the edge where a thumb expects it */
  .hud-tour { order: 1; margin-left: auto; font-size: .54rem; padding: .45rem .5rem; }
  .hud-menu { order: 2; margin-left: 0; }
}

/* ?debug=1 readout */
.diag {
  position: fixed; left: 8px; right: 8px; bottom: 8px; z-index: 30; margin: 0;
  padding: .5rem .6rem; font: 400 10px/1.35 var(--font-data); white-space: pre-wrap;
  color: #cfe4ff; background: rgba(4, 7, 12, .86); border: 1px solid rgba(122, 190, 255, .4);
  pointer-events: none;
}
