/* Scene Machine main stage. Always dark, like a theatre with the lights down.
   Tokens and rules are described in research/app/UI.md. */

:root {
  color-scheme: dark;
  --ground: #131314;            /* Meet's own ground; what shows when no background is up */
  --stage-bg: #202124;
  --surface: #2d2e31;
  --surface-hover: #3c4043;
  --surface-translucent: rgb(32 33 36 / 0.72);
  --surface-veil: rgb(32 33 36 / 0.85);   /* menus and dialogs: a little more opaque */
  --on-surface: #e8eaed;
  --on-surface-muted: #9aa0a6;
  --divider: #5f6368;
  --accent: #c7311a;
  --accent-soft: #eb7866;
  --on-accent: #ffffff;
  --err: #f28b82;
  --warn: #fdd663;
  --ok: #81c995;
  --radius-tile: 14px;
  --radius-menu: 8px;
  --radius-pill: 999px;
  --shadow: 0 2px 8px rgb(0 0 0 / 0.5);
  --shadow-button: 0 1px 3px rgb(0 0 0 / 0.3);   /* pills and round buttons: lighter and tighter */
  --font-ui: "Google Sans Flex", "Google Sans", Roboto, Arial, sans-serif;
  --font-display: "Saira Condensed", var(--font-ui);
  --fade: 2000ms; /* Media.FADE_MS: a picture coming in, and level changes */
  --fade-out: 4000ms; /* Background.FADE_OUT_MS: a picture going, under the next */
  /* Blur and darken changing on the picture already showing (a dashboard
     override, or the next scene sharing the picture): slow and smooth, a
     mood shifting rather than a switch flipped. */
  --level-fade: 5000ms;
  --level-ease: cubic-bezier(.45, 0, .25, 1);
  /* The dashboard's track bars: the lit track played and still to play,
     any other where it was left, and the seek preview under the mouse. */
  --progress-played: var(--accent);
  --progress-rest: color-mix(in srgb, var(--accent) 20%, black);
  --progress-remembered: rgb(255 255 255 / 0.12);
  --progress-preview: rgb(255 255 255 / 0.15);
  /* documentrules.CURSOR_COLORS, in the same order: presence bubbles. */
  --cursor-0: #ff6b5c;
  --cursor-1: #4cc26b;
  --cursor-2: #5ab0ff;
  --cursor-3: #f7b32b;
  --cursor-4: #c77dff;
  --cursor-5: #ff8fab;
  --cursor-6: #2ec4b6;
  --cursor-7: #ffa552;

}

/* Shoelace follows our tokens. Its dark theme sheet sets these same tokens
   on `.sl-theme-dark`, so ours must be on that class too, not only :root,
   or the theme wins wherever the class sits below the root (dev pages). */
:root,
.sl-theme-dark {
  --sl-font-sans: var(--font-ui);
  --sl-color-primary-500: var(--accent);
  --sl-color-primary-600: var(--accent);
  --sl-color-primary-700: var(--accent-soft);
  --sl-panel-background-color: var(--surface-veil);
  --sl-panel-border-color: var(--divider);
  --sl-border-radius-medium: var(--radius-menu);
  --sl-input-background-color: var(--stage-bg);
}

/* Shoelace pads the dialog header, body and footer separately, which doubles
   the gap between a title and its content. One outer margin, small gaps
   between the parts. Applies to every dialog on the stage. */
/* The scrim behind every dialog: darker than the dark theme's 43% so the
   stage recedes and the dialog reads as the one thing to deal with. */
:root {
  --sl-overlay-background-color: rgb(0 0 0 / 0.8);
}

sl-dialog {
  --header-spacing: 20px 24px 6px;
  --body-spacing: 6px 24px 20px;
  --footer-spacing: 0 24px 20px;
}

/* Shoelace pads the close button's corner with "0 var(--header-spacing)",
   which our three-value spacing turns into 24px under the X: the header
   row grew to 72px and left a gap under every title. Set it outright: the
   X glyph as far in from the right as from the top (20px), close to the
   editors' 22px. The editors place theirs themselves (editors.css). */
sl-dialog::part(header-actions) {
  padding: 0 14px 0 0;
  align-items: flex-start;
}

.editor-dialog::part(header-actions) { padding: 0; }

/* Menus and dialogs sit on the same translucent dark surface as the music
   and dice panels (trying it, 2026-09-25). Dialogs also blur what is
   behind them; menus cannot, because backdrop-filter makes the menu the
   containing block for its submenu popups and they end up trapped inside. */
sl-dialog::part(panel) {
  backdrop-filter: blur(6px);
}

/* Without a blur a translucent menu looks busy over art, so menus are
   nearly solid: the dialogs' dark grey at 92%, to read as the same
   surface as the blurred panels. */
sl-menu {
  --sl-panel-background-color: rgb(32 33 36 / 0.92);
  --sl-panel-border-color: transparent;
  border: 0;
}

/* Select drop-downs (the pickers in the editors) are solid, not the veil. */
sl-select::part(listbox) {
  background: var(--surface);
}

/* Dividers draw with the panel border colour by default; keep them. */
sl-menu sl-divider {
  --color: var(--divider);
}

/* The close X: a 22px glyph about 20px in from
   the corner, a little larger than Shoelace's. Shoelace pads the header
   actions sideways only, so a top margin lines it up with the title. */
sl-dialog::part(close-button) {
  font-size: 22px;
  margin: 14px 0 0;
}

sl-dialog::part(close-button__base) {
  padding: 6px;
  color: var(--on-surface);
}

sl-dialog::part(close-button__base):hover { color: var(--accent-soft); }

* { box-sizing: border-box; }

html, body {
  height: 100%;
  margin: 0;
  overflow: hidden;
  background: var(--ground);
  color: var(--on-surface);
  font: 15px/1.4 var(--font-ui);
}

.material-symbols-outlined {
  font-size: 24px;
  line-height: 1;
  user-select: none;
}

/* Layers, back to front, in document order. */

.stage {
  position: relative;
  width: 100%;
  height: 100%;
  container-type: size;
  overflow: hidden;
  /* Stage chrome is not a document: dragging over tiles, labels, cards and
     pills must not highlight text. Fields and the debug log opt back in. */
  user-select: none;
}

.stage input,
.stage textarea,
.debug-drawer .log {
  user-select: text;
}

.background,
.background-slot {
  position: absolute;
  inset: 0;
}

/* The slots' z-index (the incoming picture over the outgoing one) stays
   inside the background; without this it lifted the picture over the
   stage's menu and buttons. */
.background { isolation: isolate; }

/* Per-scene levels as data attributes (0..10) on each background layer
   (the two image slots and the video), set on a layer as it becomes the
   incoming one, so an outgoing layer keeps its blur and darken while it
   fades. The scene form's preview uses the same attributes at a quarter
   of the blur, since it is about a quarter of the stage. */
.background-slot,
.background-video,
.scene-form .preview { --bg-level-blur: 0; --bg-darken: 0; }
.background-slot[data-blur="1"],
.background-video[data-blur="1"],
.scene-form .preview[data-blur="1"] { --bg-level-blur: 1; }
.background-slot[data-blur="2"],
.background-video[data-blur="2"],
.scene-form .preview[data-blur="2"] { --bg-level-blur: 2; }
.background-slot[data-blur="3"],
.background-video[data-blur="3"],
.scene-form .preview[data-blur="3"] { --bg-level-blur: 3; }
.background-slot[data-blur="4"],
.background-video[data-blur="4"],
.scene-form .preview[data-blur="4"] { --bg-level-blur: 4; }
.background-slot[data-blur="5"],
.background-video[data-blur="5"],
.scene-form .preview[data-blur="5"] { --bg-level-blur: 5; }
.background-slot[data-blur="6"],
.background-video[data-blur="6"],
.scene-form .preview[data-blur="6"] { --bg-level-blur: 6; }
.background-slot[data-blur="7"],
.background-video[data-blur="7"],
.scene-form .preview[data-blur="7"] { --bg-level-blur: 7; }
.background-slot[data-blur="8"],
.background-video[data-blur="8"],
.scene-form .preview[data-blur="8"] { --bg-level-blur: 8; }
.background-slot[data-blur="9"],
.background-video[data-blur="9"],
.scene-form .preview[data-blur="9"] { --bg-level-blur: 9; }
.background-slot[data-blur="10"],
.background-video[data-blur="10"],
.scene-form .preview[data-blur="10"] { --bg-level-blur: 10; }
.background-slot[data-darken="1"],
.background-video[data-darken="1"],
.scene-form .preview[data-darken="1"] { --bg-darken: 0.1; }
.background-slot[data-darken="2"],
.background-video[data-darken="2"],
.scene-form .preview[data-darken="2"] { --bg-darken: 0.2; }
.background-slot[data-darken="3"],
.background-video[data-darken="3"],
.scene-form .preview[data-darken="3"] { --bg-darken: 0.3; }
.background-slot[data-darken="4"],
.background-video[data-darken="4"],
.scene-form .preview[data-darken="4"] { --bg-darken: 0.4; }
.background-slot[data-darken="5"],
.background-video[data-darken="5"],
.scene-form .preview[data-darken="5"] { --bg-darken: 0.5; }
.background-slot[data-darken="6"],
.background-video[data-darken="6"],
.scene-form .preview[data-darken="6"] { --bg-darken: 0.6; }
.background-slot[data-darken="7"],
.background-video[data-darken="7"],
.scene-form .preview[data-darken="7"] { --bg-darken: 0.7; }
.background-slot[data-darken="8"],
.background-video[data-darken="8"],
.scene-form .preview[data-darken="8"] { --bg-darken: 0.8; }
.background-slot[data-darken="9"],
.background-video[data-darken="9"],
.scene-form .preview[data-darken="9"] { --bg-darken: 0.9; }
.background-slot[data-darken="10"],
.background-video[data-darken="10"],
.scene-form .preview[data-darken="10"] { --bg-darken: 1; }
.background-slot,
.background-video { --bg-blur: calc(var(--bg-level-blur) * 2px); }
.scene-form .preview { --bg-blur: calc(var(--bg-level-blur) * 0.5px); }

.background-slot img,
.background-video {
  filter: blur(var(--bg-blur));
}

/* Each layer shades itself. Levels only ease on the layer in front (a GM
   editing the current scene); an incoming layer takes its levels before it
   shows, and an outgoing one keeps them. */
.background-slot::after,
.background-video::after {
  content: "";
  position: absolute;
  inset: 0;
  background: rgb(0 0 0 / var(--bg-darken));
  pointer-events: none;
}

.background-slot.is-front img,
.background-video.is-front {
  transition: filter var(--level-fade) var(--level-ease);
}

.background-slot.is-front::after,
.background-video.is-front::after {
  transition: background var(--level-fade) var(--level-ease);
}

/* A scene change crossfades on the music's clock (Background.mjs): the
   outgoing picture creeps out over --fade-out, slow at first, so it is
   still mostly there while the new track loads; the incoming one comes in
   on top over --fade once the track plays. */
.background-slot {
  opacity: 0;
  transition: opacity var(--fade-out) ease-in;
}

.background-slot.is-front {
  opacity: 1;
  z-index: 1;
  transition: opacity var(--fade) ease;
}

/* A slot reused while still fading out goes at once, so its old picture
   never visibly turns into the new one. */
.background-slot.is-snap { transition: none; }

.background-slot img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.background-slot.is-empty img { display: none; }

/* Background video: a muted YouTube player oversized to cover the stage at
   any aspect ratio, faded in over the image once it is playing. */
.background-video {
  position: absolute;
  inset: 0;
  overflow: hidden;
  /* Not quite 0: Chrome throttles a fully invisible iframe, and YouTube's
     title overlay would then not run its timer and clear before we show. */
  opacity: 0.001;
  transition: opacity var(--fade) ease;
  pointer-events: none;
}

.background-video.is-front { opacity: 1; }

/* --video-overscan oversizes the player a little so no letterbox edge shows;
   YouTube's title bar is handled by holding the layer back for the first
   seconds after playing starts (VIDEO_SHOW_DELAY_MS in Media.mjs). */
.background-video {
  --video-overscan: 1.3;
}

.background-video > div,
.background-video iframe {
  position: absolute;
  top: 50%;
  left: 50%;
  width: calc(max(100cqw, 177.78cqh) * var(--video-overscan));
  height: calc(max(100cqh, 56.25cqw) * var(--video-overscan));
  transform: translate(-50%, -50%);
  pointer-events: none;
}

/* The music player is audio only. YouTube wants a real-sized player, so it
   is full size but parked off the stage. */
.music-player,
.music-player iframe {
  position: absolute;
  width: 200px;
  height: 200px;
}

.music-player {
  left: -1000px;
  top: 0;
  overflow: hidden;
  pointer-events: none;
}

.ui-slot {
  position: absolute;
  inset: 0;
}

/* The last UI's drawing, fading out over the next (Shell.fadeOut_,
   UI_FADE_OUT_MS). Two classes, so it wins over a UI's own entrance. */
.ui-slot.is-leaving {
  pointer-events: none;
  animation: ui-slot-out 1000ms ease forwards;
}

@keyframes ui-slot-out {
  to { opacity: 0; }
}

/* Role tray: one row of square tiles along the bottom (UI.md). Tiles scale
   with the count so the row always fits; --count comes from data-count. */

/* The roll toasts' layer (RollToasts.mjs) is laid out exactly like the
   tray, so each slot sits over its tile. */
.role-tray,
.roll-toasts {
  --count: 4;
  --side: 11cqw;
  --gap: clamp(8px, 3cqw, 30px);
  --tile: min(29cqh, calc((100cqw - 2 * var(--side) - (var(--count) - 1) * var(--gap)) / var(--count)));
  position: absolute;
  left: 0;
  right: 0;
  bottom: 6cqh;
  display: flex;
  justify-content: center;
  gap: var(--gap);
}

.role-tray[hidden] { display: none; }

/* Hidden by a scene (Hide PC List): the tray slides off the bottom of the
   stage and fades, easing in as it leaves and easing out as it returns.
   1s, the same as RoleTray.SLIDE_MS, which takes it out of the layout
   after. */
.role-tray {
  transition: transform 1000ms ease-out, opacity 1000ms ease-out;
}

.role-tray.is-off {
  transform: translateY(calc(100% + 8cqh));
  opacity: 0;
  transition-timing-function: ease-in;
}

@media (prefers-reduced-motion: reduce) {
  .role-tray { transition: none; }
}
.role-tray[data-count="1"],
.roll-toasts[data-count="1"] { --count: 1; }
.role-tray[data-count="2"],
.roll-toasts[data-count="2"] { --count: 2; }
.role-tray[data-count="3"],
.roll-toasts[data-count="3"] { --count: 3; }
.role-tray[data-count="4"],
.roll-toasts[data-count="4"] { --count: 4; }
.role-tray[data-count="5"],
.roll-toasts[data-count="5"] { --count: 5; --side: 6cqw; }
.role-tray[data-count="6"],
.roll-toasts[data-count="6"] { --count: 6; --side: 4cqw; }
.role-tray[data-count="7"],
.roll-toasts[data-count="7"] { --count: 7; --side: 3cqw; }
.role-tray[data-count="8"],
.roll-toasts[data-count="8"] { --count: 8; --side: 2cqw; }

.role-tile {
  position: relative;
  width: var(--tile);
  height: var(--tile);
  border-radius: var(--radius-tile);
  overflow: visible;
  box-shadow: var(--shadow);
  transition: transform 150ms ease, opacity 300ms ease;
}

.role-thumb {
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: var(--radius-tile);
  display: block;
}

/* A long name wraps onto tight lines and grows upward from the bottom
   edge; nothing is cut off. */
.role-name {
  position: absolute;
  left: 7%;
  bottom: 6%;
  right: 7%;
  font: 500 calc(var(--tile) * 0.12) / 0.9 var(--font-ui);
  color: #fff;
  text-shadow: 0 1px 3px rgb(0 0 0 / 0.9), 0 0 12px rgb(0 0 0 / 0.5);
  text-align: center;
  overflow-wrap: anywhere;
}

.role-badge {
  position: absolute;
  top: 6%;
  right: 6%;
  max-width: 88%;
  padding: 2px 8px;
  border-radius: var(--radius-pill);
  background: var(--surface-translucent);
  font: 500 calc(var(--tile) * 0.09) / 1.4 var(--font-ui);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.role-badge:empty { display: none; }
.role-tile.is-mine .role-badge { background: var(--accent); color: var(--on-accent); cursor: pointer; }
.role-tile.is-mine .role-badge:hover { background: var(--accent-soft); }

.role-caption {
  position: absolute;
  left: 50%;
  top: 100%;
  transform: translateX(-50%);
  margin-top: 6px;
  padding: 4px 10px;
  border-radius: var(--radius-pill);
  background: var(--accent);
  color: var(--on-accent);
  font: 500 12px/1.3 var(--font-ui);
  white-space: nowrap;
}

.role-caption:empty { display: none; }

.role-tile.is-highlighted {
  outline: 4px solid var(--accent);
  outline-offset: 0;
}

/* A slight inner shadow on a selected tile. It lives on an overlay because
   an inset shadow on the tile itself would paint under the portrait. */
.role-tile.is-highlighted::after,
.role-tray.is-choosing .role-tile.is-mine::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  box-shadow: inset 0 0 0 1px rgb(0 0 0 / .18), inset 0 1px 6px rgb(0 0 0 / .2);
  pointer-events: none;
}

.role-tile.is-highlighted::before {
  content: "";
  position: absolute;
  left: 50%;
  bottom: 100%;
  transform: translateX(-50%);
  margin-bottom: 6px;
  border: 10px solid transparent;
  border-bottom-color: var(--accent);
  border-top: 0;
}

.role-attachment {
  position: absolute;
  inset: 0;
  pointer-events: none;
}

/* ChooseRoles: tiles become buttons. */

.role-tray.is-choosing .role-tile { cursor: pointer; }
.role-tray.is-choosing .role-tile:hover { transform: translateY(-4px); }
.role-tray.is-choosing .role-tile.is-mine { outline: 4px solid var(--accent); outline-offset: 0; }

.role-tile.is-shaking { animation: role-shake 300ms ease; }

@keyframes role-shake {
  25% { transform: translateX(-6px); }
  50% { transform: translateX(6px); }
  75% { transform: translateX(-4px); }
}

/* Round tool buttons, top right. The 24px glyphs sit 9px inside 42px
   circles that touch, 9px from the corner, so the icons are as far from
   each other (18px) as from the stage's edges. A link game adds the
   fullscreen button on top, pushing the rest (and their panels) down one. */

.tool-buttons {
  position: absolute;
  top: 9px;
  right: 9px;
  display: flex;
  flex-direction: column;
}

/* Stage buttons (hamburger, fullscreen, music, dice): bare white glyphs
   over the art with a soft dark glow so they read on a pale background;
   hovering fills the circle 40% black and brings the glyph to full
   strength (80% at rest, the same for all of them). .tool-button and .menu-button add size
   and place; .tool-button is listed here too so a cached page without the
   shared class still looks right. */
.stage-button,
.tool-button {
  display: grid;
  place-items: center;
  border: 0;
  border-radius: 50%;
  cursor: pointer;
  color: #fff;
  background: transparent;
  font: inherit;
  opacity: .8;
}

.stage-button .material-symbols-outlined,
.tool-button .material-symbols-outlined {
  text-shadow: 0 1px 2px rgb(0 0 0 / .4), 0 0 10px rgb(0 0 0 / .2);
}

/* An SVG glyph (the dice) in place of a Material Symbol: same size and
   colour, and the same glow as a drop shadow, since text-shadow does not
   reach an SVG. */
.tool-icon {
  width: 24px;
  height: 24px;
  fill: currentColor;
  filter: drop-shadow(0 1px 2px rgb(0 0 0 / .4)) drop-shadow(0 0 10px rgb(0 0 0 / .2));
}

.stage-button:hover,
.tool-button:hover {
  background: rgb(0 0 0 / .4);
  opacity: 1;
}

.tool-button {
  width: 42px;
  height: 42px;
}

/* Hamburger, top left, mirroring the tool buttons: a 42px circle 9px from
   the corner, so its glyph is 18px in from both edges. */

.menu {
  position: absolute;
  top: 9px;
  left: 9px;
}

.menu-button {
  width: 42px;
  height: 42px;
  padding: 0;
}

.menu sl-menu {
  min-width: 180px;
  font-size: 16px;
}

.menu sl-menu-item::part(base) {
  padding-block: 3px;
}

/* The hovered row is a lighter grey band, not Shoelace's accent fill: a
   touch lighter than --surface-hover and 85% so the menu shows through.
   The same band for a row that keeps keyboard focus after the pointer
   leaves (Shoelace focuses rows on hover) and for a row whose submenu is
   open, which Shoelace paints with its neutral-100 token. */
.menu sl-menu {
  --menu-band: rgb(68 72 76 / 0.85);
  --sl-color-neutral-100: var(--menu-band);
  --sl-color-neutral-1000: var(--on-surface);
}

.menu sl-menu-item:not([disabled])::part(base):hover,
.menu sl-menu-item:not([disabled]):focus-visible::part(base) {
  background: var(--menu-band);
  color: var(--on-surface);
}

/* Pill buttons (start, reset, roll...). */

.pill {
  font: 500 15px/1 var(--font-ui);
  color: var(--on-surface);
  background: var(--surface);
  border: 0;
  border-radius: var(--radius-pill);
  padding: 11px 20px;
  cursor: pointer;
  box-shadow: var(--shadow-button);
}

.pill:hover { background: var(--surface-hover); }
.pill.accent { background: var(--accent); color: var(--on-accent); }
.pill.accent:hover { background: var(--accent-soft); }
.pill:disabled { opacity: .5; cursor: default; }

/* Dice and music panels, drop from their buttons. They pop in and out
   like Shoelace's dropdown menus (fade and scale from 90%, 100 ms, from the
   corner nearest the buttons): @starting-style gives the entry, and
   allow-discrete holds display until the exit has played. */

.dice-panel,
.music-panel {
  position: absolute;
  right: 55px; /* beside the tool buttons: 9 + 42 + 4 */
  width: 300px;
  padding: 8px;
  border-radius: var(--radius-tile);
  background: var(--surface-translucent);
  box-shadow: var(--sl-shadow-large);   /* the menus' shadow */
  backdrop-filter: blur(6px);
  /* Centred on its button: top is the button's middle (9 px down, 42 px
     buttons), and translate lifts it by half its own height. */
  translate: 0 -50%;
  transform-origin: right center;
  transition: opacity 100ms ease, transform 100ms ease, display 100ms allow-discrete;
}

.music-panel { top: 30px; display: flex; align-items: center; gap: 10px; } /* 9 + 42 / 2 */
.dice-panel { top: 72px; } /* 9 + 42 + 42 / 2 */

/* In a link game the fullscreen button takes the top slot. */
body.is-link-game .music-panel { top: 72px; } /* 9 + 42 + 42 / 2 */
body.is-link-game .dice-panel { top: 114px; } /* 9 + 42 * 2 + 42 / 2 */

.dice-panel[hidden],
.music-panel[hidden] {
  display: none;
  opacity: 0;
  transform: scale(.9);
}

@starting-style {
  .dice-panel,
  .music-panel {
    opacity: 0;
    transform: scale(.9);
  }
}

@media (prefers-reduced-motion: reduce) {
  .dice-panel,
  .music-panel { transition: none; }
}

/* Music button states: locked until the first click, dim with nothing
   playing, red on an embed error. */
/* The GM tools (hamburger, dashboard arrow) hide for players
   (MainStage.showGmTools_), and so do tool buttons without their feature;
   the attribute must beat any display rule. */
.menu[hidden],
.button-tooltip[hidden],
.tool-button[hidden] { display: none; }

.music-button.is-locked { outline: 2px solid var(--accent); outline-offset: -2px; } /* inside: the buttons touch */
.music-button.is-error { color: var(--err); }

/* The music panel is only the volume slider, floating beside the button
   with no panel behind it (2026-09-29). */
.music-panel {
  width: 200px;
  background: none;
  box-shadow: none;
  backdrop-filter: none;
}
.music-panel input[type="range"] {
  flex: 1;
  margin: 0;
  accent-color: var(--accent);
  /* No panel behind it, so a heavy shadow keeps it readable on light art. */
  filter: drop-shadow(0 1px 2px rgb(0 0 0 / .8)) drop-shadow(0 0 6px rgb(0 0 0 / .6));
}

.roll-form { position: relative; display: flex; }

.roll-form input {
  flex: 1;
  min-width: 0;
  font: 16px/1 var(--font-ui);
  color: var(--on-surface);
  background: var(--stage-bg);
  border: 1px solid var(--divider);
  border-radius: var(--radius-pill);
  padding: 10px 80px 10px 14px;
}

.roll-form input:focus { outline: 2px solid var(--accent); outline-offset: 1px; }

/* Roll tucks into the right end of the field, so the two read as one. */
.roll-form .pill {
  position: absolute;
  top: 3px;
  right: 3px;
  bottom: 3px;
  padding: 0 18px;
}

/* Status banner, top centre: notices in the brand red, errors on the
   plain surface in red. Fixed above everything but the shared cursors,
   dialogs and tooltips included; it sits outside the stage in
   index.html for this. */

.banner {
  position: fixed;
  z-index: 10000;
  top: 12px;
  left: 50%;
  translate: -50% 0;
  max-width: 70%;
  padding: 10px 16px;
  border-radius: var(--radius-menu);
  background: var(--accent);
  color: var(--on-accent);
  box-shadow: var(--shadow);
  display: none;
}

.banner.show { display: block; animation: banner-pop-in 450ms cubic-bezier(.3, .7, .3, 1) both; }
.banner.is-leaving { animation: banner-pop-out 260ms ease-in both; }
.banner.err { background: var(--surface); color: var(--err); }

/* Roll toasts (RollToasts.mjs): a dice result popping up above the
   roller's tile for everyone, with the banner's bounce. The layer stays put
   when a scene hides the tray, so toasts keep the tiles' places. */
.roll-toasts {
  bottom: calc(6cqh + var(--tile) + 14px);
  z-index: 15;
  pointer-events: none;
}

.roll-toast-slot {
  position: relative;
  width: var(--tile);
}

.roll-toast {
  position: absolute;
  left: 50%;
  bottom: 0;
  translate: -50% 0;
  transform-origin: 50% 100%;
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 6px 14px;
  border-radius: var(--radius-pill);
  background: var(--accent);
  color: var(--on-accent);
  box-shadow: var(--shadow);
  white-space: nowrap;
  animation: banner-pop-in 450ms cubic-bezier(.3, .7, .3, 1) both;
}

/* Points down at the tile. */
.roll-toast::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 100%;
  translate: -50% 0;
  border: 7px solid transparent;
  border-top-color: var(--accent);
  border-bottom: 0;
}

.roll-toast.is-out { animation: banner-pop-out 260ms ease-in both; }

/* A private roll (the GM, or no role): the roller alone sees it, centred
   under the dice panel (same right and width), pointing up at it. */
.roll-toast-own {
  position: absolute;
  top: 108px;
  right: 55px;
  width: 300px;
  z-index: 15;
  pointer-events: none;
}

body.is-link-game .roll-toast-own { top: 150px; } /* fullscreen button above: 108 + 42 */

.roll-toast-own .roll-toast {
  top: 0;
  bottom: auto;
  transform-origin: 50% 0;
}

.roll-toast-own .roll-toast::after {
  top: auto;
  bottom: 100%;
  border: 7px solid transparent;
  border-bottom-color: var(--accent);
  border-top: 0;
}

/* With the tray hidden by the scene the toasts float near the bottom, at
   their tiles' places across, still pointing down at the tile off stage. */
.role-tray.is-off ~ .roll-toasts,
.role-tray[hidden] ~ .roll-toasts { bottom: 8cqh; }

.roll-toast-label { font: 500 15px/1.2 var(--font-ui); }
/* "Perception = 12": an equals between the description (or dice) and the
   total. A no-break space, since a plain one would collapse at the end of
   the flex item; the gap after it is about a space at the label's size. */
.roll-toast-label::after { content: "\00a0="; }
.roll-toast-total { font: 800 24px/1 var(--font-ui); }

@media (prefers-reduced-motion: reduce) {
  .roll-toast, .roll-toast.is-out { animation: none; }
}

/* Toasts (toast.mjs): a bouncy pop in, a quick pop out. */
@keyframes banner-pop-in {
  0% { opacity: 0; scale: .3; }
  55% { opacity: 1; scale: 1.15; }
  75% { scale: .95; }
  100% { scale: 1; }
}

@keyframes banner-pop-out {
  0% { scale: 1; }
  35% { scale: 1.08; opacity: 1; }
  100% { scale: .3; opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .banner.show, .banner.is-leaving { animation: none; }
}

.retry {
  position: absolute;
  top: 60px;
  left: 50%;
  transform: translateX(-50%);
}

.retry[hidden] { display: none; }

/* Confirm dialog */

.confirm p { margin: 0; }
.confirm::part(panel) { max-width: 420px; }

/* Player Settings (PlayerSettings.mjs): one column, the portrait first,
   then the character's name and the person's. Until a first name is given
   there is no way out but Done (is-required). Narrow, so the square
   portrait, which fills the body, is no wider than the fields and the
   whole dialog fits the laptop stage's 555 px. */
.player-settings { --width: 210px; }
.player-settings.is-required::part(close-button) { display: none; }
.player-body { display: flex; flex-direction: column; gap: 12px; }
.player-role { display: flex; flex-direction: column; gap: 12px; }
.player-role[hidden] { display: none; }
.player-portrait { width: 100%; }
.player-field {
  align-self: stretch;
  display: flex;
  flex-direction: column;
  gap: 4px;
  font-size: 13px;
  color: var(--on-surface-muted);
}
.player-field input {
  min-width: 0;
  padding: 8px 10px;
  border: 1px solid var(--divider);
  border-radius: var(--radius-menu);
  background: var(--stage-bg);
  color: var(--on-surface);
  font: 16px var(--font-ui);
}
.player-body .pill { align-self: center; }
.player-body .pill:disabled { opacity: .5; cursor: default; }

/* Scene UIs */

.choose-roles-label {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 38cqh;
  margin: 0;
  text-align: center;
  font: 600 16px/1 var(--font-ui);
  letter-spacing: .08em;
  text-transform: uppercase;
  text-shadow: 0 1px 4px rgb(0 0 0 / 0.8);
  transition: opacity 500ms ease;
}

/* Once you hold a role the instruction is not for you. */
.choose-roles-label.is-hidden { opacity: 0; }

/* Story Cards. Sizes in container units so the table
   scales with the stage. No overlay of its own: the GM sets the scene's
   background, blur and darken like any other scene (2026-09-25). */

.story-cards {
  --card-w: 10.5cqw;
  /* Entering the scene, the table fades in rather than popping up. Only
     backwards fill, so once in it leaves no opacity (and no stacking
     context) behind. */
  animation: story-cards-in 1000ms ease backwards;
}

@keyframes story-cards-in {
  from { opacity: 0; }
}

.story-deck {
  position: absolute;
  left: 50%;
  top: 6cqh;
  width: calc(6 * var(--card-w) + 5 * 1.5cqw);
  height: calc(2 * var(--card-w) * 1.45);
  transform: translateX(-50%);
}

/* A card is two faces on an inner element that turns (CardMotion.flip):
   face down at 0deg, face up at 180deg. The card itself only moves. */
.story-card {
  /* Derived here, not on the scene, so cards attached to tray tiles (outside
     the scene's slot) and flying copies size from their own --card-w. */
  --card-h: calc(var(--card-w) * 1.45);
  --card-radius: calc(var(--card-w) * 0.12);
  position: absolute;
  width: var(--card-w);
  height: var(--card-h);
  border-radius: var(--card-radius);
  perspective: calc(var(--card-w) * 6);
}

.story-card-inner {
  position: absolute;
  inset: 0;
  transform-style: preserve-3d;
}

.story-card.is-up .story-card-inner { transform: rotateY(180deg); }

.story-card-back,
.story-card-front {
  position: absolute;
  inset: 0;
  border-radius: var(--card-radius);
  box-shadow: 0 6px 14px rgb(0 0 0 / 0.5);
  backface-visibility: hidden;
  background-size: cover;
  background-position: center;
}

/* Placeholder art (img/cardback.jpg, img/cardfront.jpg): swap the files to
   redesign. The front takes each outcome's colour as a tint over the art. */
.story-card-back { background-image: url(../img/cardback.jpg); }

.story-card-front {
  --card-tint: #ffffff;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2%;
  transform: rotateY(180deg);
  color: #1f1f1f;
  text-align: center;
  background-color: var(--card-tint);
  background-image: url(../img/cardfront.jpg);
  background-blend-mode: multiply;
}

.story-card.is-bad .story-card-front { --card-tint: #d33a2c; color: #fff; }
.story-card.is-important .story-card-front { --card-tint: #f6cf3c; }

.story-card-something { font: 400 calc(var(--card-w) * 0.13) / 1 var(--font-ui); }
.story-card-outcome { font: 800 calc(var(--card-w) * 0.19) / 1.1 var(--font-ui); letter-spacing: .02em; }
/* The one long word, sized to fit the card's width. */
.story-card.is-important .story-card-outcome { font-size: calc(var(--card-w) * 0.135); }
.story-card-decider { margin-top: 10%; font: 400 calc(var(--card-w) * 0.12) / 1.2 var(--font-ui); }

/* In the deck, a card sits at a slot: two rows, 6 on top and 5 below, the
   second offset by half a card. Home puts the sorted deck there face up;
   a round puts card i at slot i face down. */
.story-deck .story-card {
  left: calc(var(--col) * (var(--card-w) + 1.5cqw) + var(--row) * (var(--card-w) + 1.5cqw) / 2);
  top: calc(var(--row) * var(--card-h) * 0.72);
}

.story-deck .story-card[data-col="0"] { --col: 0; }
.story-deck .story-card[data-col="1"] { --col: 1; }
.story-deck .story-card[data-col="2"] { --col: 2; }
.story-deck .story-card[data-col="3"] { --col: 3; }
.story-deck .story-card[data-col="4"] { --col: 4; }
.story-deck .story-card[data-col="5"] { --col: 5; }
.story-deck .story-card[data-row="0"] { --row: 0; }
.story-deck .story-card[data-row="1"] { --row: 1; }

/* The front row overlaps the top row, whatever order the cards are in the
   DOM, and so do copies flying to it. */
.story-deck .story-card[data-row="1"],
.story-flight .story-card[data-row="1"] { z-index: 1; }

/* Start: pulled into one stack mid-deck, each card a little askew (from
   its index, so every client stacks alike), before the deal. */
.story-deck .story-card[data-place="stack"] {
  left: calc(50% - var(--card-w) / 2 + var(--dx));
  top: calc(var(--card-h) * 0.36 + var(--dy));
  transform: rotate(var(--tilt));
}

.story-deck .story-card[data-index="0"] { --dx: -0.3cqw; --dy: 0.2cqh; --tilt: -3deg; }
.story-deck .story-card[data-index="1"] { --dx: 0.2cqw; --dy: -0.3cqh; --tilt: 2deg; }
.story-deck .story-card[data-index="2"] { --dx: 0.4cqw; --dy: 0.1cqh; --tilt: 5deg; }
.story-deck .story-card[data-index="3"] { --dx: -0.1cqw; --dy: 0.4cqh; --tilt: -1deg; }
.story-deck .story-card[data-index="4"] { --dx: 0.3cqw; --dy: -0.1cqh; --tilt: 4deg; }
.story-deck .story-card[data-index="5"] { --dx: -0.4cqw; --dy: 0.3cqh; --tilt: -5deg; }
.story-deck .story-card[data-index="6"] { --dx: 0.1cqw; --dy: 0cqh; --tilt: 1deg; }
.story-deck .story-card[data-index="7"] { --dx: 0.5cqw; --dy: -0.2cqh; --tilt: -2deg; }
.story-deck .story-card[data-index="8"] { --dx: -0.2cqw; --dy: 0.5cqh; --tilt: 6deg; }
.story-deck .story-card[data-index="9"] { --dx: 0.2cqw; --dy: 0.3cqh; --tilt: -4deg; }
.story-deck .story-card[data-index="10"] { --dx: 0cqw; --dy: -0.4cqh; --tilt: 3deg; }

/* Once everyone has drawn, the rest of the deck leaves the table; Reset
   brings it back (as long as CardMotion's RESET_FADE_MS). */
.story-deck { transition: opacity 300ms ease; }
.story-cards[data-phase="allChosen"] .story-deck,
.story-cards[data-phase="revealed"] .story-deck { opacity: 0; pointer-events: none; }

.story-card.is-pickable { cursor: pointer; transition: transform 150ms ease; }
.story-card.is-pickable:hover { transform: translateY(-1cqh) scale(1.04); }

/* Story Cards Single: your own face-down card on your tile turns over when
   clicked (the tile's attachment layer is otherwise not clickable). */
.story-card.is-flippable { pointer-events: auto; cursor: pointer; }

/* Story Cards Single: discards rest below the stage, out of sight, and
   come back up from there when the deck is dealt again. */
.story-deck .story-card[data-place="gone"] {
  left: calc(50% - var(--card-w) / 2);
  top: 110cqh;
  visibility: hidden;
  pointer-events: none;
}

/* Flights (CardMotion.fly): a copy crosses the stage above the tray while
   the real card waits hidden at its destination. The animation places the
   copy and holds its --card-w. */
.story-flight {
  position: absolute;
  inset: 0;
  z-index: 20;
  pointer-events: none;
}

.story-flight .story-card {
  left: 0;
  top: 0;
}

.story-card.is-flying { visibility: hidden; }

/* Pills on the right, below the tool buttons. */
.story-controls {
  position: absolute;
  right: 3cqw;
  top: 36cqh;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.story-pill {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 9px 18px 9px 14px;
  font-size: 18px;
  font-weight: 400;
}

.story-pill .material-symbols-outlined { font-size: 20px; }

.story-ready {
  position: absolute;
  left: 0;
  right: 0;
  top: 20cqh;
  text-align: center;
}

.story-ready[hidden] { display: none; }

.story-ready-label {
  margin: 0 0 12px;
  font: 400 32px/1.2 var(--font-ui);
}

.story-ready .story-pill { font-size: 22px; padding: 12px 26px 12px 20px; }

/* Cards on tiles. */
.role-attachment .story-card {
  --card-w: calc(var(--tile) * 0.62);
  left: -10%;
  top: -14%;
  transform: rotate(-12deg);
  transform-origin: 50% 50%;
}

/* CardMotion also skips its animations under reduced motion. */
@media (prefers-reduced-motion: reduce) {
  .story-deck, .story-card.is-pickable { transition: none; }
  .story-cards { animation: none; }
}

/* Live cursors: an SVG over everything, never interactive. Each cursor is a
   <g> moved by its transform attribute; the viewBox tracks the stage size so
   one unit is one pixel and the glyph never scales. */

/* Outside the stage in index.html, fixed over the whole page: the one
   thing above the toast (.banner, 10000). */
.cursors {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 10001;
  overflow: visible;
  pointer-events: none;
}

.cursor {
  transition: transform 80ms linear, opacity 600ms ease;
  --cursor-color: var(--on-surface-muted);
}

.cursor.is-idle,
.cursor.is-gone { opacity: 0; }

.cursor-arrow {
  fill: var(--cursor-color);
  stroke: rgb(0 0 0 / 0.6);
  stroke-width: 1;
  stroke-linejoin: round;
}

.cursor-dot { display: none; fill: var(--cursor-color); stroke: rgb(0 0 0 / 0.6); }

.cursor-label {
  font: 500 10px var(--font-ui);
  fill: #fff;
  paint-order: stroke;
  stroke: rgb(0 0 0 / 0.7);
  stroke-width: 3px;
  stroke-linejoin: round;
}

/* Nobody's seat: a small grey dot, no arrow, no name. */
.cursor.is-unclaimed .cursor-arrow { display: none; }
.cursor.is-unclaimed .cursor-dot { display: block; }

.cursor[data-color="0"] { --cursor-color: #eb7866; }
.cursor[data-color="1"] { --cursor-color: #f28b82; }
.cursor[data-color="2"] { --cursor-color: #81c995; }
.cursor[data-color="3"] { --cursor-color: #fdd663; }
.cursor[data-color="4"] { --cursor-color: #8ab4f8; }
.cursor[data-color="5"] { --cursor-color: #ff8bcb; }
.cursor[data-color="6"] { --cursor-color: #78d9ec; }
.cursor[data-color="7"] { --cursor-color: #fcad70; }

@media (prefers-reduced-motion: reduce) {
  .cursor { transition: none; }
}

/* Dice tray: transparent, viewport-sized WebGL overlay. Pointer events pass
   through so the stage stays interactive. */

#dicetray {
  position: fixed;
  inset: 0;
  width: 100vw;
  height: 100vh;
  pointer-events: none;
  z-index: 50;
}

#dicetray canvas { display: block; }

/* Debug drawer, only with ?debug. */

.debug-drawer {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  max-height: 45%;
  overflow: auto;
  padding: 8px 12px;
  background: rgb(0 0 0 / 0.8);
  font-size: 12px;
  display: none;
}

.debug-drawer.show { display: block; }

/* Collapsed by default so the stage can be judged; the toggle expands it. */
.debug-drawer:not(.open) > :not(.debug-toggle) { display: none; }
.debug-drawer:not(.open) { padding: 0; background: none; }

.debug-toggle {
  font: 11px/1 var(--font-ui);
  color: var(--on-surface-muted);
  background: var(--surface-translucent);
  border: 0;
  border-radius: 6px 6px 0 0;
  padding: 4px 10px;
  cursor: pointer;
  margin-bottom: 4px;
}

.debug-row { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 6px; }

.tag {
  display: inline-block;
  padding: 2px 8px;
  border-radius: var(--radius-pill);
  background: var(--surface);
  color: var(--on-surface-muted);
}

.tag:empty { display: none; }

.debug-drawer .buttons { display: flex; flex-wrap: wrap; gap: 4px; margin-bottom: 6px; }

.debug-drawer .buttons button {
  font: 11px/1 var(--font-ui);
  color: var(--on-surface);
  background: var(--surface);
  border: 1px solid var(--divider);
  border-radius: 6px;
  padding: 4px 8px;
  cursor: pointer;
}

.debug-drawer .buttons button[hidden] { display: none; }

.debug-drawer textarea {
  width: 100%;
  height: 40px;
  font: 11px/1.3 var(--font-ui);
  color: var(--on-surface);
  background: var(--stage-bg);
  border: 1px solid var(--divider);
  border-radius: 6px;
}

.log {
  margin: 6px 0 0;
  font: 11px/1.4 var(--font-ui);
  white-space: pre-wrap;
  word-break: break-word;
}

.log .ok { color: var(--ok); }
.log .err { color: var(--err); }
.log .ev { color: var(--accent-soft); }
.log .t { color: var(--on-surface-muted); }

@media (prefers-reduced-motion: reduce) {
  .background-slot { transition: none; }
}

/* GM Dashboard (GmDashboard.mjs). On this person's screen
   the stage, and the page-wide layers drawn in stage coordinates (the
   shared cursors, the dice), shrink to 80% into the top right corner;
   the panels fill the L left around them. Stage fractions still land on
   the same spot because everything scales about the same corner. */

:root {
  --dashboard-scale: .8;
  --dashboard-move: 450ms;
  --stage-corner: 12px; /* the shrunk stage's rounded bottom-left corner */
}

.stage,
.cursors,
#dicetray {
  transform-origin: top right;
  transition: scale var(--dashboard-move) cubic-bezier(.3, .7, .3, 1);
}

body.is-dashboard .stage,
body.is-dashboard .cursors,
body.is-dashboard #dicetray { scale: var(--dashboard-scale); }

.gm-dashboard {
  position: fixed;
  inset: 0;
  z-index: 5;
  display: grid;
  grid-template-columns: calc((1 - var(--dashboard-scale)) * 100%) 1fr;
  grid-template-rows: 1fr calc((1 - var(--dashboard-scale)) * 100%);
  pointer-events: none;
  visibility: hidden;
  opacity: 0;
  transition: opacity var(--dashboard-move) ease, visibility 0s linear var(--dashboard-move);
  user-select: none;
}

body.is-dashboard .gm-dashboard {
  visibility: visible;
  opacity: 1;
  transition: opacity var(--dashboard-move) ease;
}

.dashboard-side,
.dashboard-bar {
  pointer-events: auto;
  background: var(--stage-bg);
  min-width: 0;
  min-height: 0;
}

.dashboard-side {
  position: relative;
  grid-column: 1;
  grid-row: 1 / 3;
  display: flex;
  flex-direction: column;
}

/* The column's right edge has a line only beside the stage, not beside the
   bottom bar, so the column and the bar read as one L. Row 1 of the grid,
   the stage's row, is the scale's share of the height. */
.dashboard-side::after {
  content: '';
  position: absolute;
  top: 0;
  right: 0;
  width: 1px;
  height: calc(var(--dashboard-scale) * 100%);
  background: var(--divider);
  pointer-events: none;
}

.dashboard-lists {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
}

.dashboard-bar {
  position: relative;
  grid-column: 2;
  grid-row: 2;
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 0 24px;
  border-top: 1px solid var(--divider);
}

/* The shrunk stage's bottom-left corner looks rounded: the dashboard paints
   over it, its grey outside a quarter circle and the divider on the arc, so
   the column's line and the bar's line bend round into each other. The
   stage itself is untouched, so nothing is scaled or clipped. The square
   starts on the column's line (left -1px) and ends on the bar's (the
   border row above the padding box). */
.dashboard-bar::before {
  content: '';
  position: absolute;
  left: -1px;
  top: calc(-1px - var(--stage-corner));
  width: calc(var(--stage-corner) + 1px);
  height: calc(var(--stage-corner) + 1px);
  background: radial-gradient(circle at 100% 0,
    transparent calc(var(--stage-corner) - .5px),
    var(--divider) calc(var(--stage-corner) - .5px) calc(var(--stage-corner) + .5px),
    var(--stage-bg) calc(var(--stage-corner) + .5px));
  pointer-events: none;
}

.dashboard-header {
  display: flex;
  align-items: center;
  padding: 6px 10px;
  border-bottom: 1px solid var(--divider);
}

.dashboard-title {
  margin: 0;
  font: 400 18px/1.2 var(--font-ui);
}

.dashboard-section-head {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 6px 10px 2px;
}

/* A little air under the column's header. */
.dashboard-scenes .dashboard-section-head { padding-top: 10px; }

.dashboard-section-head h3 {
  margin: 0;
  cursor: pointer;
  font: 700 12px/1.2 var(--font-ui);
  letter-spacing: .04em;
  text-transform: uppercase;
}

/* The controls (Phase 10d). The lit scene, track or ambient is red; a
   paused track is a paler red; a dot marks anything the dashboard has
   overridden, so the GM can see what differs from the scene. */

.dashboard-section-body { padding: 0 6px 6px 0; }

/* The fold caret, like a tree folder's: pointing right when closed, down
   when open. A closed section keeps its heading and slider. */
.dashboard-section-caret {
  display: grid;
  place-items: center;
  width: 16px;
  height: 16px;
  margin-left: -4px;
  margin-right: -4px;
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  cursor: pointer;
}

/* Shoelace's chevron at the trees' 15px, so the two carets match. */
.dashboard-section-caret sl-icon {
  font-size: 15px;
  rotate: 90deg;
  transition: rotate 150ms ease;
}

.dashboard-section.is-collapsed .dashboard-section-caret sl-icon { rotate: 0deg; }
.dashboard-section.is-collapsed .dashboard-section-body { display: none; }

/* Small type in the left column, so it holds up in a small Meet window.
   Effects are drawn as scenes are. */
.dashboard-scenes .folder-tree sl-tree,
.dashboard-scenes .folder-tree .tree-label,
.dashboard-effects .folder-tree sl-tree,
.dashboard-effects .folder-tree .tree-label { font-size: 12px; }
/* Less indent per folder level than the editors' trees: the column is
   narrow. A folder's items sit 12px in from its name. */
.dashboard-scenes .folder-tree sl-tree,
.dashboard-effects .folder-tree sl-tree { --indent-size: 12px; }
/* A folder's caret hangs in the gutter, its glyph only, so the folder's
   name lines up with the items' names above it. */
.dashboard-scenes .folder-tree sl-tree-item.is-folder::part(expand-button),
.dashboard-effects .folder-tree sl-tree-item.is-folder::part(expand-button) {
  width: 14px;
  padding: 0;
  margin-inline-start: -14px;
}
.dashboard-scenes .folder-tree sl-tree-item::part(item),
.dashboard-effects .folder-tree sl-tree-item::part(item) { padding-block: 1px; padding-inline-start: 8px; }
.dashboard-scenes .folder-tree .tree-icon,
.dashboard-effects .folder-tree .tree-icon { font-size: 13px; }

/* Each effect's picture before its name, square, its left edge on the
   music rows' thumbnails'. While it plays, it looks as a playing track's
   thumbnail does: their waveform (playing.gif) on a darkened picture, a
   red ring and a little depth inside, faded in quickly, held, then
   faded out when it ends. */
.dashboard-effects .tree-thumb {
  position: relative;
  flex: none;
  width: 16px;
  height: 16px;
  /* 4px more in, so the thumbnails line up with the music rows'. */
  margin-inline: 4px 6px;
  border-radius: 3px;
  overflow: hidden;
  background: var(--surface);
  outline: 2px solid transparent;
  transition: outline-color .2s;
}

.dashboard-effects .tree-thumb img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.dashboard-effects .tree-thumb-playing {
  position: absolute;
  inset: 0;
  border-radius: inherit;
  opacity: 0;
  transition: opacity .2s;
  background: rgb(0 0 0 / 0.35) url(../img/playing.gif) center / 12px no-repeat;
  box-shadow: inset 0 0 3px rgb(0 0 0 / 0.8);
}

.dashboard-effects sl-tree-item.is-playing .tree-thumb { outline-color: var(--accent); }
.dashboard-effects sl-tree-item.is-playing .tree-thumb-playing { opacity: 1; }

.dashboard-empty {
  margin: 2px 0 4px 24px;
  font-size: 12px;
  color: var(--on-surface-muted);
}

/* The scene on stage is lit like the track and ambient, in red. */
.dashboard-scenes .folder-tree sl-tree-item::part(item--selected) {
  background: var(--accent);
  color: var(--on-accent);
}

.dashboard-row {
  display: grid;
  grid-template-columns: 28px minmax(0, 1fr);
  align-items: center;
  gap: 6px;
  /* The thumbnails line up with the section headings' text, as the scene
     names do. */
  padding: 1px 4px 1px 24px;
  border-radius: 4px;
}

.dashboard-row:hover { background: var(--surface-hover); }
.dashboard-row.is-lit { background: var(--accent); color: var(--on-accent); }
.dashboard-row.is-lit.is-paused { background: color-mix(in srgb, var(--accent) 40%, transparent); }

/* A music row's bar fills the label's cell instead: red as far as the
   track has played, dark red for the rest. */
.dashboard-row.is-lit:has(.dashboard-row-progress) { background: none; }
.dashboard-row.is-lit:has(.dashboard-row-progress):hover { background: var(--surface-hover); }

.dashboard-row-progress,
.dashboard-row-preview {
  grid-column: 2;
  grid-row: 1;
  display: block;
  width: 100%;
  height: 100%;
  border: 0;
  border-radius: 3px;
  background: transparent;
  overflow: hidden;
  pointer-events: none;
  appearance: none;
  -webkit-appearance: none;
}

.dashboard-row-progress { visibility: hidden; }
.dashboard-row.has-progress .dashboard-row-progress { visibility: visible; }
.dashboard-row.is-lit .dashboard-row-progress { background: var(--progress-rest); }
.dashboard-row.is-lit.is-paused .dashboard-row-progress { opacity: .5; }

.dashboard-row-progress::-webkit-progress-bar { background: transparent; }
.dashboard-row-progress::-webkit-progress-value { background: var(--progress-remembered); }
.dashboard-row-progress::-moz-progress-bar { background: var(--progress-remembered); }
.dashboard-row.is-lit .dashboard-row-progress::-webkit-progress-bar { background: var(--progress-rest); }
.dashboard-row.is-lit .dashboard-row-progress::-webkit-progress-value { background: var(--progress-played); }
.dashboard-row.is-lit .dashboard-row-progress::-moz-progress-bar { background: var(--progress-played); }

/* Where a click on the label would seek, while the mouse is over it. */
.dashboard-row-preview { opacity: 0; }
.dashboard-row-label:hover ~ .dashboard-row-preview { opacity: 1; }
.dashboard-row-preview::-webkit-progress-bar { background: transparent; }
.dashboard-row-preview::-webkit-progress-value { background: var(--progress-preview); }
.dashboard-row-preview::-moz-progress-bar { background: var(--progress-preview); }
.dashboard-row.is-seek-start .dashboard-row-label { cursor: w-resize; }

.dashboard-row-icon {
  position: relative;
  width: 28px;
  height: 16px;
  padding: 0;
  border: 0;
  border-radius: 3px;
  overflow: hidden;
  background: var(--surface);
  cursor: pointer;
}

.dashboard-row-icon img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* The waveform while the track plays (Scene Machine's playing.gif, white
   on clear), and on hover the glyph for what a click does. */
.dashboard-row-playing,
.dashboard-row-hover {
  position: absolute;
  inset: 0;
  opacity: 0;
  transition: opacity .3s;
  pointer-events: none;
}

.dashboard-row-playing {
  background: rgb(0 0 0 / 0.35) url(../img/playing.gif) center / 14px no-repeat;
}

.dashboard-row.is-playing .dashboard-row-icon:not(:hover) .dashboard-row-playing { opacity: 1; }

/* A playing thumbnail is ringed in red, with a little depth inside. */
.dashboard-row-icon::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  box-shadow: inset 0 0 3px rgb(0 0 0 / 0.8);
  opacity: 0;
  transition: opacity .3s;
  pointer-events: none;
}

.dashboard-row.is-playing .dashboard-row-icon { outline: 2px solid var(--accent); }
.dashboard-row.is-playing .dashboard-row-icon::after { opacity: 1; }

.dashboard-row-hover {
  display: grid;
  place-items: center;
  background: rgb(0 0 0 / 0.5);
  color: var(--on-accent);
  font-size: 16px;
  transition-duration: .1s;
}

.dashboard-row-icon:hover .dashboard-row-hover { opacity: 1; }

/* The label seeks: where along it you click is where along the track. */
.dashboard-row-label {
  grid-column: 2;
  grid-row: 1;
  position: relative;
  min-width: 0;
  padding: 2px 4px;
  border: 0;
  background: none;
  color: inherit;
  font: 12px/1.3 var(--font-ui);
  text-align: left;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  cursor: pointer;
}

.dashboard-field {
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
}

.dashboard-field-label {
  font-size: 12px;
  color: var(--on-surface-muted);
}

.dashboard-picker { flex: 1; }

.dashboard-levels {
  flex: none;
  display: flex;
  flex-direction: column;
  gap: 8px;
  width: 160px;
}

.dashboard-levels .dashboard-field {
  flex-direction: row;
  align-items: center;
  gap: 8px;
}

.dashboard-levels .dashboard-field-label { width: 48px; font-size: 13px; color: var(--on-surface); }

.dashboard-slider input[type="range"] {
  flex: 1;
  min-width: 0;
  width: 100%;
  accent-color: var(--accent);
}

/* The heading sliders say what they are in their aria-label. */
.dashboard-section-head .dashboard-slider { flex-direction: row; align-items: center; gap: 2px; width: 84px; margin-left: auto; }
.dashboard-section-head .dashboard-field-label { display: none; }

.dashboard-field-label {
  display: inline-flex;
  align-items: center;
}

/* The red X beside anything the dashboard overrides: a click gives it
   back to the scene. Hidden while the control shows the scene's own. */
.override-clear {
  flex: none;
  display: inline-grid;
  place-items: center;
  width: 16px;
  height: 16px;
  margin-left: 4px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: none;
  color: var(--accent-soft);
  cursor: pointer;
}

.override-clear .material-symbols-outlined { font-size: 16px; font-weight: 700; }
.override-clear:hover { background: var(--accent); color: var(--on-accent); }
.override-clear[hidden] { display: none; }
.dashboard-section-head .override-clear { margin-left: 0; }
/* Snug after the heading word: the head's gap and the letter spacing's
   trailing space already part them. */
.dashboard-section-head h3 + .override-clear { margin-left: -3px; }

/* The toggle, the stage's lower-left corner. Full strength: only the GM
   sees it (MainStage.showGmTools_). */
.dashboard-toggle {
  position: absolute;
  left: 1px;
  bottom: 1px;
  z-index: 21;
  width: 28px;
  height: 28px;
  padding: 0; /* the browser's button padding would push the glyph right */
}

.dashboard-toggle .material-symbols-outlined {
  font-size: 22px;
  transition: rotate var(--dashboard-move) ease;
}

/* Open, the arrow turns round: back out to fill the page. */
body.is-dashboard .dashboard-toggle .material-symbols-outlined { rotate: 180deg; }

@media (prefers-reduced-motion: reduce) {
  .stage, .cursors, #dicetray, .gm-dashboard, .dashboard-toggle .material-symbols-outlined { transition: none; }
}
