/* GlassFX — HOTSPOTS, THE NAVIGATION BAR, AND PRESENTING.
   ══════════════════════════════════════════════════════════════════════
   app.css is the workshop's chrome. This is everything that belongs to
   the PRESENTATION — the parts a visitor sees — plus the two bits of the
   workshop that only exist because the presentation does: the drag
   handles on a field, and the ghost that follows a scan onto a scene.

   ONE INK AT FOUR STEPS, and one accent. The tokens are in app.css and
   nothing below here spells a colour by hand.
   ══════════════════════════════════════════════════════════════════════ */

/* ── THE ORB, AND THE LINE THAT JOINS IT TO THE CARD ───────────────
   Both are flat screen elements positioned from a projected world point
   — the same trick the card uses, without the matrix. */

.gfx-orb {
  position: absolute; left: 0; top: 0;
  border: 0; padding: 0; margin: 0;
  border-radius: 50%;
  background: var(--orb-ink, var(--ink));
  box-shadow: 0 0 calc(14px * var(--orb-glow, .3)) calc(3px * var(--orb-glow, .3)) var(--orb-ink, var(--ink));
  cursor: pointer;
  will-change: transform;
}
.gfx-orb[hidden] { display: none; }
.gfx-orb.is-ring { background: transparent; border: 1.6px solid var(--orb-ink, var(--ink)); }
.gfx-orb:hover { filter: brightness(1.3); }
.gfx-orb.is-on { outline: 2px solid var(--acc); outline-offset: 3px; }

.gfx-line {
  position: absolute; left: 0; top: 0;
  transform-origin: 0 50%;
  background: var(--line-ink, var(--ink));
  opacity: .5;
  pointer-events: none;
  will-change: transform, width;
}
.gfx-line[hidden] { display: none; }
.gfx-line.is-dashed { background: none; border-top: 1px dashed var(--line-ink, var(--ink)); }
/* the growable half — see core/slides.js, the renderer owns the outer
   node's transform so the animation lives on this child */
.gfx-line-in { position: absolute; inset: 0; transform-origin: 0 50%; }

.gfx-slide-s { margin-top: .35em; opacity: .85; font-weight: 300; letter-spacing: .06em; }
.gfx-slide.is-title .gfx-slide-h { letter-spacing: .12em; font-weight: 200; }

/* ── THE TYPE (V3 Phase 2) ──────────────────────────────────────────
   One voice for the whole show, from `doc.type`, put on #stage as
   variables by TYPE.apply() in the shell. With none set every fallback
   below is exactly what the rules above already drew. */
.gfx-slide-h, .gfx-page h2 {
  font-family: var(--show-head, inherit);
  font-weight: var(--show-head-weight, 300);
  letter-spacing: var(--show-head-track, .05em);
  text-transform: var(--show-head-case, none);
  text-shadow: var(--show-shadow, none);
}
.gfx-slide.is-title .gfx-slide-h { font-weight: var(--show-head-weight, 200); letter-spacing: var(--show-head-track, .12em); }
.gfx-slide-s, .gfx-slide-b, .gfx-page-sub, .gfx-page-body, .gfx-page-foot {
  font-family: var(--show-body, inherit);
  text-shadow: var(--show-shadow, none);
}
.gfx-page-b { font-family: var(--show-body, inherit); }

/* ── THE BRAND ON A PAGE ─────────────────────────────────────────── */
.gfx-page-logo { max-width: var(--brand-logo-w, 150px); max-height: 18vmin; object-fit: contain; pointer-events: none; }
.gfx-page-logo.is-plate { background: #fff; padding: 8px 14px; border-radius: 8px; box-sizing: content-box; }
.gfx-page-logo--head { margin-bottom: 6px; }
.gfx-page-logo--foot { margin-top: 18px; }
.gfx-page-foot {
  margin-top: 10px; font-size: clamp(11px, 1.4vmin, 14px); letter-spacing: .06em; opacity: .7;
  display: flex; gap: 14px; flex-wrap: wrap; justify-content: inherit;
}
.gfx-page--centre .gfx-page-foot { justify-content: center; }
.gfx-page--right .gfx-page-foot { justify-content: flex-end; }

/* A card that is only on screen because we are composing — in the
   presentation it is closed until something opens it. It says so
   quietly rather than pretending to be finished. */
.gfx-slide.is-ghost { opacity: .42; }
.gfx-slide.is-ghost::after {
  content: ""; position: absolute; inset: -7px;
  border: 1px dashed var(--ink-4); border-radius: 13px; pointer-events: none;
}

/* ── THE GAZE RETICLE ──────────────────────────────────────────────
   The dwell has to be VISIBLE, or holding a look on something feels
   like lag rather than a control. */
.gfx-reticle { display: none; }
body[data-stage="present"] .gfx-reticle {
  display: block;
  position: absolute; left: 50%; top: 50%;
  width: 26px; height: 26px; margin: -13px 0 0 -13px;
  border-radius: 50%; border: 1.5px solid var(--ink-4);
  pointer-events: none; opacity: .45; z-index: 55;
  transition: opacity .2s;
}
body[data-stage="present"] #stage.is-gazing .gfx-reticle,
body[data-stage="present"] .gfx-world.is-gazing + .gfx-reticle { opacity: 1; }
.gfx-reticle::after {
  content: ""; position: absolute; inset: 3px; border-radius: 50%;
  background: conic-gradient(var(--acc) calc(var(--gaze, 0) * 360deg), transparent 0);
}

/* ══ THE 2-D NAVIGATION BAR ═══════════════════════════════════════
   Drawn in BOTH stages, so the author can see and use the thing they
   are building instead of having to preview to find out. */

.nui {
  position: absolute; left: 50%; bottom: 26px; transform: translateX(-50%);
  z-index: 60;
  display: flex; align-items: center; gap: 10px;
  padding: 7px 9px;
  background: var(--glass); backdrop-filter: blur(16px) saturate(1.2);
  border: 1px solid var(--ink-4); border-radius: 26px;
  box-shadow: 0 10px 34px rgb(0 0 0 / .45);
  user-select: none;
}
.nui[hidden] { display: none; }

.nui-b {
  width: 38px; height: 38px; border-radius: 50%;
  background: none; border: 1px solid transparent;
  color: var(--ink-3); display: grid; place-items: center; cursor: pointer;
  transition: color .2s, border-color .2s;
}
.nui-b:hover { color: var(--ink-1); border-color: var(--ink-4); }

.nui-mid { display: flex; align-items: center; gap: 12px; padding: 0 4px; }

.nui-info {
  display: flex; align-items: center; gap: 7px;
  background: none; border: 1px solid var(--ink-4); border-radius: 19px;
  padding: 8px 15px; color: var(--ink-2); cursor: pointer;
  font-size: 11px; letter-spacing: .14em; font-weight: 600;
  transition: background .2s, color .2s, border-color .2s;
}
.nui-info:hover { color: var(--ink-1); border-color: var(--acc-2); }
/* FILL MEANS STATE — the one place a solid is allowed. */
.nui-info.is-on { background: var(--ink); border-color: var(--ink); color: var(--dark); }

.nui-c { color: var(--ink-3); font-size: 11px; font-family: var(--mono); letter-spacing: .08em; }

/* ── THE INFO PANEL ───────────────────────────────────────────────── */

.nui-info-panel {
  position: absolute; left: 50%; bottom: 84px;
  transform: translateX(-50%) translateY(14px);
  z-index: 59;
  width: min(520px, calc(100% - 48px)); max-height: 56%; overflow: auto;
  padding: 22px 26px 20px;
  background: var(--glass); backdrop-filter: blur(20px) saturate(1.25);
  border: 1px solid var(--ink-4); border-radius: 16px;
  box-shadow: 0 18px 50px rgb(0 0 0 / .5);
  opacity: 0; pointer-events: none;
  transition: opacity .32s ease, transform .32s ease;
}
.nui-info-panel[hidden] { display: none; }
.nui-info-panel.is-open { opacity: 1; transform: translateX(-50%) translateY(0); pointer-events: auto; }
.nui-info-panel h3 {
  margin: 0 0 8px; padding-right: 32px;
  font-size: 15px; font-weight: 500; color: var(--ink-1); letter-spacing: .05em;
}
.nui-info-panel p { margin: 0 0 14px; color: var(--ink-2); line-height: 1.6; font-size: 13px; }
.nui-info-panel dl {
  margin: 0; display: grid; grid-template-columns: auto 1fr; gap: 7px 18px; font-size: 12.5px;
}
.nui-info-panel dt { color: var(--ink-3); }
.nui-info-panel dd { margin: 0; color: var(--ink-1); }

.nui-close {
  position: absolute; right: 12px; top: 12px;
  width: 28px; height: 28px; border-radius: 50%;
  background: none; border: 1px solid var(--ink-4); color: var(--ink-3);
  display: grid; place-items: center; cursor: pointer;
}
.nui-close:hover { color: var(--ink-1); border-color: var(--ink-2); }

/* ══ PRESENTING ═══════════════════════════════════════════════════
   The chrome goes. THE VIEWPORT IS NEVER display:none — presenting only
   takes the panels away, and the canvas keeps a non-zero box for the
   whole session. */

.bar-right { grid-row: 1; justify-self: end; }
.bar-play {
  background: var(--ink-5); border: 1px solid var(--ink-4); border-radius: var(--r-ctl);
  padding: 5px 14px; color: var(--ink-2); cursor: default;
  font-size: 11px; letter-spacing: .1em; font-weight: 600;
}
.bar-play:hover { border-color: var(--acc-2); color: var(--ink-1); }

.exit-present { display: none; }
body[data-stage="present"] .exit-present {
  display: block; position: absolute; right: 18px; top: 16px; z-index: 70;
  background: var(--glass); backdrop-filter: blur(12px);
  border: 1px solid var(--ink-4); border-radius: var(--r-ctl);
  padding: 6px 13px; color: var(--ink-3); cursor: pointer;
  font-size: 11px; letter-spacing: .12em; font-weight: 600;
}
body[data-stage="present"] .exit-present:hover { color: var(--ink-1); }

body[data-stage="present"] #bar,
body[data-stage="present"] #dock { display: none; }
body[data-stage="present"] { --rail-l: 0px; --rail-r: 0px; }
body[data-stage="present"] #stage { left: 0; right: 0; }
body[data-stage="present"] .stage-note { display: none; }

/* V3 · THE SHIPPED SHOW (SHIP › Send it out). The page IS the presentation:
   no way back to an editor that is not there. */
body.is-show .exit-present,
body.is-show #bar, body.is-show #dock, body.is-show .v3-strip,
body.is-show .v3-tools, body.is-show #home { display: none !important; }
body.is-show #stage { left: 0; right: 0; }
/* the storyboard's room at the bottom is the editor's — and a show waits
   on its cover or its loading screen before the engine flips the stage to
   presenting, which left a 122 px black band under a window in the page */
body.is-show { --strip-h: 0px !important; --rail-l: 0px !important; --rail-r: 0px !important; }
.show-fail {
  position: absolute; inset: 0; display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 8px; z-index: 80;
  color: #eee; font: 15px/1.5 system-ui, sans-serif; text-align: center; padding: 24px;
}
.show-fail span { color: #999; font-size: 13px; }

/* ON THEIR WEBSITE (SHIP › On their website) */
/* the scroll-through: the page's scroll is the control, so none of the show's own */
body.is-scrollwin #nav2d, body.is-scrollwin #navMarks, body.is-scrollwin #info2d,
body.is-scrollwin #toast { display: none !important; }
/* a window in the page: its cover, until pressed */
.show-cover {
  position: absolute; inset: 0; z-index: 90; cursor: pointer;
  background: #0b0b0d center / cover no-repeat;
  display: flex; align-items: flex-end; justify-content: flex-start;
}
.show-cover::before {
  content: ''; position: absolute; inset: 0;
  background: linear-gradient(to top, rgba(0,0,0,.72) 0%, rgba(0,0,0,.18) 55%, rgba(0,0,0,0) 100%);
}
.show-cover__in {
  position: relative; display: flex; flex-direction: column; align-items: flex-start; gap: 6px;
  padding: clamp(16px, 4vw, 40px); color: #fff; max-width: 100%;
}
.show-cover__in b {
  font-family: var(--show-head, system-ui, sans-serif); font-weight: var(--show-head-weight, 300);
  font-size: clamp(20px, 4.4vw, 44px); line-height: 1.1; letter-spacing: var(--show-head-track, .02em);
  text-transform: var(--show-head-case, none);
}
.show-cover__in span { font-family: var(--show-body, system-ui, sans-serif); font-size: clamp(12px, 1.6vw, 15px); color: rgba(255,255,255,.8); }
.show-cover__in i {
  font-style: normal; margin-top: 10px; padding: 10px 20px; border-radius: 999px;
  border: 1px solid rgba(255,255,255,.35); background: rgba(255,255,255,.12);
  -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px);
  font: 600 11px/1 system-ui, sans-serif; letter-spacing: .14em; text-transform: uppercase;
}
.show-cover:hover .show-cover__in i, .show-cover:focus-visible .show-cover__in i { background: rgba(255,255,255,.24); }
.show-full {
  position: absolute; top: 12px; right: 12px; z-index: 70; width: 34px; height: 34px;
  display: flex; align-items: center; justify-content: center; border-radius: 999px;
  border: 1px solid rgba(255,255,255,.28); background: rgba(10,10,12,.45); color: #fff;
  -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px); text-decoration: none; font-size: 16px;
}

/* ══ PANEL EXTRAS ═════════════════════════════════════════════════ */

.gfx-hint {
  color: var(--ink-3); font-size: 11px; line-height: 1.5;
  padding: 7px 2px 8px; border-top: 1px solid var(--ink-5); margin-top: 6px;
}

.gfx-item .n em {
  display: block; font-style: normal; color: var(--ink-3); font-size: 10.5px; margin-top: 1px;
}
.gfx-item.is-picked { box-shadow: inset 0 0 0 1px var(--acc-2); }
.gfx-item.is-live::after {
  content: ""; width: 5px; height: 5px; border-radius: 50%;
  background: var(--acc); flex: 0 0 auto;
}
.gfx-item.is-drop { background: var(--acc-2); color: var(--dark); }
.gfx-drag { cursor: grab; }
body.gfx-dragging .gfx-drag { cursor: grabbing; }

.gfx-ghost {
  position: fixed; z-index: 1000; pointer-events: none;
  transform: translate(12px, 10px);
  background: var(--acc); color: #17130a;
  font-weight: 600; font-size: 11.5px;
  padding: 5px 11px; border-radius: var(--r-ctl);
  box-shadow: 0 8px 22px rgb(0 0 0 / .45);
}

.gfx-rows { display: flex; flex-direction: column; gap: 4px; margin: 6px 0; }
.gfx-row2 { display: flex; gap: 4px; }
.gfx-row2 input {
  flex: 1 1 0; min-width: 0;
  background: var(--dark); border: 1px solid var(--ink-4); border-radius: 5px;
  padding: 5px 7px; color: var(--ink-1); font: inherit; font-size: 11.5px;
}
.gfx-row2 input:focus { outline: 0; border-color: var(--acc-2); }
.gfx-row2 button {
  flex: 0 0 auto; width: 26px;
  background: none; border: 1px solid var(--ink-4); border-radius: 5px;
  color: var(--ink-3); cursor: default; display: grid; place-items: center;
}
.gfx-row2 button:hover { color: var(--warn); border-color: var(--warn); }

/* ══ DRAGGABLE FIELDS ═════════════════════════════════════════════
   The WHOLE ROW is the handle — the label, the gap and the field — not
   the 12-pixel knob Tweakpane hides at the left edge. The input goes
   back to a text caret only once it actually has focus. */

.gfx-scrub { cursor: ew-resize; user-select: none; touch-action: none; }
.gfx-scrub input:focus { cursor: text; }
.gfx-scrub.is-scrubbing { background: var(--ink-5); border-radius: 5px; }
.gfx-scrub.is-scrubbing .tp-txtv_i { color: var(--acc); }

/* One "I am dragging" class, and the innermost drag wins. Two of them
   fighting is how a cursor ends up stuck as a resize arrow. */
body.gfx-dragging { cursor: ew-resize; }
body.gfx-dragging * { cursor: inherit !important; }

/* ══ THE NAVIGATION — EVERY CONTROL ITS OWN PILL ════════════════════
   One long bar makes three unrelated jobs look like one control. Apart,
   each pill says exactly what it is, and the middle of the screen is
   where the thing you press most often lives.

              ▬ ─ ─ ─          where you are, on its own, above
        ‹     INFO     ›       back · info · on, INFO centred          */

.nui-pill {
  background: var(--glass);
  backdrop-filter: blur(16px) saturate(1.2);
  border: 1px solid var(--ink-4);
  color: var(--ink-2);
  box-shadow: 0 8px 26px rgb(0 0 0 / .42);
  cursor: pointer;
  transition: background .22s, color .22s, border-color .22s;
}
.nui-pill[hidden] { display: none; }

/* where you are */
.nui-markpill {
  position: absolute; left: 50%; bottom: 92px; transform: translateX(-50%);
  z-index: 60; cursor: default;
  display: flex; align-items: center; gap: 9px;
  /* NO PILL BEHIND THE MARKS. The glass panel was a second object
     competing with the three pills below it for the same corner of the
     eye; short heavy marks are legible on their own and the picture is
     not covered twice. */
  padding: 4px 0;
}
.nui-mark { background: none; border: 0; padding: 5px 0; cursor: pointer; display: block; }
.nui-mark i { display: block; background: var(--ink-4); transition: background .28s ease, width .28s ease, transform .28s ease; }
/* SHORTER AND HEAVIER. A long thin rule reads as a divider; a short
   thick one reads as a step in a sequence, which is what it is. */
.nui-marks--lines .nui-mark i { width: 11px; height: 4px; border-radius: 3px; }
.nui-marks--lines .nui-mark:hover i { background: var(--ink-3); }
.nui-marks--lines .nui-mark.is-on i { width: 21px; background: var(--ink-1); }
.nui-marks--dots .nui-mark i { width: 6px; height: 6px; border-radius: 50%; }
.nui-marks--dots .nui-mark:hover i { background: var(--ink-3); }
.nui-marks--dots .nui-mark.is-on i { background: var(--ink-1); transform: scale(1.5); }

/* how to move — three pills, and the middle of the row is the middle
   of the picture, so INFO sits dead centre whatever is either side */
.nui-row {
  position: absolute; left: 50%; bottom: 26px; transform: translateX(-50%);
  z-index: 60;
  display: flex; align-items: center; gap: 14px;
  user-select: none;
}
.nui-row[hidden] { display: none; }

.nui-round {
  width: 48px; height: 48px; border-radius: 50%;
  display: grid; place-items: center; padding: 0;
}
.nui-round:hover { color: var(--ink-1); border-color: var(--acc-2); }

.nui-word {
  border-radius: 24px; padding: 15px 26px;
  font-size: 11px; letter-spacing: .18em; font-weight: 600;
}
.nui-word:hover { color: var(--ink-1); border-color: var(--acc-2); }
/* FILL MEANS STATE — the one place a solid is allowed */
.nui-word.is-on { background: var(--ink); border-color: var(--ink); color: var(--dark); }

/* keeps INFO centred when there is no prev or next to balance it */
.nui-gap { width: 48px; height: 1px; }

/* the panel INFO opens: centred over it, not off in a corner */
.nui-info-panel {
  left: 50%; right: auto; bottom: 152px;
  transform: translateX(-50%) translateY(14px);
  width: min(480px, calc(100% - 48px));
}
.nui-info-panel.is-open { transform: translateX(-50%) translateY(0); }

@media (max-width: 720px) {
  .nui-row { gap: 10px; bottom: 20px; }
  .nui-word { padding: 13px 20px; }
  .nui-round { width: 44px; height: 44px; }
  .nui-markpill { bottom: 80px; }
  .nui-info-panel { bottom: 136px; }
}

/* A WINDOW IN A CLIENT'S PAGE IS SHORT (a 16:9 box in a text column is
   ~200 px tall): full-size pills covered the bottom third of it. Only a
   short window is touched — the whole-page show keeps the sizes above. */
@media (max-height: 460px) {
  .nui-row { gap: 8px; bottom: 10px; }
  .nui-word { padding: 9px 16px; font-size: 10px; }
  .nui-round { width: 32px; height: 32px; }
  .nui-gap { width: 32px; }
  .nui-markpill { bottom: 50px; }
  .nui-info-panel { bottom: 56px; max-height: calc(100% - 70px); overflow: auto; }
}

/* ══ THE RIDE BAR (27 Sep 2026) — the Hargreaves pill on its timeline ══
   A looping orbit ride (core/orbit-ride.js) owns the middle of the bottom
   edge: a hairline with the start and end at its ends, and the pill riding
   ON it at the ride's own moment — tap it to play or pause, drag it along
   to scrub, up or down to pull the ride in or let it out. Paused, the line
   and the times come up bright; playing, they sink back. The words go to
   the corners. (shell.js makeRideBar) */
.nui-ride {
  position: absolute; left: 50%; bottom: 30px; transform: translateX(-50%);
  width: min(560px, calc(100% - 340px)); height: 48px;
  z-index: 61; display: flex; align-items: center; gap: 14px;
  pointer-events: none; user-select: none; -webkit-user-select: none;
  color: var(--ink-1, #fff); font-size: 11px; letter-spacing: .14em; font-weight: 600;
}
.nui-ride[hidden] { display: none; }
.nui-ride__t {
  flex: none; min-width: 36px; font-variant-numeric: tabular-nums;
  text-shadow: 0 1px 8px rgb(0 0 0 / .75);
  opacity: .9; transition: opacity .4s ease;
}
.nui-ride__t0 { text-align: right; }
.nui-ride__track { position: relative; flex: 1; height: 48px; pointer-events: auto; cursor: pointer; touch-action: none; }
.nui-ride__track::before, .nui-ride__fill {
  content: ''; position: absolute; left: 0; top: 50%; height: 2px; margin-top: -1px; border-radius: 1px;
  transition: background .4s ease, height .2s ease, margin .2s ease;
}
.nui-ride__track::before { right: 0; background: rgb(255 255 255 / .34); box-shadow: 0 0 6px rgb(0 0 0 / .35); }
.nui-ride__fill { width: 0; background: rgb(255 255 255 / .9); pointer-events: none; }
/* PLAYING: the line sinks to a hairline and the times step back — but the
   start and the end stay readable (his ask: clear, but minimal) */
.nui-ride.is-playing .nui-ride__t { opacity: .62; }
.nui-ride.is-playing .nui-ride__track::before { background: rgb(255 255 255 / .22); }
.nui-ride.is-playing .nui-ride__fill { background: rgb(255 255 255 / .6); }
/* SCRUBBING: the line thickens under the hand */
.nui-ride.is-scrub .nui-ride__track::before, .nui-ride.is-scrub .nui-ride__fill { height: 3px; margin-top: -1.5px; }
.nui-ride.is-scrub .nui-ride__t { opacity: 1; }

.nui-ride__pill {
  position: absolute; left: 0; top: 50%; transform: translate(-50%, -50%);
  width: 64px; height: 40px; border-radius: 22px; padding: 0;
  display: grid; place-items: center; cursor: grab; touch-action: none; pointer-events: auto;
  color: var(--ink-1, #fff);
  transition: background .22s, color .22s, border-color .22s, width .2s ease, height .2s ease;
}
.nui-ride__pill:hover { border-color: var(--acc-2, rgb(255 255 255 / .6)); }
.nui-ride__pill.spring { transition: transform .55s cubic-bezier(.2,.9,.25,1.15), background .22s, border-color .22s, width .2s ease, height .2s ease; }
.nui-ride__pill svg { width: 16px; height: 16px; fill: currentColor; grid-area: 1 / 1; }
.nui-ride__pill .pa { display: none; }
.nui-ride.is-playing .nui-ride__pill .pa { display: block; }
.nui-ride.is-playing .nui-ride__pill .pl { display: none; }
.nui-ride.is-scrub .nui-ride__pill { cursor: grabbing; width: 70px; height: 44px; }
/* THE TIME NOW, above the pill where a thumb on it cannot hide it */
.nui-ride__now {
  position: absolute; left: 50%; bottom: calc(100% + 9px); transform: translateX(-50%);
  font-size: 12px; letter-spacing: .14em; white-space: nowrap; font-variant-numeric: tabular-nums;
  text-shadow: 0 1px 10px rgb(0 0 0 / .8);
  opacity: 0; transition: opacity .3s ease, font-size .2s ease, bottom .2s ease; pointer-events: none;
}
.nui-ride:not(.is-playing) .nui-ride__now { opacity: .9; }
.nui-ride.is-scrub .nui-ride__now { opacity: 1; font-size: 16px; bottom: calc(100% + 13px); }
/* UP AND DOWN IS THE ZOOM: a quiet + above and − below while it is held */
.nui-ride__zoom {
  position: absolute; left: 50%; transform: translateX(-50%); font-size: 15px; font-weight: 400;
  text-shadow: 0 1px 8px rgb(0 0 0 / .8); opacity: 0; transition: opacity .2s ease; pointer-events: none;
}
.nui-ride__zoom--in { bottom: calc(100% + 6px); }
.nui-ride__zoom--out { top: calc(100% + 4px); }
.nui-ride.is-zoom .nui-ride__now { opacity: 0; }
.nui-ride.is-zoom .nui-ride__zoom { opacity: .45; }
.nui-ride.is-in .nui-ride__zoom--in, .nui-ride.is-out .nui-ride__zoom--out { opacity: 1; }

/* the words, in the corners (paintNav: .nui-row--ride) */
.nui-row--ride { left: 0; right: 0; transform: none; height: 0; bottom: 0; gap: 0; }
.nui-row--ride .nui-corner { position: absolute; bottom: 30px; }
.nui-row--ride .nui-corner--l { left: 28px; }
.nui-row--ride .nui-corner--r { right: 28px; }
body.has-ride-bar .nui-info-panel { bottom: 110px; }

@media (max-width: 720px) {
  .nui-ride { bottom: 22px; width: calc(100% - 232px); gap: 8px; font-size: 10px; letter-spacing: .08em; }
  .nui-ride__t { min-width: 28px; }
  .nui-ride__pill { width: 54px; height: 38px; }
  .nui-ride.is-scrub .nui-ride__pill { width: 58px; height: 40px; }
  .nui-row--ride .nui-corner { bottom: 26px; padding: 12px 14px; letter-spacing: .12em; }
  .nui-row--ride .nui-corner--l { left: 12px; }
  .nui-row--ride .nui-corner--r { right: 12px; }
}
/* a phone held upright: the words go up a row so the line gets the width */
@media (max-width: 480px) {
  .nui-ride { width: calc(100% - 40px); bottom: 20px; }
  .nui-row--ride .nui-corner { bottom: 80px; }
  body.has-ride-bar .nui-info-panel { bottom: 136px; }
}
@media (max-height: 460px) {
  .nui-ride { bottom: 8px; height: 40px; }
  .nui-row--ride .nui-corner { bottom: 10px; }
}

/* ══ A PAGE OVER THE ROOM ═══════════════════════════════════════════
   A landing, home or exit page. Screen-stuck over the scan, not a card
   standing in it: this is chrome for the visitor, and the room behind
   is the picture it is laid over.

   THE SAME ELEMENT IN BOTH STAGES. What the author builds is what the
   visitor gets — no separate preview to drift out of step. */
.gfx-page {
  position: absolute; inset: 0; z-index: 560;
  display: flex; flex-direction: column; justify-content: center;
  gap: 14px; padding: 6vmin 7vmin;
  pointer-events: none;
  /* a wash so words stay readable over a bright scan, without hiding it */
  background: radial-gradient(120% 90% at 50% 50%, rgb(6 6 5 / .55), rgb(6 6 5 / .18) 70%, transparent);
  color: var(--ink-1, #f2eee6);
}
.gfx-page[hidden] { display: none; }
.gfx-page > *, .gfx-page button { pointer-events: auto; }

.gfx-page--left   { align-items: flex-start; text-align: left; }
.gfx-page--centre { align-items: center; text-align: center; }
.gfx-page--right  { align-items: flex-end; text-align: right; }

.gfx-page h2 {
  margin: 0; font-weight: 200; letter-spacing: .06em;
  font-size: clamp(26px, 5.2vmin, 64px); line-height: 1.06;
  white-space: pre-wrap; max-width: 22em;
}
.gfx-page-sub {
  margin: 0; font-weight: 300; opacity: .82;
  font-size: clamp(13px, 2.1vmin, 22px); white-space: pre-wrap; max-width: 34em;
}
.gfx-page-body {
  margin: 0; opacity: .62; font-weight: 300; line-height: 1.5;
  font-size: clamp(12px, 1.6vmin, 16px); white-space: pre-wrap; max-width: 40em;
}

.gfx-page-btns { display: flex; gap: 10px; margin-top: 6px; }
.gfx-page--stack .gfx-page-btns { flex-direction: column; align-items: inherit; }
.gfx-page--row   .gfx-page-btns { flex-direction: row; flex-wrap: wrap; justify-content: inherit; }

/* THE BUTTONS ARE GLASS, like everything else in this build */
.gfx-page-b {
  font: inherit; font-size: clamp(12px, 1.5vmin, 15px);
  letter-spacing: .04em; cursor: pointer;
  padding: .75em 1.6em; border-radius: 999px;
  transition: border-color .16s, background .16s, color .16s;
}
.gfx-page-b--glass {
  background: rgb(255 255 255 / .06);
  backdrop-filter: blur(14px) saturate(1.2);
  -webkit-backdrop-filter: blur(14px) saturate(1.2);
  border: 1px solid rgb(242 238 230 / .34); color: var(--ink-1, #f2eee6);
}
.gfx-page-b--glass:hover { background: rgb(255 255 255 / .12); border-color: rgb(242 238 230 / .7); }
.gfx-page-b--solid {
  background: var(--show-accent, var(--acc, #c9a227)); border: 1px solid var(--show-accent, var(--acc, #c9a227));
  color: #17130a; font-weight: 600;
}
/* a light accent (the Clean Listing white) needs dark ink; a dark one, light */
.gfx-page-b--solid { color: var(--show-accent-ink, #17130a); }
.gfx-page-b--solid:hover { filter: brightness(1.1); }
.gfx-page-b--quiet {
  background: none; border: 1px solid transparent;
  color: var(--ink-2, rgb(242 238 230 / .72)); text-decoration: underline;
  text-underline-offset: .35em; text-decoration-thickness: 1px;
  padding-left: .4em; padding-right: .4em;
}
.gfx-page-b--quiet:hover { color: var(--ink-1, #f2eee6); }

/* ══ THE GAZE ═══════════════════════════════════════════════════════
   LIFTED FROM THE STUPA TOUR (`tour.js:gazeTick` + `tour.css:.awake`),
   which is where this was actually built and finished. It is not in
   GLASSWORKS 0.99 — everything called "gaze" there is the camera's look
   easing home, a different thing entirely.

   AN ORB AT REST IS BARELY THERE. Aim the middle of the view at it and
   it wakes: grows, brightens, starts breathing. Look away and it
   settles back. Only the nearest orb inside the reticle is ever awake.

   `--gaze-rest` AND `--gaze-scale` COME FROM THE DOCUMENT, so the
   author sets how shy the orbs are. The original's rest opacity is
   0.16, not 0 — deliberately: a visitor can see that something is there
   and go and look, where a truly invisible orb is found by accident. 0
   is allowed for the author who wants exactly that. */
.gfx-world.is-gaze .gfx-orb {
  opacity: calc(var(--gaze-rest, .16) * var(--orb-alpha, 1));
  transform-origin: 50% 50%;
  transition: opacity var(--gaze-secs, .26s) ease,
              scale var(--gaze-secs, .26s) ease,
              box-shadow var(--gaze-secs, .26s) ease;
  scale: var(--gaze-scale, .45);
  /* A SLEEPING ORB IS NOT CLICKABLE. Without this a click on bare scan
     near where an invisible orb happens to be opens it, which reads as
     the room being haunted. */
  pointer-events: none;
}
.gfx-world.is-gaze .gfx-orb.is-awake {
  opacity: var(--orb-alpha, 1);
  scale: 1;
  pointer-events: auto;
  box-shadow: 0 0 calc(22px * var(--orb-glow, .3)) calc(6px * var(--orb-glow, .3)) var(--orb-ink, var(--ink));
}

/* the reticle — the dwell has to be VISIBLE or it feels like lag */
.gfx-reticle {
  position: absolute; left: 50%; top: 50%;
  width: 5px; height: 5px; margin: -2.5px 0 0 -2.5px;
  border-radius: 50%; pointer-events: none;
  background: rgb(242 238 230 / .35);
  transition: background .2s ease, box-shadow .2s ease;
}
.gfx-world.is-gaze-hot .gfx-reticle {
  background: rgb(242 238 230 / .9);
  box-shadow: 0 0 8px rgb(242 238 230 / .6);
}
.gfx-reticle[hidden] { display: none; }

@media (prefers-reduced-motion: reduce) {
  .gfx-world.is-gaze .gfx-orb { transition-duration: .01s; }
}

/* ══ OPENING A HOTSPOT ══════════════════════════════════════════════
   THE ORDER IS THE POINT: the orb is already up, the branch grows out
   of it, and the card unfolds at the end of the branch. Played together
   it reads as three things appearing at once; played in sequence it
   reads as ONE thing growing, which is what a hotspot is.

   The delays come from DOC.REVEAL_ANIMS as `--r-stem` and `--r-card`,
   so the timing is data and a fifth style is a line in that table.

   EVERY ONE ANIMATES A CHILD, never `.gfx-slide` or `.gfx-line`
   themselves — the renderer writes `transform` on both of those every
   frame and would win. */

.gfx-line.is-revealing .gfx-line-in {
  animation: gfx-stem var(--r-secs, .42s) cubic-bezier(.22,.9,.3,1) var(--r-stem, .05s) both;
}
@keyframes gfx-stem { from { transform: scaleX(0); } to { transform: scaleX(1); } }

.gfx-slide.gfx-r-grow.is-revealing > * {
  animation: gfx-open-grow var(--r-secs, .42s) cubic-bezier(.16,.84,.34,1) var(--r-card, .34s) both;
}
@keyframes gfx-open-grow {
  from { opacity: 0; transform: scale(.72); }
  to   { opacity: 1; transform: scale(1); }
}

.gfx-slide.gfx-r-fade.is-revealing > * {
  animation: gfx-open-fade var(--r-secs, .45s) ease var(--r-card, .06s) both;
}
@keyframes gfx-open-fade { from { opacity: 0; } to { opacity: 1; } }

.gfx-slide.gfx-r-rise.is-revealing > * {
  animation: gfx-open-rise var(--r-secs, .5s) cubic-bezier(.16,.84,.34,1) var(--r-card, .3s) both;
}
@keyframes gfx-open-rise {
  from { opacity: 0; transform: translateY(14%) scale(.94); }
  to   { opacity: 1; transform: translateY(0) scale(1); }
}

/* folding needs a hinge and some depth, or it reads as a squash */
.gfx-slide.gfx-r-fold.is-revealing > * {
  transform-origin: 50% 0;
  animation: gfx-open-fold var(--r-secs, .55s) cubic-bezier(.2,.8,.3,1) var(--r-card, .3s) both;
}
@keyframes gfx-open-fold {
  from { opacity: 0; transform: perspective(600px) rotateX(-78deg); }
  to   { opacity: 1; transform: perspective(600px) rotateX(0deg); }
}

@media (prefers-reduced-motion: reduce) {
  .gfx-line.is-revealing .gfx-line-in { animation: none; }
  .gfx-slide[class*="gfx-r-"].is-revealing > * {
    animation: gfx-open-fade .3s ease both !important;
  }
}

/* the ways-to-move pill, above the back/info/on row — a separate pill
   because "how I move" and "where I am in the show" are different
   questions and one bar would read as one control */
.nui-modes { display: flex; gap: 4px; margin-bottom: 8px; }
.nui-modes[hidden] { display: none; }

/* ── THE ICON ROW: beds · baths · cars · area (cards and pages) ──
   A block of inline items, so it follows the card's own text-align. */
.gfx-facts { display: block; margin: .45em 0 .2em; line-height: 1.2; }
.gfx-fact { display: inline-flex; align-items: center; gap: .38em; margin: .15em 1.05em .15em 0; white-space: nowrap; vertical-align: middle; }
.gfx-fact:last-child { margin-right: 0; }
.gfx-fact svg { width: 1.3em; height: 1.3em; flex: none; opacity: .92; }
.gfx-fact b { font-weight: inherit; letter-spacing: .02em; }
.gfx-page-facts { font-size: 15px; margin: .6em 0 .4em; font-family: var(--show-body, inherit); }
