/* GlassFX v0.1 — THE CHROME.
   ══════════════════════════════════════════════════════════════════════
   ONE INK AT FOUR STEPS, and one accent. Never write an rgba() below the
   token block: a colour spelled by hand is a colour a theme cannot reach,
   and the shipped presentations prove the rule pays — the Stark show is
   entirely red without one Stark-specific file existing.

   THE VIEWPORT IS NEVER display:none. Not in any tab, not while a sheet
   is open. It keeps a non-zero box for the whole session.
   ══════════════════════════════════════════════════════════════════════ */

:root {
  --ink: #f2eee6;
  --ink-1: rgb(242 238 230 / 1);
  --ink-2: rgb(242 238 230 / .72);
  --ink-3: rgb(242 238 230 / .45);
  --ink-4: rgb(242 238 230 / .20);
  --ink-5: rgb(242 238 230 / .09);
  --dark: #0b0a09;
  --pit: #131211;
  --acc: #c9a227;
  --acc-2: rgb(201 162 39 / .55);
  --warn: #e0663c;

  --glass: rgb(19 18 17 / .82);
  --bar-h: 42px;
  --tab-h: 30px;
  --r-ctl: 7px;
  --r-pane: 10px;
  --rail-l: 300px;
  --rail-r: 330px;

  --font: "Inter", "Segoe UI Variable Text", "Segoe UI", system-ui, sans-serif;
  --mono: "Cascadia Mono", "Consolas", ui-monospace, monospace;

  color-scheme: dark;
}

* { box-sizing: border-box; }

html, body {
  margin: 0; height: 100%;
  background: var(--dark);
  color: var(--ink-2);
  font: 13px/1.45 var(--font);
  overflow: hidden;
  -webkit-font-smoothing: antialiased;
}

button { font: inherit; color: inherit; }

#app { position: fixed; inset: 0; display: flex; flex-direction: column; }

/* ══ THE BAR ═══════════════════════════════════════════════════════ */

#bar {
  height: calc(var(--bar-h) + var(--tab-h));
  flex: 0 0 auto;
  display: grid;
  grid-template-columns: auto auto 1fr auto;
  grid-template-rows: var(--bar-h) var(--tab-h);
  align-items: center;
  background: var(--pit);
  border-bottom: 1px solid var(--ink-5);
  padding: 0 10px 0 14px;
  gap: 0 14px;
  user-select: none;
}

.bar-mark {
  grid-row: 1; font-weight: 600; letter-spacing: .14em;
  font-size: 12px; color: var(--ink-1); white-space: nowrap;
}
.bar-mark i { font-style: normal; color: var(--acc); margin-left: 6px; font-size: 10px; letter-spacing: .06em; }

.bar-menus { grid-row: 1; display: flex; gap: 2px; position: relative; }
.bar-menu {
  background: none; border: 0; padding: 5px 10px; border-radius: var(--r-ctl);
  color: var(--ink-3); cursor: default;
}
.bar-menu:hover, .bar-menu.is-on { background: var(--ink-5); color: var(--ink-1); }

.bar-project {
  grid-row: 1; justify-self: center; display: flex; align-items: center; gap: 8px;
  color: var(--ink-2); font-size: 12px; min-width: 0;
}
#projName { font-weight: 500; color: var(--ink-1); }
.bar-where { color: var(--ink-3); font-size: 11px; max-width: 42vw; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bar-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--acc); opacity: 0; transition: opacity .2s; }
body.is-dirty .bar-dot { opacity: 1; }

/* the tabs. A tab is a full workspace; switching one writes ONE attribute
   on <body> and shows a different set of panels. Nothing is rebuilt. */
.bar-tabs { grid-row: 2; grid-column: 1 / -1; display: flex; gap: 2px; align-self: end; }
.bar-tab {
  background: none; border: 0; border-bottom: 2px solid transparent;
  padding: 4px 16px 7px; color: var(--ink-3); cursor: default;
  font-size: 11px; letter-spacing: .13em; font-weight: 600;
}
.bar-tab:hover { color: var(--ink-2); }
.bar-tab.is-on { color: var(--ink-1); border-bottom-color: var(--acc); }

/* ══ MENUS ═════════════════════════════════════════════════════════ */

.menu-pop {
  position: absolute; top: calc(var(--bar-h) - 6px); left: 0; z-index: 400;
  min-width: 250px; padding: 5px;
  background: var(--glass); backdrop-filter: blur(16px) saturate(1.3);
  border: 1px solid var(--ink-4); border-radius: var(--r-pane);
  box-shadow: 0 16px 40px rgb(0 0 0 / .55);
}
.menu-row {
  display: flex; align-items: center; gap: 10px; width: 100%;
  background: none; border: 0; text-align: left;
  padding: 7px 10px; border-radius: var(--r-ctl); color: var(--ink-2); cursor: default;
}
.menu-row:hover:not(:disabled) { background: var(--ink-5); color: var(--ink-1); }
.menu-row:disabled { color: var(--ink-4); }
.menu-row .k { margin-left: auto; color: var(--ink-3); font-size: 11px; font-family: var(--mono); }
.menu-rule { height: 1px; background: var(--ink-5); margin: 5px 8px; }
.menu-note { padding: 4px 10px 8px; color: var(--ink-3); font-size: 11px; line-height: 1.4; }

/* ══ WORK + STAGE ══════════════════════════════════════════════════ */

#work { position: relative; flex: 1 1 auto; min-height: 0; }

/* inset by the rails so the picture is never hidden under a panel */
/* TWO STACKING CONTEXTS, NOT A Z-INDEX RACE. The stage gets z-index 0 and
   the dock z-index 1, so every panel is above every slide by structure.
   Without this the slides' own depth ordering (10, 11, 12 …) competes
   directly with the panels' and a floating panel ends up behind the type
   it is meant to be editing. */
#stage {
  position: absolute; top: 0; bottom: 0; z-index: 0;
  left: var(--rail-l); right: var(--rail-r);
  overflow: hidden; background: #000;
  transition: left .12s ease, right .12s ease;
}
#application-canvas { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }

.gfx-world { position: absolute; inset: 0; pointer-events: none; }
.gfx-world > * { pointer-events: auto; }

.stage-note {
  position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%);
  text-align: center; color: var(--ink-3); font-size: 13px; line-height: 1.7;
  pointer-events: none; max-width: 420px;
}
.stage-note b { display: block; color: var(--ink-2); font-weight: 500; margin-bottom: 6px; }
.stage-note:empty { display: none; }

/* ══ SLIDES IN THE ROOM ════════════════════════════════════════════ */

.gfx-slide {
  position: absolute; left: 0; top: 0;
  transform-origin: 0 0;
  padding: 18px 24px;
  color: var(--slide-ink, #f2eee6);
  cursor: default;
  will-change: transform;
}
/* THE PANEL'S LOOK IS NOT HERE ANY MORE. It was a fill, a border, a
   14 px radius and an 8 px blur, and only the two colours could be
   reached by a control — so the blur and the corner, which are most of
   what glass IS, were not authorable. It is now the slide's `box`,
   written by DOC.applyBox in core/slides.js, and the defaults of that
   box reproduce exactly what these four lines drew.
   The class stays: the Slide Builder and the selection outline both ask
   whether a card has a panel. */
/* ── A TYPED LINE BREAK IS A LINE BREAK ───────────────────────────
   Every run is `pre-wrap`. The panel has had real textareas for a while
   and the in-room editor takes Shift+Enter, so an author could type two
   lines into a sub-heading and watch them render as one — the newline
   was in the document and the renderer threw it away. `pre-wrap` keeps
   the breaks the author typed AND still wraps long lines at the card's
   width, which is what a card needs; `pre` would do only the first and
   run the text off the edge. */
.gfx-slide-h,
.gfx-slide-s,
.gfx-slide-b { white-space: pre-wrap; overflow-wrap: anywhere; }
.gfx-slide-h { font-weight: 300; letter-spacing: .05em; line-height: 1.1; }
.gfx-slide-b { margin-top: .55em; opacity: .78; line-height: 1.35; font-weight: 300; }
.gfx-slide.is-on { outline: 2px solid var(--acc); outline-offset: 5px; }

/* ══ THE DOCK ══════════════════════════════════════════════════════ */

/* THE COLUMNS ARE NAMED, NOT COUNTED. The float layer and the drop hint
   are position:absolute, so they are not grid items at all — leave the
   placement to auto and the right-hand rail lands in the 6px grip column
   and draws 2px wide. Every in-flow child says which column it is in. */
#dock, .gfx-dock {
  position: absolute; inset: 0; z-index: 1;
  display: grid;
  grid-template-columns: var(--rail-l) var(--grip-l, 6px) 1fr var(--grip-r, 6px) var(--rail-r);
  pointer-events: none;
}
.gfx-rail {
  pointer-events: auto;
  overflow-y: auto; overflow-x: hidden;
  padding: 8px; display: flex; flex-direction: column; gap: 8px;
  background: rgb(11 10 9 / .86); backdrop-filter: blur(14px);
}
.gfx-rail--left { grid-column: 1; border-right: 1px solid var(--ink-5); }
.gfx-railgrip--left { grid-column: 2; }
.gfx-railgrip--right { grid-column: 4; }
.gfx-rail--right { grid-column: 5; border-left: 1px solid var(--ink-5); }
.gfx-dock.no-left .gfx-rail--left, .gfx-dock.no-left .gfx-railgrip--left,
.gfx-dock.no-right .gfx-rail--right, .gfx-dock.no-right .gfx-railgrip--right { display: none; }

.gfx-railgrip { pointer-events: auto; cursor: col-resize; }
.gfx-railgrip:hover { background: var(--acc-2); }

.gfx-floatlayer { position: absolute; inset: 0; pointer-events: none; }

.gfx-drophint {
  position: absolute; top: 0; bottom: 0; width: var(--rail-l);
  background: var(--acc-2); opacity: .22; pointer-events: none; z-index: 500;
}
.gfx-drophint--left { left: 0; }
.gfx-drophint--right { right: 0; left: auto; width: var(--rail-r); }

/* ── a panel ── */
/* [hidden] LOSES to display:flex. The attribute's default rule is
   `display:none` at UA weight, and any author `display` beats it — so a
   panel belonging to another tab stayed on screen. This one line is the
   fix, and it has to be !important because the flex rule is right below. */
.gfx-panel[hidden] { display: none !important; }

.gfx-panel {
  pointer-events: auto;
  display: flex; flex-direction: column;
  background: var(--glass);
  border: 1px solid var(--ink-4); border-radius: var(--r-pane);
  overflow: hidden; flex: 0 0 auto;
}
.gfx-panel.is-float {
  position: absolute; z-index: 20;
  backdrop-filter: blur(18px) saturate(1.25);
  box-shadow: 0 14px 44px rgb(0 0 0 / .5);
  resize: none;
}
.gfx-panel-h {
  display: flex; align-items: center; gap: 7px;
  padding: 7px 7px 7px 10px;
  background: var(--ink-5);
  cursor: grab; user-select: none; flex: 0 0 auto;
}
body.gfx-dragging .gfx-panel-h { cursor: grabbing; }
.gfx-panel-i { display: flex; color: var(--ink-3); }
.gfx-panel-t {
  flex: 1 1 auto; font-size: 10.5px; letter-spacing: .13em; font-weight: 600;
  text-transform: uppercase; color: var(--ink-2); white-space: nowrap; overflow: hidden;
}
.gfx-panel-b {
  background: none; border: 0; padding: 3px; border-radius: 5px;
  color: var(--ink-3); display: flex; cursor: default;
}
.gfx-panel-b:hover { background: var(--ink-5); color: var(--ink-1); }
.gfx-panel-body { overflow: auto; flex: 1 1 auto; min-height: 0; }
.gfx-panel.is-collapsed .gfx-panel-body, .gfx-panel.is-collapsed .gfx-panel-grip { display: none; }
.gfx-panel-grip {
  position: absolute; right: 0; bottom: 0; width: 15px; height: 15px;
  cursor: nwse-resize; display: none;
}
.gfx-panel.is-float .gfx-panel-grip { display: block; }
.gfx-panel.is-float .gfx-panel-grip::after {
  content: ''; position: absolute; right: 3px; bottom: 3px; width: 7px; height: 7px;
  border-right: 1.5px solid var(--ink-4); border-bottom: 1.5px solid var(--ink-4);
}

/* ── things a panel holds that Tweakpane does not do ── */
.gfx-panel-extra { padding: 0 8px 8px; }
.gfx-list { display: flex; flex-direction: column; gap: 2px; margin: 2px 0 8px; }
.gfx-item {
  display: flex; align-items: center; gap: 8px; width: 100%;
  background: none; border: 0; text-align: left;
  padding: 6px 8px; border-radius: var(--r-ctl); color: var(--ink-2); cursor: default;
}
.gfx-item:hover { background: var(--ink-5); }
.gfx-item.is-on { background: var(--ink-5); color: var(--ink-1); box-shadow: inset 2px 0 0 var(--acc); }
.gfx-item .i { color: var(--ink-3); display: flex; }
.gfx-item.is-on .i { color: var(--acc); }
.gfx-item .n { flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.gfx-item .c { color: var(--ink-3); font-size: 11px; font-family: var(--mono); }
.gfx-sub { padding-left: 16px; }

.gfx-btnrow { display: flex; gap: 6px; margin: 6px 0 2px; }
.gfx-btn {
  flex: 1 1 auto; display: flex; align-items: center; justify-content: center; gap: 6px;
  background: var(--ink-5); border: 1px solid var(--ink-4); border-radius: var(--r-ctl);
  padding: 7px 10px; color: var(--ink-2); cursor: default;
}
.gfx-btn:hover { border-color: var(--acc-2); color: var(--ink-1); }
.gfx-btn--go { background: var(--acc); border-color: var(--acc); color: #17130a; font-weight: 600; }
.gfx-btn--go:hover { filter: brightness(1.1); color: #17130a; }
.gfx-empty { color: var(--ink-3); font-size: 11.5px; line-height: 1.55; padding: 4px 2px 8px; }
.gfx-empty b { color: var(--ink-2); font-weight: 500; }

/* ══ SHEETS (New / Open / Save As) ═════════════════════════════════ */

.sheet {
  position: fixed; inset: 0; z-index: 900;
  background: rgb(4 4 3 / .55); backdrop-filter: blur(3px);
  display: grid; place-items: center;
}
.sheet[hidden] { display: none; }
.sheet-card {
  width: min(640px, 92vw); max-height: 82vh; overflow: auto;
  background: var(--pit); border: 1px solid var(--ink-4); border-radius: 14px;
  box-shadow: 0 30px 80px rgb(0 0 0 / .6); padding: 22px 24px 20px;
}
.sheet-card h2 { margin: 0 0 4px; font-size: 15px; font-weight: 600; color: var(--ink-1); letter-spacing: .01em; }
.sheet-card p.lede { margin: 0 0 18px; color: var(--ink-3); font-size: 12px; }
.sheet-f { display: flex; flex-direction: column; gap: 5px; margin-bottom: 14px; }
.sheet-f label { font-size: 11px; letter-spacing: .1em; text-transform: uppercase; color: var(--ink-3); }
.sheet-f input, .sheet-f select {
  background: var(--dark); border: 1px solid var(--ink-4); border-radius: var(--r-ctl);
  padding: 9px 11px; color: var(--ink-1); font: inherit; width: 100%;
}
.sheet-f input:focus, .sheet-f select:focus { outline: 0; border-color: var(--acc-2); }
.sheet-row { display: flex; gap: 8px; align-items: flex-end; }
.sheet-row .sheet-f { flex: 1 1 auto; margin-bottom: 0; }
.sheet-acts { display: flex; gap: 8px; justify-content: flex-end; margin-top: 20px; }
.sheet-err { color: var(--warn); font-size: 12px; min-height: 18px; margin-top: 8px; }

.proj-list { display: flex; flex-direction: column; gap: 4px; max-height: 44vh; overflow: auto; margin: 2px 0 6px; }
.proj {
  display: flex; align-items: center; gap: 12px; width: 100%; text-align: left;
  background: none; border: 1px solid transparent; border-radius: var(--r-ctl);
  padding: 10px 12px; color: var(--ink-2); cursor: default;
}
.proj:hover { background: var(--ink-5); border-color: var(--ink-4); }
.proj .nm { font-weight: 500; color: var(--ink-1); }
.proj .mt { color: var(--ink-3); font-size: 11px; }
.proj .rt { margin-left: auto; color: var(--ink-3); font-size: 11px; font-family: var(--mono); text-align: right; }

/* ══ TOAST ═════════════════════════════════════════════════════════ */

.toast {
  position: fixed; left: 50%; bottom: 26px; transform: translateX(-50%); z-index: 950;
  background: var(--glass); backdrop-filter: blur(16px);
  border: 1px solid var(--ink-4); border-radius: 22px;
  padding: 9px 18px; color: var(--ink-1); font-size: 12.5px;
  box-shadow: 0 10px 32px rgb(0 0 0 / .5);
}
.toast[hidden] { display: none; }
.toast.is-bad { border-color: var(--warn); color: var(--warn); }

/* ══ TWEAKPANE, wearing the Glass look ═════════════════════════════
   Tweakpane theming is entirely CSS variables, so this is a re-skin, not
   a fork — the library stays exactly as it was vendored. */

.gfx-panel-tp { padding: 8px 8px 2px; }
.gfx-panel-tp .tp-dfwv { width: 100%; min-width: 0; }
.gfx-panel-tp {
  --tp-base-background-color: transparent;
  --tp-base-shadow-color: transparent;
  --tp-button-background-color: rgb(242 238 230 / .10);
  --tp-button-background-color-hover: rgb(242 238 230 / .18);
  --tp-button-background-color-active: rgb(201 162 39 / .5);
  --tp-button-background-color-focus: rgb(242 238 230 / .18);
  --tp-button-foreground-color: #f2eee6;
  --tp-container-background-color: rgb(242 238 230 / .05);
  --tp-container-background-color-hover: rgb(242 238 230 / .10);
  --tp-container-background-color-active: rgb(201 162 39 / .35);
  --tp-container-background-color-focus: rgb(242 238 230 / .10);
  --tp-container-foreground-color: rgb(242 238 230 / .72);
  --tp-groove-foreground-color: rgb(242 238 230 / .10);
  --tp-input-background-color: rgb(4 4 3 / .6);
  --tp-input-background-color-hover: rgb(4 4 3 / .8);
  --tp-input-background-color-active: rgb(4 4 3 / .9);
  --tp-input-background-color-focus: rgb(4 4 3 / .9);
  --tp-input-foreground-color: #f2eee6;
  --tp-label-foreground-color: rgb(242 238 230 / .45);
  --tp-monitor-background-color: rgb(4 4 3 / .6);
  --tp-monitor-foreground-color: rgb(242 238 230 / .72);
  --tp-blade-value-width: 62%;
}

/* ══ TOOLTIPS ═══════════════════════════════════════════════════════
   A control whose name you have to guess at is a control you do not
   use. These say what a thing is FOR — never a repeat of its label —
   in one sentence, with the unit where there is one. */
.gfx-tip {
  position: fixed; z-index: 9000; pointer-events: none;
  max-width: 260px; padding: 7px 9px;
  background: var(--pit); color: var(--ink-1);
  border: 1px solid var(--ink-5); border-radius: var(--r-ctl);
  box-shadow: 0 6px 20px rgba(0, 0, 0, .45);
  font-size: 11.5px; line-height: 1.45;
  opacity: 0; transform: translateY(-2px);
  transition: opacity .12s ease, transform .12s ease;
}
.gfx-tip.is-up { opacity: 1; transform: none; }
.gfx-tip-t { display: block; }
/* the shortcut sits under the sentence, quiet — it is a reminder, not
   the message */
.gfx-tip-k {
  display: block; margin-top: 4px;
  color: var(--ink-3); font-size: 10.5px; letter-spacing: .04em;
}

/* ══ EACH PANEL IS ITS OWN BLOCK ════════════════════════════════════
   Three panels down a rail used to read as one long list: the border
   was ink at 20% over a dark glass — technically a line, visually
   nothing — and the 8px rail gap was the same colour as everything
   else, so a heading was the only thing saying "a new panel starts
   here". A heading is not a boundary.

   So: a border you can actually see, a shadow that lifts the panel off
   the rail behind it, and a titlebar that sits on its own darker band
   with a rule under it. */
.gfx-rail { gap: 12px; }

.gfx-panel {
  border-color: var(--ink-3);
  box-shadow: 0 2px 10px rgb(0 0 0 / .45);
}
/* the panel you are working in says so */
.gfx-panel:focus-within { border-color: var(--acc-2); }

.gfx-panel-h {
  background: rgb(0 0 0 / .32);
  border-bottom: 1px solid var(--ink-4);
}
.gfx-panel-t { letter-spacing: .1em; }

/* ── the way in, at the top of every panel ──────────────────────── */
.gfx-btnrow--top {
  margin: 0 0 8px;
  padding-bottom: 8px;
  border-bottom: 1px solid var(--ink-5);
}
.gfx-btnrow--top .gfx-btn { width: 100%; justify-content: center; }

/* ── renaming a row where it stands ─────────────────────────────── */
.gfx-item .nm { font-weight: inherit; }
.gfx-item .nm.is-renaming {
  outline: 1.5px solid var(--acc);
  outline-offset: 2px; border-radius: 3px;
  background: var(--dark);
  cursor: text; user-select: text; -webkit-user-select: text;
}
body[data-tab] .gfx-item .nm { cursor: default; }

/* ══ THE LOADING SCREEN ════════════════════════════════════════════
   Inside #stage, so it covers the picture and nothing else — the dock
   is a separate stacking context and stays reachable, which is what
   lets the author tune this while it is up.

   IT IS NOT display:none WHEN IDLE. `hidden` is honoured as opacity 0
   plus pointer-events:none, because the fade out IS the reveal of the
   scene and a display flip cannot be animated. Every colour is a token;
   the heading's face follows the project. */
.gfx-load {
  position: absolute; inset: 0; z-index: 600;
  display: grid; place-items: center;
  background: var(--dark);
  opacity: 1; transition: opacity var(--load-fade, 1.2s) ease;
  overflow: hidden;
}
.gfx-load[hidden] { display: grid; opacity: 0; pointer-events: none; }

/* kind:cloud — do not hide the picture. The scan's own points ARE the
   loading screen, so the backdrop is a vignette, not a wall. */
.gfx-load.is-cloud { background: radial-gradient(ellipse at center, rgb(11 10 9 / 0) 30%, rgb(11 10 9 / .92) 100%); }

.gfx-load__vid {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; opacity: .55;
}
.gfx-load__vid[hidden] { display: none; }

.gfx-load__mid {
  position: relative; z-index: 1;
  display: grid; justify-items: center; gap: 10px;
  padding: 0 32px; max-width: 620px; text-align: center;
}
.gfx-load__head {
  font-size: 26px; font-weight: 300; letter-spacing: .08em;
  color: var(--ink-1); text-transform: uppercase;
}
.gfx-load__head:empty { display: none; }
.gfx-load__sub { font-size: 12px; letter-spacing: .18em; color: var(--ink-3); text-transform: uppercase; }
.gfx-load__sub:empty { display: none; }

.gfx-load__bar {
  width: 240px; height: 2px; margin-top: 8px;
  background: var(--ink-5); border-radius: 2px; overflow: hidden;
}
.gfx-load__bar[hidden] { display: none; }
.gfx-load__bar > i {
  display: block; height: 100%; width: 0%;
  background: var(--acc); transition: width .18s linear;
}

/* THE AUTHOR IS WATCHING, NOT A VISITOR. A pinned preview says so, or
   the next person to open the project thinks it has hung. */
.gfx-load.is-preview::after {
  content: 'PREVIEW — the visitor does not see this badge';
  position: absolute; left: 50%; bottom: 18px; transform: translateX(-50%);
  font-size: 10px; letter-spacing: .16em; color: var(--acc);
  border: 1px solid var(--acc-2); border-radius: var(--r-ctl);
  padding: 4px 10px; white-space: nowrap;
}

/* world-level instructions, under the scene's own info */
.nui-info-how {
  margin-top: 12px; padding-top: 10px;
  border-top: 1px solid var(--ink-5);
  color: var(--ink-3); font-size: 12px;
}

/* the one multi-line field: prose, with room to see it */
.gfx-longtext {
  display: block; width: 100%; margin-top: 4px;
  min-height: 52px; resize: vertical;
  background: var(--pit); color: var(--ink-1);
  border: 1px solid var(--ink-5); border-radius: var(--r-ctl);
  padding: 5px 7px; font: 11px/1.45 var(--font);
}
.gfx-longtext:focus { outline: none; border-color: var(--acc-2); }

/* both ways into a project, side by side above everything */
.gfx-waysin { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; }
.gfx-waysin .gfx-btn { width: 100%; justify-content: center; }

/* ══ MOTION — WHAT MAKES IT BREATHE ═════════════════════════════════
   Object motion is CSS and nothing else. Each preset in DOC.MOTIONS
   names one of these classes; the renderer sets the class and one
   custom property, `--m-secs`, for the period. No JavaScript runs per
   frame, the compositor owns it, and nothing here can touch the
   document.

   EVERY ONE OF THESE IS DELIBERATELY TOO SUBTLE. The brief is "alive on
   a subconscious level" — the moment a viewer notices the animation
   rather than the room, the effect has failed. If one looks too quiet
   in isolation, that is the correct amount; they are meant to be felt
   against a still scan, not judged on their own. */

/* a sweep of light travelling across the glass, clipped to the card */
.gfx-slide.gfx-m-shimmer, .gfx-slide.gfx-m-sheen { position: absolute; overflow: hidden; }
.gfx-slide.gfx-m-shimmer::after,
.gfx-slide.gfx-m-sheen::after {
  content: ''; position: absolute; inset: -40% -60%;
  pointer-events: none;
  background: linear-gradient(105deg,
    transparent 38%,
    rgb(255 255 255 / .10) 46%,
    rgb(255 255 255 / .22) 50%,
    rgb(255 255 255 / .10) 54%,
    transparent 62%);
  transform: translateX(-120%);
  animation: gfx-sweep var(--m-secs, 7.5s) linear infinite;
}
.gfx-slide.gfx-m-sheen::after { background: linear-gradient(100deg,
    transparent 42%, rgb(255 255 255 / .07) 49%, rgb(255 255 255 / .14) 50%,
    rgb(255 255 255 / .07) 51%, transparent 58%); }
@keyframes gfx-sweep {
  0%   { transform: translateX(-120%); }
  /* THE PAUSE IS THE POINT. A sweep that runs continuously reads as a
     loading spinner; one that crosses and then waits reads as light
     moving over glass as someone walks past. */
  55%, 100% { transform: translateX(120%); }
}

/* the edge, breathing — the stroke's own alpha, not the whole card's */
.gfx-slide.gfx-m-breathe { animation: gfx-breathe var(--m-secs, 5.5s) ease-in-out infinite; }
@keyframes gfx-breathe {
  0%, 100% { border-color: var(--slide-edge, rgb(242 238 230 / .22)); }
  50%      { border-color: var(--slide-edge-up, rgb(242 238 230 / .46)); }
}

/* a slow bloom around the edge */
.gfx-slide.gfx-m-glowpulse { animation: gfx-glowpulse var(--m-secs, 6.5s) ease-in-out infinite; }
@keyframes gfx-glowpulse {
  0%, 100% { box-shadow: var(--slide-shadow, none); }
  50%      { box-shadow: var(--slide-shadow, none), 0 0 26px rgb(255 255 255 / .13); }
}

/* the card rising and settling on its stem. TRANSLATE ONLY, and only in
   the card's OWN local Y — the renderer owns `transform` on .gfx-slide
   (it carries the projection matrix), so this animates a child-safe
   property instead: a few tenths of a percent of the card's height. */
.gfx-slide.gfx-m-float > * { animation: gfx-float var(--m-secs, 6.5s) ease-in-out infinite; }
@keyframes gfx-float {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(-1.5%); }
}

/* arriving: one pass, never a loop */
.gfx-slide.gfx-m-settle > * { animation: gfx-settle var(--m-secs, 1.6s) cubic-bezier(.16,.84,.34,1) 1 both; }
@keyframes gfx-settle {
  from { opacity: 0; transform: translateY(6%); }
  to   { opacity: 1; transform: translateY(0); }
}

/* ── THE ORB'S OWN RHYTHM ──────────────────────────────────────────
   A different element and usually a different tempo: a slow card under
   a quicker orb is the pairing that reads as alive. The orb's own
   transform is set per frame by the renderer (it is screen-positioned),
   so these animate filter and opacity, which the renderer never writes. */
.gfx-orb.gfx-m-pulse { animation: gfx-pulse var(--m-secs, 3.2s) ease-in-out infinite; }
@keyframes gfx-pulse {
  0%, 100% { filter: brightness(1)    drop-shadow(0 0 3px currentColor); }
  50%      { filter: brightness(1.35) drop-shadow(0 0 9px currentColor); }
}
.gfx-orb.gfx-m-halo { animation: gfx-halo var(--m-secs, 4.6s) ease-in-out infinite; }
@keyframes gfx-halo {
  0%, 100% { box-shadow: 0 0 0 0 rgb(255 255 255 / .28); }
  70%      { box-shadow: 0 0 0 9px rgb(255 255 255 / 0); }
  100%     { box-shadow: 0 0 0 0 rgb(255 255 255 / 0); }
}
.gfx-orb.gfx-m-blink { animation: gfx-blink var(--m-secs, 2.4s) ease-in-out infinite; }
@keyframes gfx-blink {
  0%, 62%, 100% { opacity: 1; }
  78%           { opacity: .45; }
}

/* ── SOMEONE WHO ASKED FOR LESS GETS LESS ─────────────────────────
   This is the whole reason object motion is CSS: honouring the setting
   costs one rule rather than a flag threaded through a renderer. The
   `settle` arrival keeps working, because a fade-in is not the kind of
   movement that makes anybody ill — it just stops moving. */
@media (prefers-reduced-motion: reduce) {
  .gfx-slide[class*="gfx-m-"],
  .gfx-slide[class*="gfx-m-"] > *,
  .gfx-slide[class*="gfx-m-"]::after,
  .gfx-orb[class*="gfx-m-"] { animation: none !important; }
  .gfx-slide.gfx-m-settle > * { animation: gfx-fadein .35s ease 1 both !important; }
}
@keyframes gfx-fadein { from { opacity: 0; } to { opacity: 1; } }

/* ══ OVERLAYS — FLAT PICTURES IN THE ROOM ══════════════════════════
   A floor plan, an elevation, a site diagram. Positioned by the same
   projected matrix the cards use, so it sits in the room and
   foreshortens honestly rather than being pasted on the screen.

   `pointer-events: none` is the important one. A plan lies over the
   floor and under the hotspots, and it is large — if it took clicks it
   would swallow every attempt to orbit the room or pick a hotspot
   standing on it, which is most of what an author does with one. */
.gfx-overlay {
  position: absolute; left: 0; top: 0;
  transform-origin: 0 0;
  pointer-events: none;
  will-change: transform;
  line-height: 0;
}
.gfx-overlay img {
  width: 100%; height: auto; display: block;
  /* a plan is line art: keep the lines crisp rather than smoothed into
     grey when it is scaled up close */
  image-rendering: auto;
}
.gfx-overlay.is-on { outline: 2px solid var(--acc); outline-offset: 4px; }
