*, *::before, *::after {
  margin: 0; padding: 0; box-sizing: border-box;
  -webkit-user-select: none; user-select: none;
  -webkit-tap-highlight-color: transparent;
}

html, body {
  width: 100%; height: 100%; overflow: hidden;
  font-family: var(--text);
  -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
  background: var(--shell-bg); color: var(--fg);
  transition: background .45s;
}

:root {
  /* Section typography — every section's own style.css repoints these families;
     the values here are the engine's defaults. */
  --display: 'Inter', -apple-system, BlinkMacSystemFont, system-ui, sans-serif;
  /* Pinned so the browser never fakes a bolder cut than the section ships. */
  --display-weight: 700;
  --text: 'Inter', -apple-system, BlinkMacSystemFont, system-ui, sans-serif;
  --mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;

  /* App shell geometry */
  --shell-gap: 16px;      /* gutter between the window and the shell edges */
  --header-h: 74px;       /* header row: also the window's top inset */
  --window-radius: 16px;
  --island-gap: 12px;     /* inset of the open sidebar island from the edges */
  /* The shell sits a touch under the sidebar so the panel still reads as raised. */
  --shell-bg: color-mix(in srgb, var(--menu-bg) 78%, #05050a);

  --scene-bg: #0a0a14;
  --scene-accent: #6366f1;
  --menu-bg: #0a0a14;
  --menu-orb: #6366f1;
  --menu-orb-fg: #fff;
  --fg: #f4f2ff;
  --fg-a05: rgba(244, 242, 255, 0.05);
  --fg-a08: rgba(244, 242, 255, 0.08);
  --fg-a12: rgba(244, 242, 255, 0.12);
  --fg-a25: rgba(244, 242, 255, 0.25);
  --fg-a40: rgba(244, 242, 255, 0.4);
  --fg-a55: rgba(244, 242, 255, 0.55);
  --fg-a75: rgba(244, 242, 255, 0.75);
  --surface: color-mix(in srgb, var(--scene-bg) 30%, rgba(10, 10, 20, 0.85));
  --surface-solid: color-mix(in srgb, var(--scene-bg) 20%, #111218);
  --sidebar-w: 296px;
  --peek: 10px;
  --dock-h: 118px;
  --transition-fast: .18s;
  --transition-med: .28s cubic-bezier(.2, .8, .2, 1);
}

:root[data-mode="light"] {
  --shell-bg: color-mix(in srgb, var(--menu-bg) 74%, #ffffff);
  --fg: #15151a;
  --fg-a05: rgba(21, 21, 26, 0.05);
  --fg-a08: rgba(21, 21, 26, 0.08);
  --fg-a12: rgba(21, 21, 26, 0.12);
  --fg-a25: rgba(21, 21, 26, 0.25);
  --fg-a40: rgba(21, 21, 26, 0.4);
  --fg-a55: rgba(21, 21, 26, 0.55);
  --fg-a75: rgba(21, 21, 26, 0.75);
  --surface: color-mix(in srgb, var(--scene-bg) 25%, rgba(252, 252, 250, 0.82));
  --surface-solid: color-mix(in srgb, var(--scene-bg) 15%, #fefefe);
  color: var(--fg);
}

.scroll::-webkit-scrollbar { width: 6px; height: 6px; }
.scroll::-webkit-scrollbar-track { background: transparent; }
.scroll::-webkit-scrollbar-thumb { background: var(--fg-a12); border-radius: 3px; }
.scroll::-webkit-scrollbar-thumb:hover { background: var(--fg-a25); }

button { font-family: inherit; color: inherit; border: none; background: none; cursor: pointer; }
button:focus-visible { outline: 2px solid currentColor; outline-offset: 2px; }
iframe { color-scheme: normal; background-color: inherit; border: none; }

/* ===== APP HEADER =====
   The shell's title row: identity on the left, every action on the right.
   It stops at the sidebar's edge, so pinning the list narrows the header and
   the window together and the two right edges stay flush. */
.app-header {
  position: fixed;
  top: 0; left: 0;
  right: var(--stage-right, 0px);
  height: var(--header-h);
  display: flex; align-items: center; gap: 14px;
  padding: 0 var(--shell-gap);
  z-index: 30;
  color: var(--fg);
  /* The row is transparent and reaches over the sidebar's peek strip, so only
     its real contents may take the pointer — otherwise the empty middle of the
     header swallows the hover and the clicks meant for the diamond handle. */
  pointer-events: none;
  transition: right var(--transition-med), height var(--transition-med), opacity .25s;
}
.app-header > * { pointer-events: auto; }

/* A hover-opened sidebar is raised above the header so the action cluster does
   not paint over its own head row — which would then bury a dropdown opened
   from that same cluster. While one is open the header takes the top layer back. */
.app-header:has(.lang-selector.open),
.app-header:has(.scene-help.open) { z-index: 40; }

.header-actions {
  margin-left: auto;
  flex: 0 0 auto;
  display: flex; align-items: center; gap: 6px;
}

/* ===== STAGE — the inset window =====
   The experiment is not full-bleed any more: it is a separate rounded surface
   sitting inside our chrome, which is what makes the whole thing read as one
   application with an app loaded into it. */
.stage {
  position: fixed;
  top: var(--header-h);
  left:   calc(var(--stage-left,   0px) + var(--shell-gap));
  right:  calc(var(--stage-right,  0px) + var(--shell-gap));
  bottom: calc(var(--stage-bottom, 0px) + var(--shell-gap));
  padding: 0;
  background: var(--scene-bg);
  border: 1px solid var(--fg-a05);
  border-radius: var(--window-radius);
  overflow: hidden;
  isolation: isolate;
  box-shadow: 0 30px 70px -30px rgba(0,0,0,.7), 0 2px 12px -6px rgba(0,0,0,.45);
  transition:
    top var(--transition-med), left var(--transition-med),
    right var(--transition-med), bottom var(--transition-med),
    border-radius var(--transition-med), border-width var(--transition-med),
    border-color var(--transition-med), box-shadow var(--transition-med),
    background .6s;
}

.stage-slot {
  position: absolute; inset: 0;
  border-radius: inherit; overflow: hidden;
  opacity: 0;
  transform: scale(1.04);
  filter: blur(12px);
  transition:
    opacity 450ms cubic-bezier(.4, 0, .2, 1),
    transform 600ms cubic-bezier(.4, 0, .2, 1),
    filter 450ms cubic-bezier(.4, 0, .2, 1);
  pointer-events: none;
}
.stage-slot.visible {
  opacity: 1; transform: scale(1); filter: blur(0);
  pointer-events: auto;
}
.stage-slot iframe {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  background: var(--scene-bg);
}

.stage-flash {
  position: absolute; inset: 0;
  background: var(--scene-bg);
  opacity: 0; pointer-events: none;
  transition: opacity 180ms ease-out;
  z-index: 5;
}
.stage-flash.active { opacity: 0.35; }

/* ===== STAGE LOADER =====
   Engine-built overlay shown while a scene loads with preloading off; it sits
   between the slots and the flash layer and fades once the crossfade starts. */
.stage-loader {
  position: absolute; inset: 0;
  z-index: 3;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 18px;
  background: var(--scene-bg);
  opacity: 0;
  pointer-events: none;
  transition: opacity .3s ease;
}
.stage-loader.show { opacity: 1; }
.stage-loader-spin { position: relative; width: 42px; height: 42px; }
.stage-loader-halo {
  position: absolute; inset: -16px;
  border-radius: 50%;
  background: radial-gradient(circle, var(--scene-accent) 0%, transparent 68%);
  opacity: .2;
  filter: blur(8px);
  animation: stage-loader-halo 1.8s ease-in-out infinite;
}
.stage-loader-ring {
  position: absolute; inset: 0;
  border-radius: 50%;
  border: 2px solid var(--fg-a12);
  border-top-color: var(--scene-accent);
  animation: stage-loader-spin .9s linear infinite;
}
.stage-loader-name {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--fg-a55);
}
@keyframes stage-loader-spin { to { transform: rotate(360deg); } }
@keyframes stage-loader-halo {
  0%, 100% { opacity: .12; transform: scale(.92); }
  50%      { opacity: .3;  transform: scale(1.08); }
}
@keyframes stage-loader-pulse {
  0%, 100% { opacity: .35; }
  50%      { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  /* No spin — a gentle opacity pulse carries the "still working" signal. */
  .stage-loader-ring { animation: stage-loader-pulse 1.6s ease-in-out infinite; }
  .stage-loader-halo { animation: none; }
}

#spec-frame {
  position: fixed; width: 0; height: 0;
  top: -9999px; left: -9999px; pointer-events: none;
}

/* ===== SCENE LABEL =====
   A header child, not an overlay: it sits on the shell, so it simply follows
   --fg like the rest of the chrome. */
.scene-label {
  position: relative; top: auto; left: auto; z-index: auto;
  flex: 1 1 auto; min-width: 0;
  display: flex; align-items: center; gap: 12px;
  padding: 0;
  color: var(--fg);
  text-shadow: none;
  transition: opacity .25s;
}

/* ===== BACK TO THE LAB =====
   Sits immediately left of the current scene's icon and copies its size and shape,
   so the two read as one control: "this scene · leave it". The label unfurls on
   hover (0fr → 1fr) instead of permanently stealing width from the scene title —
   and because the column is content-sized, it fits every one of the 12 locales. */
.lab-home {
  flex: 0 0 auto;
  height: 36px;
  border-radius: 10px;
  background: var(--fg-a05);
  border: 1px solid var(--fg-a05);
  color: var(--fg-a75);
  text-decoration: none;
  overflow: hidden;
  display: inline-grid;
  grid-template-columns: 36px minmax(0, 0fr);
  align-items: center;
  transition: grid-template-columns var(--transition-med),
    background var(--transition-fast),
    color var(--transition-fast), border-color var(--transition-fast);
}
.lab-home > svg { grid-column: 1; justify-self: center; }
.lab-home-label {
  grid-column: 2;
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  padding-right: 12px;
  font-size: 12px; font-weight: 600; letter-spacing: .02em;
}
.lab-home:hover,
.lab-home:focus-visible {
  grid-template-columns: 36px minmax(0, 1fr);
  background: var(--fg-a12);
  color: var(--fg);
  border-color: var(--fg-a25);
}
@media (prefers-reduced-motion: reduce) {
  .lab-home { transition: color var(--transition-fast); }
}

.scene-label-icon {
  flex: 0 0 auto;
  width: 36px; height: 36px; border-radius: 10px;
  background: var(--surface);
  backdrop-filter: blur(20px) saturate(1.2);
  -webkit-backdrop-filter: blur(20px) saturate(1.2);
  border: 1px solid var(--fg-a05);
  display: grid; place-items: center;
  font-size: 17px; overflow: hidden;
  position: relative;
}
.scene-label-icon .ico-slot {
  position: absolute; inset: 0;
  display: grid; place-items: center;
  opacity: 0; transform: scale(0.82); filter: blur(4px);
  transition:
    opacity .45s cubic-bezier(.4,0,.2,1),
    transform .5s cubic-bezier(.4,0,.2,1),
    filter .45s cubic-bezier(.4,0,.2,1);
  pointer-events: none;
}
.scene-label-icon .ico-slot.visible { opacity: 1; transform: scale(1); filter: blur(0); }
.scene-label-icon .ico-slot img { width: 100%; height: 100%; object-fit: cover; }

/* The slots inside are absolutely positioned, so the column has no intrinsic
   width of its own — it takes the header's leftover room instead. */
.scene-label-text {
  flex: 1 1 auto; min-width: 0;
  display: flex; flex-direction: column; gap: 1px;
}

.scene-label-name,
.scene-label-meta {
  overflow: hidden; position: relative; white-space: nowrap;
}
.scene-label-name { height: 22px; }
.scene-label-meta { height: 16px; }

/* left+right rather than shrink-to-fit: the slot has to inherit the column's
   width, otherwise it sizes itself to the text, never overflows, and the
   ellipsis never fires — the parent just hard-clips the name mid-letter. */
.label-slot {
  position: absolute; left: 0; right: 0; top: 0;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  opacity: 0;
  transform: translateY(-25%);
  filter: blur(4px);
  transition:
    opacity .45s cubic-bezier(.4,0,.2,1),
    transform .45s cubic-bezier(.4,0,.2,1),
    filter .45s cubic-bezier(.4,0,.2,1);
  pointer-events: none;
}
.label-slot.visible {
  opacity: 1;
  transform: translateY(0);
  filter: blur(0);
}
.label-slot.leaving {
  opacity: 0;
  transform: translateY(25%);
  filter: blur(4px);
}

/* Capped so a long title can never push the action cluster off the header. */
.scene-label-name .label-slot {
  max-width: 34vw;
  font-size: 14px;
  color: var(--fg); letter-spacing: -.01em;
}
.scene-label-meta .label-slot {
  font-size: 11px; font-family: var(--mono);
  color: var(--fg-a55); letter-spacing: .04em;
}

/* ===== NAV ARROWS =====
   A slim tab held against each edge of the stage, not a disc floating over the
   middle of it: the labs put their own panels and handles near the edges, and a
   52px circle sitting 14px inside the frame covered one often enough to be a
   nuisance. The outer side is square — it meets the frame — and only the side
   facing the lab is rounded. */
.nav-arrow {
  position: fixed;
  /* Centred inside the window: the header inset above, the dock / gutter below.
     The duplicated declaration is a dvh progressive enhancement. */
  top: calc((var(--header-h) + 100vh  - var(--stage-bottom, 0px) - var(--shell-gap)) / 2);
  top: calc((var(--header-h) + 100dvh - var(--stage-bottom, 0px) - var(--shell-gap)) / 2);
  transform: translateY(-50%);
  width: 26px; height: 36px;
  background: var(--surface);
  backdrop-filter: blur(20px) saturate(1.2);
  -webkit-backdrop-filter: blur(20px) saturate(1.2);
  border: 1px solid var(--fg-a05);
  color: var(--fg);
  /* Not a grid: the glyph is pinned below rather than centred by the box, because
     the box grows on hover and anything centred in it would be carried along. */
  z-index: 20; display: block;
  transition: background var(--transition-fast), border-color var(--transition-fast),
              color var(--transition-fast), opacity var(--transition-fast),
              height var(--transition-med),
              top var(--transition-med), left var(--transition-med), right var(--transition-med);
}
/* Short at rest, and it grows into the stage on hover rather than moving: the tab
   is held against the frame edge and cannot go anywhere. */
.nav-arrow:hover:not(:disabled) { height: 52px; }
.nav-arrow svg {
  /* Pinned to the line the tab hangs on, not centred inside it. top: 50% of the
     height and the tab's own translateY(-50%) cancel exactly — the glyph sits at
     calcTop - 8px whatever the height is — so growing the tab cannot shift it by
     even a rounded pixel. Centring it in the box would have it ride the growth. */
  position: absolute;
  top: 50%; left: 50%;
  width: 16px; height: 16px;
  /* Off centre, toward the edge its tab is held against: the glyph reads as
     pointing out of the frame rather than sitting in a box, and the room it
     leaves behind it is the room the shaft grows into. */
  transform: translate(-50%, -50%) translateX(var(--nav-shift, 0px));
}
/* The chevron slides out on hover; the shaft does not slide anywhere, it is drawn
   where it belongs. Which is why the move is on the polyline and not on the svg:
   moving the svg would carry the shaft with it, and a tail that arrives sideways
   reads as the whole glyph being nudged rather than as an arrow being completed.
   Lengths inside the svg are user units, so 3px here is 3 of the 24 across. */
.nav-arrow polyline { transition: transform var(--transition-med); }
.nav-arrow:hover:not(:disabled) polyline { transform: translateX(var(--chev-out, 0px)); }
/* The shaft that turns the chevron into an arrow on hover: a fixed-length line,
   hidden by its own dash. dashoffset animates in every browser; the geometry
   attributes the line would otherwise have to move (x1/x2) do not. */
.nav-arrow .nav-shaft {
  stroke-dasharray: 10;
  stroke-dashoffset: 10;
  transition: stroke-dashoffset var(--transition-med);
}
/* Hover paints the glyph and slides it out; the tab itself does not move and
   does not fill. It is part of the frame — flooding it with the accent read as
   the frame flashing, and the tab growing dragged the eye to the wrong thing. */
.nav-arrow:hover:not(:disabled) { color: var(--scene-accent); }
.nav-arrow:hover:not(:disabled) .nav-shaft { stroke-dashoffset: 0; }
.nav-arrow:disabled { opacity: 0.25; pointer-events: none; }

.nav-left {
  left: calc(var(--stage-left, 0px) + var(--shell-gap));
  border-left: 0;
  border-radius: 0 12px 12px 0;
  --nav-shift: -2px;
  --chev-out: -3px;
}
.nav-right {
  right: calc(var(--stage-right, 0px) + var(--shell-gap));
  border-right: 0;
  border-radius: 12px 0 0 12px;
  --nav-shift: 2px;
  --chev-out: 3px;
}

/* Nav hints tooltip */
.nav-tip {
  position: absolute;
  display: flex; flex-direction: column; align-items: center; gap: 3px;
  padding: 7px 10px; border-radius: 8px;
  background: rgba(20, 22, 30, 0.92);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  border: 1px solid var(--fg-a12);
  color: var(--fg); font-size: 12px; font-weight: 500;
  white-space: nowrap; pointer-events: none;
  opacity: 0; transition: opacity .2s;
  z-index: 1;
}
/* Pinned like the glyph, for the same reason. */
.nav-tip { top: 50%; transform: translateY(-50%); }
.nav-left  .nav-tip { left: calc(100% + 10px); }
.nav-right .nav-tip { right: calc(100% + 10px); }
.nav-tip kbd {
  font-family: var(--mono);
  font-size: 10px; color: var(--fg-a55);
  letter-spacing: .04em;
}
:root:not(.nav-hints-dismissed) .nav-arrow:hover .nav-tip { opacity: 1; }

/* ===== HEADER BUTTONS =====
   Tips, open-in-new-tab and every action share one control: same box, same
   surface, same hover — the header reads as a single row of switches. */
.action-btn,
.scene-help-btn,
.scene-open-btn {
  flex: 0 0 auto;
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  width: 36px; height: 36px; padding: 0;
  border-radius: 10px;
  border: 1px solid var(--fg-a05);
  background: var(--fg-a05);
  color: var(--fg);
  font-family: var(--mono);
  font-size: 11px; letter-spacing: .1em;
  box-shadow: none; opacity: 1;
  transition: background .2s, border-color .2s, color .2s;
}
.action-btn:hover,
.scene-help-btn:hover,
.scene-open-btn:hover { background: var(--fg-a12); }
.action-pill { width: auto; padding: 0 10px; }

.action-bar { display: flex; align-items: center; gap: 6px; color: var(--fg); }
.scene-help    { position: relative; }
.scene-open-btn{ position: relative; }
.scene-help[hidden], .scene-open-btn[hidden] { display: none; }
.scene-help.open .scene-help-btn {
  background: color-mix(in srgb, var(--menu-orb) 26%, var(--fg-a08));
  border-color: var(--fg-a25);
}

/* Lang selector */
.lang-selector { position: relative; }
.lang-dropdown {
  position: absolute;
  top: calc(100% + 10px); right: 0; left: auto; bottom: auto;
  width: 240px; max-height: min(420px, 60vh); overflow-y: auto;
  display: flex; flex-direction: column; gap: 1px;
  background: var(--surface-solid);
  border: 1px solid var(--fg-a12);
  border-radius: 14px;
  padding: 6px;
  box-shadow: 0 24px 60px -18px rgba(0,0,0,.55);
  transform-origin: top right;
  opacity: 0; visibility: hidden; transform: translateY(-6px) scale(.98);
  z-index: 40;
  scrollbar-width: thin;
  transition: opacity var(--transition-fast), visibility var(--transition-fast), transform var(--transition-fast);
}
.lang-selector.open .lang-dropdown { opacity: 1; visibility: visible; transform: translateY(0) scale(1); }
.lang-option {
  display: flex; align-items: center; gap: 8px;
  padding: 7px 10px; border-radius: 8px;
  font-size: 13px; color: var(--fg-a75);
  transition: background var(--transition-fast), color var(--transition-fast);
}
.lang-option:hover { background: var(--fg-a08); color: var(--fg); }
.lang-option.active { color: var(--fg); font-weight: 600; }
.lang-option.active::after {
  content: '✓';
  margin-left: auto;
  color: var(--scene-accent);
  font-weight: 700;
}
.flag-icon { width: 18px; height: 14px; object-fit: cover; border-radius: 2px; }

/* ===== SIDEBAR ===== */
.sidebar {
  position: fixed; top: 0; right: 0; bottom: 0;
  width: var(--sidebar-w);
  z-index: 25;
  pointer-events: none;
}
/* Hovered open, the panel slides out across the header row, so it has to win the
   stacking order — otherwise the header's action cluster paints over the
   sidebar's own head and swallows the clicks meant for the pin button. Pinned,
   the header already stops at the panel's edge, and collapsed it must stay under
   the header so the top-right corner (the hide-UI toggle) keeps its clicks. */
.sidebar.open:not(.pinned) { z-index: 35; }
.sidebar-hover-zone,
.sidebar-handle,
.sidebar-panel { pointer-events: auto; }
.sidebar-hover-zone {
  position: absolute; top: 0; bottom: 0;
  right: 0;
  width: 28px;
  z-index: 0; /* stay under the panel/handle so it never intercepts their clicks */
  transition: width var(--transition-med);
}
.sidebar.open .sidebar-hover-zone { width: calc(var(--sidebar-w) + 22px); }

/* Reveal handle — a small diamond tab pinned to the peek strip, vertically
   centred on the header row. Fades out once the panel is open or pinned. */
.sidebar-handle {
  --size: 30px;
  position: absolute;
  top: calc((var(--header-h) - var(--size)) / 2);
  right: calc(var(--peek) - (var(--size) / 2));
  width: var(--size);
  height: var(--size);
  /* It is the panel's edge poking out, not a plaque of its own, so it takes the
     panel's fill and the same border the panel shows on its left side. */
  background: var(--menu-bg);
  border-bottom-left-radius: 4px;
  backdrop-filter: blur(20px) saturate(1.2);
  -webkit-backdrop-filter: blur(20px) saturate(1.2);
  border: 1px solid var(--fg-a08);
  border-right: none;
  color: var(--fg);
  display: grid; place-items: center;
  z-index: 2;
  opacity: 1;
  box-shadow: -4px 8px 20px -6px rgba(0,0,0,0.25);
  transform: rotate(45deg);
  transition:
    opacity .25s,
    top var(--transition-med),
    right var(--transition-med),
    transform var(--transition-med),
    background .35s;
}
.sidebar-handle > svg { transform: rotate(-45deg) translateX(-6px); }

/* On the way out the tab leaves rather than dissolves: it is a diamond rotated
   45deg, so translating along its own axes by one side length carries it out
   through the corner it is pinned to. Hovering the strip at the right edge
   counts as hovering the tab — that strip is what the pointer actually crosses
   on the way to the sidebar. */
.sidebar-handle:hover,
.sidebar-hover-zone:hover ~ .sidebar-handle,
.sidebar-hover-zone:hover + .sidebar-handle {
  opacity: 0;
  transform: rotate(45deg) translate(var(--size), calc(var(--size) * -1));
}

.sidebar.open .sidebar-handle {
  opacity: 0;
  pointer-events: none;
  transform: rotate(45deg) translate(var(--size), calc(var(--size) * -1));
}
.sidebar.pinned .sidebar-handle { display: none; }

.sidebar-panel {
  position: absolute; top: 0; right: calc(-1 * (var(--sidebar-w) - var(--peek))); bottom: 0;
  width: var(--sidebar-w);
  background: var(--menu-bg);
  backdrop-filter: blur(24px) saturate(1.2);
  -webkit-backdrop-filter: blur(24px) saturate(1.2);
  /* Collapsed only the left edge shows; the border exists on all four sides but
     transparent, so opening into the island animates just the color. */
  border: 1px solid transparent;
  border-left-color: var(--fg-a08);
  border-radius: 0;
  display: flex; flex-direction: column;
  gap: 0;
  overflow: hidden;
  isolation: isolate;
  transition: top var(--transition-med), bottom var(--transition-med),
              right var(--transition-med), border-radius var(--transition-med),
              border-color var(--transition-med), box-shadow var(--transition-med),
              background .35s, backdrop-filter .35s;
}
/* Open — hovered or pinned — the panel floats free of the viewport edges as a
   rounded island, matching the stage window. Collapsed it stays a full-height
   flush strip with square corners (the 10px peek). */
.sidebar.open .sidebar-panel {
  top: var(--island-gap); bottom: var(--island-gap); right: var(--island-gap);
  border-color: var(--fg-a05);
  border-radius: var(--window-radius);
  box-shadow: 0 30px 70px -30px rgba(0,0,0,.7);
}
.sidebar.open:not(.pinned) .sidebar-panel {
  backdrop-filter: blur(32px) saturate(1.5);
  -webkit-backdrop-filter: blur(32px) saturate(1.5);
}

/* Orb blobs */
.sidebar-panel::before,
.sidebar-panel::after {
  content: '';
  position: absolute;
  border-radius: 50%;
  filter: blur(60px);
  opacity: 0.45;
  pointer-events: none;
  z-index: -1;
  background: var(--menu-orb);
  transition: background .6s;
}
.sidebar-panel::before { width: 200px; height: 200px; top: -60px; right: -60px; }
.sidebar-panel::after  { width: 160px; height: 160px; bottom: 60px; left: -40px; opacity: 0.3; }

.sidebar-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 18px 16px 14px;
  flex-shrink: 0;
}
.sidebar-brand { display: flex; align-items: center; gap: 10px; min-width: 0; }

.sidebar-logo {
  width: 36px; height: 36px; border-radius: 9px;
  background: var(--menu-orb);
  color: var(--menu-orb-fg);
  display: grid; place-items: center;
  font-size: 16px; font-weight: 700;
  flex-shrink: 0; overflow: hidden;
  position: relative;
  transition: background .6s, color .3s;
}
.sidebar-logo .ico-slot {
  position: absolute; inset: 0;
  display: grid; place-items: center;
  opacity: 0; transform: scale(0.82); filter: blur(4px);
  transition:
    opacity .45s cubic-bezier(.4,0,.2,1),
    transform .5s cubic-bezier(.4,0,.2,1),
    filter .45s cubic-bezier(.4,0,.2,1);
  pointer-events: none;
}
.sidebar-logo .ico-slot.visible { opacity: 1; transform: scale(1); filter: blur(0); }
.sidebar-logo .ico-slot img { width: 100%; height: 100%; object-fit: cover; }
.sidebar-brand-text { min-width: 0; }
.sidebar-title {
  font-size: 14px;
  color: var(--fg); letter-spacing: -.01em;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.sidebar-subtitle {
  font-size: 11px; color: var(--fg-a55);
  white-space: nowrap; margin-top: 1px;
}

.sidebar-pin {
  width: 30px; height: 30px; border-radius: 7px;
  display: grid; place-items: center;
  color: var(--fg-a40); flex-shrink: 0;
  transition: background var(--transition-fast), color var(--transition-fast);
}
.sidebar-pin:hover { background: var(--fg-a08); color: var(--fg); }
.sidebar-pin .icon-collapse { display: none; }
.sidebar.pinned .sidebar-pin .icon-pin     { display: none; }
.sidebar.pinned .sidebar-pin .icon-collapse { display: inline; }

/* ===== TAG CLOUD ===== */
.tag-cloud-wrap {
  padding: 4px 14px 8px;
  flex-shrink: 0;
}

.tag-cloud {
  position: relative;
  display: flex; flex-wrap: wrap; gap: 5px;
  padding: 3px;
}

/* Sliding indicator for the "All" chip — an opaque pill the active label rides
   on top of. JS sets top/left/width/height to the active chip's own box, so it
   tracks a single chip even when the chips wrap onto a second row (otherwise it
   would span the full cloud height and bleed over the row below). */
.filter-indicator {
  position: absolute;
  top: 3px; height: 24px;
  border-radius: 7px;
  background: var(--menu-orb);
  opacity: 1;
  pointer-events: none;
  transition: left .2s cubic-bezier(.2,.8,.2,1), top .2s cubic-bezier(.2,.8,.2,1),
              width .2s cubic-bezier(.2,.8,.2,1), height .2s cubic-bezier(.2,.8,.2,1),
              opacity .2s, background .35s;
  z-index: 0;
}

.tag-chip {
  position: relative; z-index: 1;
  padding: 4px 9px;
  border-radius: 7px;
  font-size: 11px; font-weight: 500;
  background: var(--fg-a05);
  border: 1px solid var(--fg-a05);
  color: var(--fg-a75);
  transition: background var(--transition-fast), color var(--transition-fast),
              border-color var(--transition-fast);
  cursor: pointer;
}
.tag-chip:hover {
  background: var(--fg-a08);
  color: var(--fg);
}
.tag-chip.active {
  background: transparent;
  border-color: transparent;
  color: var(--menu-orb-fg);
  font-weight: 600;
}
/* The sliding pill only ever tracks the "all" chip (the filter is multi-select,
   one pill cannot cover several chips), so every other active tag paints the
   same pill itself — without it the label would sit on bare sidebar and turn
   unreadable on light scenes. */
.tag-chip.active:not([data-tag="all"]) {
  background: var(--menu-orb);
  border-color: var(--menu-orb);
}

/* ===== SIDEBAR CONTENT ===== */
.sidebar-meta {
  display: flex; justify-content: space-between; align-items: center;
  padding: 4px 16px 6px;
  font-size: 11px; color: var(--fg-a40);
  letter-spacing: .04em; text-transform: uppercase;
  flex-shrink: 0;
}
.sidebar-list {
  flex: 1;
  overflow-y: auto; overflow-x: hidden;
  padding: 4px 8px 8px;
  display: flex; flex-direction: column; gap: 2px;
}
.sidebar-footer {
  flex-shrink: 0;
  display: flex; align-items: center;
  padding: 10px 12px;
  border-top: 1px solid var(--fg-a05);
}
/* On mobile the action bar rides in the header — a footer left without it must
   not keep a bordered gap (:empty would miss the leftover whitespace nodes). */
.sidebar-footer:not(:has(.action-bar)) { display: none; }
.sidebar-footer .action-bar { display: flex; width: 100%; gap: 6px; }
.sidebar-footer .lang-selector { margin-left: auto; }
/* In the sidebar this is not a small floating card. It fills the band the scene
   list occupies and sits flush on the footer, edge to edge, so its bottom corners
   have nothing left to round and only the top two are cut. --lang-top and
   --lang-bottom are measured off the list rather than assumed: the tag row wraps,
   the meta row comes and goes, and the notch insets the whole panel. Measuring is
   what keeps twelve languages out of a scrollbar wherever the panel is tall
   enough to hold them.

   position: static on the selector is the point of the whole rule — it hands the
   containing block up to the panel, which is what lets the dropdown be as wide as
   the sidebar instead of as wide as the little pill it hangs off. */
.sidebar-footer .lang-selector { position: static; }
.sidebar-footer .lang-dropdown {
  top: auto;
  bottom: var(--lang-bottom, 56px);
  /* left/right against the containing block resolve to the panel width in
     both engines: whichever ancestor turns out to be positioned, its padding
     box spans the panel, because nothing between them has a side border. */
  left: 0; right: 0; width: auto;
  /* An absolute, not a percentage. The two engines disagree on which ancestor
     is the containing block here — the panel in the shared viewer, the footer
     in the legacy ones — and a percentage of the footer is 57px of room. */
  max-height: var(--lang-band, 60vh);
  padding: 8px;
  border-radius: 14px 14px 0 0;
  border-bottom: 0;
  background: color-mix(in srgb, var(--menu-bg) 74%, transparent);
  backdrop-filter: blur(20px) saturate(1.3);
  -webkit-backdrop-filter: blur(20px) saturate(1.3);
  box-shadow: 0 -22px 50px -20px rgba(0, 0, 0, .6);
  /* Revealed upward out of the footer: the clip starts closed against the bottom
     edge and opens towards the top, so the list grows rather than slides. */
  transform: none;
  clip-path: inset(100% 0 0 0);
  transition: opacity var(--transition-fast), visibility var(--transition-fast),
              clip-path var(--transition-med);
}
.sidebar-footer .lang-selector.open .lang-dropdown { clip-path: inset(0 0 0 0); }

/* Scene list items */
.scene-item {
  display: flex; align-items: center; gap: 14px;
  padding: 10px;
  border-radius: 12px;
  color: var(--fg-a75);
  width: 100%;
  text-align: left;
  transition: background var(--transition-fast), color var(--transition-fast);
}
.scene-item:hover { background: var(--fg-a05); color: var(--fg); }
.scene-item.active {
  background: color-mix(in srgb, var(--menu-orb) 14%, var(--fg-a05));
  color: var(--fg);
}

.scene-thumb {
  width: 82px; height: 54px;
  border-radius: 8px;
  background: var(--sc-bg, #0a0a14);
  outline: 1px solid var(--fg-a08);
  flex-shrink: 0; overflow: hidden;
  position: relative;
  transition: outline .2s;
}
.scene-item.active .scene-thumb {
  outline: 1.5px solid var(--fg-a55);
  outline-offset: 1px;
}
.scene-thumb img {
  width: 100%; height: 100%; object-fit: cover;
  display: block;
}
.scene-thumb-fallback {
  position: absolute; inset: 0;
  display: grid; place-items: center;
  font-size: 22px;
}
.scene-thumb-overlay {
  position: absolute; inset: 0;
  background: linear-gradient(180deg, transparent 40%, rgba(0,0,0,.35) 100%);
}
.scene-thumb.intro {
  background: linear-gradient(135deg, var(--sc-bg, #0a0a14), color-mix(in srgb, var(--sc-accent, #6366f1) 30%, var(--sc-bg, #0a0a14)));
}
.intro-label {
  position: absolute; inset: 0;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 2px; font-size: 10px;
  color: var(--fg-a55); letter-spacing: .1em; text-transform: uppercase;
}
.intro-mark { font-size: 18px; }

.scene-info { min-width: 0; flex: 1; }
.scene-name {
  font-size: 12px; font-weight: 600;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  color: inherit;
}
.scene-num {
  font-size: 10px;
  font-family: var(--mono);
  color: var(--fg-a40); letter-spacing: .04em;
  margin-top: 2px;
}

/* Hidden by filter */
.scene-item.hidden, .dock-item.hidden { display: none; }
.scene-item, .dock-item {
  animation: filter-in .28s cubic-bezier(.2,.8,.2,1) both;
}
@keyframes filter-in {
  from { opacity: 0; transform: translateY(5px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* ===== MOBILE DOCK ===== */
.mobile-dock {
  display: none;
  position: fixed; bottom: 0; left: 0; right: 0;
  height: var(--dock-h, 118px);
  z-index: 20;
  background: var(--surface);
  backdrop-filter: blur(20px) saturate(1.2);
  -webkit-backdrop-filter: blur(20px) saturate(1.2);
  border-top: 1px solid var(--fg-a08);
  padding: 10px 0 14px;
}

.mobile-dock-track {
  display: flex; align-items: flex-start; gap: 8px;
  overflow-x: auto; overflow-y: hidden;
  padding: 0 12px;
  height: 100%;
  scroll-snap-type: x mandatory;
}
.dock-item {
  display: flex; flex-direction: column; align-items: center; gap: 4px;
  flex-shrink: 0;
  scroll-snap-align: center;
  padding: 2px;
  border-radius: 10px;
  transition: background var(--transition-fast);
}
.dock-item:hover { background: var(--fg-a05); }
.dock-item.active { background: color-mix(in srgb, var(--menu-orb) 14%, var(--fg-a05)); }

.dock-thumb {
  width: 56px; height: 56px; border-radius: 10px;
  background: var(--sc-bg, #0a0a14);
  border: 1px solid var(--fg-a08);
  overflow: hidden; flex-shrink: 0;
  display: grid; place-items: center;
  font-size: 20px;
}
.dock-thumb.intro {
  background: linear-gradient(135deg, var(--sc-bg, #0a0a14), color-mix(in srgb, var(--sc-accent, #6366f1) 30%, var(--sc-bg, #0a0a14)));
}
.dock-thumb img { width: 100%; height: 100%; object-fit: cover; }
.dock-name {
  font-size: 9px; font-weight: 500; color: var(--fg-a55);
  max-width: 60px; text-align: center;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* ===== SHORTCUTS MODAL ===== */
.modal-backdrop {
  position: fixed; inset: 0;
  z-index: 60;
  background: rgba(0, 0, 0, 0.5);
  backdrop-filter: blur(8px);
  display: grid;
  place-items: center;
  opacity: 0;
  visibility: hidden;
  scale: 1.1;
  filter: blur(8px);
  transition: opacity .18s, visibility .18s, transform .25s, scale .25s, filter .25s;
}
.modal-backdrop.open {
  opacity: 1;
  visibility: visible;
  scale: 1;
  filter: blur(0);
}

.modal {
  background: var(--surface-solid);
  border: 1px solid var(--fg-a12);
  border-radius: 18px;
  padding: 24px;
  width: min(400px, calc(100vw - 32px));
  box-shadow: 0 24px 64px rgba(0,0,0,.5);
}
.modal-head {
  display: flex; justify-content: space-between; align-items: flex-start;
  margin-bottom: 20px;
}
.modal-eyebrow {
  font-size: 10px; letter-spacing: .15em;
  text-transform: uppercase; color: var(--fg-a40);
  margin-bottom: 4px;
}
.modal-title { font-size: 20px; color: var(--fg); }
.modal-close {
  width: 30px; height: 30px; border-radius: 8px;
  display: grid; place-items: center;
  color: var(--fg-a55);
  transition: background var(--transition-fast), color var(--transition-fast);
}
.modal-close:hover { background: var(--fg-a08); color: var(--fg); }

.shortcut-list { display: flex; flex-direction: column; gap: 6px; }
.shortcut-row {
  display: flex; justify-content: space-between; align-items: center;
  padding: 8px 10px; border-radius: 8px;
  background: var(--fg-a05);
  font-size: 13px; color: var(--fg-a75);
}
.shortcut-row kbd {
  font-family: var(--mono);
  font-size: 11px; color: var(--fg-a55);
  background: var(--fg-a08);
  border: 1px solid var(--fg-a12);
  border-radius: 5px;
  padding: 2px 7px;
}

/* ===== SCENE HELP PANEL =====
   Anchored under the tips button in the header, so it opens downward. */
.scene-help-panel {
  position: absolute;
  top: calc(100% + 10px); right: 0; left: auto; bottom: auto;
  width: 320px; max-width: calc(100vw - 2 * var(--shell-gap));
  background: var(--surface-solid);
  border: 1px solid var(--fg-a12);
  border-radius: 14px;
  padding: 14px 16px 16px;
  box-shadow: 0 24px 60px -18px rgba(0,0,0,.55);
  transform-origin: top right;
  transform: scale(.92); opacity: 0; pointer-events: none;
  display: block;
  z-index: 40;
  transition: opacity .2s cubic-bezier(.2,.8,.2,1), transform .22s cubic-bezier(.2,.8,.2,1);
}
.scene-help.open .scene-help-panel { opacity: 1; transform: scale(1); pointer-events: auto; }

.scene-help-head {
  display: flex; justify-content: space-between; align-items: center;
  margin-bottom: 10px;
}
.scene-help-title { font-size: 13px; color: var(--fg); }
.scene-help-close {
  width: 24px; height: 24px; border-radius: 6px;
  display: grid; place-items: center;
  color: var(--fg-a40);
  transition: background var(--transition-fast), color var(--transition-fast);
}
.scene-help-close:hover { background: var(--fg-a08); color: var(--fg); }

.scene-help-list {
  list-style: none; display: flex; flex-direction: column; gap: 6px;
}
.scene-help-list li {
  font-size: 12px; line-height: 1.55; color: var(--fg-a75);
  padding-left: 12px; position: relative;
}
.scene-help-list li::before {
  content: '→';
  position: absolute; left: 0;
  color: var(--scene-accent);
  font-size: 10px;
}

/* ===== TOAST ===== */
.toast {
  position: fixed;
  bottom: calc(var(--stage-bottom, 0px) + var(--shell-gap) + 18px);
  left: 50%;
  transform: translateX(-50%) translateY(20px);
  background: var(--surface-solid);
  border: 1px solid var(--fg-a12);
  border-radius: 12px;
  padding: 10px 16px;
  font-size: 13px; font-weight: 500;
  color: var(--fg);
  z-index: 100;
  opacity: 0; pointer-events: none;
  transition: opacity .22s, transform .22s cubic-bezier(.2,.8,.2,1), bottom var(--transition-med);
  box-shadow: 0 8px 24px rgba(0,0,0,.3);
  max-width: calc(100vw - 32px);
}
.toast.show {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
  pointer-events: auto;
}
.toast--copy {
  display: flex; align-items: flex-start; gap: 10px;
}
.toast-icon { flex-shrink: 0; margin-top: 1px; }
.toast-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.toast-url {
  font-size: 11px;
  font-family: var(--mono);
  color: var(--fg-a55);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  max-width: 240px;
}

/* ===== CUSTOM TOOLTIP =====
   One floating singleton fed from data-tip; replaces the native title bubbles
   on all chrome controls. Positioned by the engine with fixed coordinates. */
.ui-tip {
  position: fixed; left: 0; top: 0;
  max-width: min(280px, calc(100vw - 16px));
  padding: 6px 10px;
  border-radius: 8px;
  background: var(--surface-solid);
  border: 1px solid var(--fg-a08);
  color: var(--fg);
  font-size: 11px; font-weight: 500;
  box-shadow: 0 12px 32px -14px rgba(0,0,0,.5);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  pointer-events: none;
  z-index: 80;
  opacity: 0;
  transform: translateY(4px) scale(.97);
  transition: opacity .18s cubic-bezier(.2,.8,.2,1), transform .18s cubic-bezier(.2,.8,.2,1);
}
.ui-tip.above { transform: translateY(-4px) scale(.97); }
.ui-tip.show { opacity: 1; transform: translateY(0) scale(1); }

/* Scene credit in the help panel */
.scene-help-list li.scene-help-credit {
  margin-top: 8px; padding-top: 8px; padding-left: 0;
  border-top: 1px solid var(--fg-a08);
  color: var(--fg-a40); font-size: 11px;
}
.scene-help-list li.scene-help-credit::before { content: none; }

/* ===== MOBILE ===== */
@media (max-width: 760px) {
  :root { --shell-gap: 10px; --header-h: 58px; --window-radius: 12px; }
  .app-header { right: 0; gap: 8px; padding: 0 var(--shell-gap); }
  .scene-label { gap: 8px; }
  .lab-home, .scene-label-icon { width: 34px; height: 34px; }
  .lab-home { grid-template-columns: 34px minmax(0, 0fr); }
  .lab-home:hover, .lab-home:focus-visible { grid-template-columns: 34px minmax(0, 1fr); }
  .scene-label-name .label-slot { font-size: 12px; max-width: 40vw; }
  .action-btn, .scene-help-btn, .scene-open-btn { width: 32px; height: 32px; border-radius: 9px; }
  /* The language pill is text-sized, not a square. */
  .action-pill { width: auto; }
  #actFullscreen, #actShortcuts { display: none; }
  .sidebar { display: none; }
  .mobile-dock { display: block; }
  .nav-arrow { width: 22px; height: 30px; }
  .nav-arrow:hover:not(:disabled) { height: 44px; }
  .nav-tip { display: none; }
}

/* ===== HIDE THE INTERFACE =====
   One switch clears the chrome around the window, because a lab's own controls
   frequently live under ours. The window itself goes full-bleed and a single
   toggle is left in the bottom-right corner. The iframe is never touched. */
.ui-toggle .icon-show { display: none; }
.ui-toggle[aria-pressed="true"] .icon-hide { display: none; }
.ui-toggle[aria-pressed="true"] .icon-show { display: block; }
.ui-toggle[aria-pressed="true"] {
  background: color-mix(in srgb, var(--menu-orb) 20%, var(--fg-a08));
  color: var(--fg);
}

:root.ui-hidden { --header-h: 0px; --shell-gap: 0px; --window-radius: 0px; }
/* The border has to collapse, not just go transparent: full-bleed, a transparent
   1px frame lets the shell ground through as a hairline around the lab. */
:root.ui-hidden .stage { border-width: 0; border-color: transparent; box-shadow: none; }
/* The zeroed --window-radius is for the full-bleed stage; the floating island
   keeps its own rounding. */
:root.ui-hidden .sidebar.open .sidebar-panel { border-radius: 16px; }

/* The whole header goes away; the floating restore button and the sidebar
   remain as the ways back in. */
:root.ui-hidden .app-header { display: none; }

/* Floating restore control — engine-built, appended to <body>. Wedged into the
   bottom-right corner with no gutter and wearing the sidebar's own surface, so
   it reads as the panel's last remaining edge rather than a stray button; the
   bevelled inner corner is what keeps it from looking like a card.
   corner-shape has no fallback where it is unsupported — the corner stays
   rounded there, which is a fine degradation. */
.ui-restore {
  display: none;
  position: fixed; right: 0; bottom: 0; top: auto; left: auto;
  width: 46px; height: 46px;
  z-index: 50;
  border-radius: 16px 0 0 0;
  corner-shape: bevel;
  background: var(--menu-bg);
  border: 1px solid var(--fg-a08);
  border-right: none; border-bottom: none;
  color: var(--fg);
  backdrop-filter: none; -webkit-backdrop-filter: none;
  opacity: .85;
  transform: translate(0, 0);
  transition: opacity var(--transition-fast), transform var(--transition-med),
              background .35s, border-color .2s;
}
.ui-restore:hover,
.ui-restore:focus-visible { opacity: 1; background: color-mix(in srgb, var(--fg) 8%, var(--menu-bg)); }
:root.ui-hidden .ui-restore { display: grid; place-items: center; }

/* Hovering the right edge slides the sidebar out over this corner — the button
   leaves the same way it would be covered, out through the corner it sits in. */
:root.ui-hidden .sidebar.open ~ .ui-restore {
  opacity: 0;
  pointer-events: none;
  transform: translate(100%, 100%);
}

/* Hover-only mode has nothing to pin: without the header there would be no way
   to unpin again, so the button goes with it. */
:root.ui-hidden .sidebar-pin { display: none; }

/* The handle centres itself in the header row — with --header-h at 0 that would
   put it half off the top edge. Nudge it down so the sidebar keeps a visible
   way in. */
:root.ui-hidden .sidebar-handle { top: 18px; }

/* visibility, not opacity alone: a fully transparent overlay would still eat
   the clicks meant for the lab underneath. */
:root.ui-hidden .nav-arrow,
:root.ui-hidden .mobile-dock {
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity var(--transition-fast) ease, visibility 0s var(--transition-fast);
}

/* ===== SECTION TYPOGRAPHY =====
   The engine only names the roles; each section's style.css supplies the faces
   through --display / --text / --mono. */
.sidebar-title,
.modal-title,
.modal-eyebrow,
.scene-help-title,
.toast-label,
.intro-label,
.scene-label-name .label-slot,
.action-btn.action-pill {
  font-family: var(--display);
  font-weight: var(--display-weight);
}
