/* ===== DESIGN TOKENS — single source of truth for all UI styling ===== */
:root {
  /* palette */
  --bg: #14181d; /* page background */
  --panel: rgba(20, 24, 29, 0.85); /* translucent "glass" panels */
  --panel-solid: #1c2127; /* opaque panels (modals) */
  --text: #e8e6e0; /* primary text */
  --accent: #c9a25a; /* gold accent (hover, labels, highlights) */
  --accent-soft: rgba(201, 162, 90, 0.25); /* accent tint (active turn row) */
  --border: rgba(255, 255, 255, 0.12); /* panel/control borders */
  --field: rgba(255, 255, 255, 0.08); /* input backgrounds */
  --overlay: rgba(0, 0, 0, 0.55); /* modal backdrop */
  --shadow: 0 2px 6px var(--shadow-soft); /* hand-card lift */
  --card: #fbfbf7; /* hand-card face */
  --card-line: #ccc; /* hand-card border */
  --ink: #141414; /* text on light cards */
  --avatar: #3a4048; /* empty avatar background */
  --danger: #d9776b; /* error / danger text */
  --danger-border: #a15147; /* danger control borders */
  --ok: #7fb069; /* success / public badge */
  --track: #0a0b0d; /* range-slider track */
  --shadow-soft: rgba(0, 0, 0, 0.35); /* soft shadow ring (slider thumbs, cards) */

  /* controls — buttons, fields, selects, and checkboxes all consume these */
  --control-bg: var(--field);
  --control-border: var(--border);
  --control-radius: var(--r-sm);
  --control-min-height: calc(30px * var(--ui-elements-scale));
  --control-pad-block: calc(6px * var(--ui-elements-scale));
  --control-pad-inline: calc(9px * var(--ui-elements-scale));

  /* shape */
  --r-sm: 6px; /* corner radii, small → extra-large */
  --r: 8px;
  --r-lg: 10px;
  --r-xl: 12px;
  --blur: blur(6px); /* panel backdrop blur */

  /* type */
  --font: system-ui, sans-serif; /* all UI text */
  --font-card: Georgia; /* rank on hand cards */

  /* Two UI-scale knobs — chrome (icons, controls, spacing) and text, independent. Desktop defaults; coarse below. */
  --ui-elements-scale: 1.25;
  --ui-text-scale: 0.75;

  /* fluid bases (floor, preferred, ceiling) — the knobs scale these into the tokens below */
  --fs-base: clamp(12px, 0.95vw, 15px); /* base UI text */
  --fs-sm-base: clamp(11px, 0.85vw, 13px); /* small text */

  /* text follows --ui-text-scale */
  --fs: calc(var(--fs-base) * var(--ui-text-scale));
  --fs-sm: calc(var(--fs-sm-base) * var(--ui-text-scale));

  /* chrome follows --ui-elements-scale (icons decoupled from the font so they track this, not text) */
  --ico-size: calc(var(--fs-base) * 1.2 * var(--ui-elements-scale));
  /* icon tiers — all derived from --ico-size so they track --ui-elements-scale */
  --ico-size-sm: calc(var(--ico-size) * 0.6); /* inline / meta icons */
  --ico-size-md: calc(var(--ico-size) * 0.73);
  --ico-size-lg: calc(var(--ico-size) * 0.93);
  --ico-size-xl: calc(
    var(--ico-size) * 1.22
  ); /* seat + FAB */ /* icon size (was 1.2em on the button font) */
  --inset: calc(
    clamp(10px, 1.4vw, 20px) * var(--ui-elements-scale)
  ); /* panel distance from screen edges */
  --hand-side: calc(
    300px * var(--ui-elements-scale)
  ); /* room reserved each side for the bottom-corner controls */
  --gap: calc(clamp(6px, 0.7vw, 10px) * var(--ui-elements-scale)); /* gaps between controls */
  --card-w: calc(
    clamp(80px, 7vw, 100px) * var(--ui-elements-scale)
  ); /* hand-card width (+50% baseline; ratio locked below) */
}

/* ---- Shared panel primitives (docked panels + modal heads/closes) ---- */
.panel {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  backdrop-filter: var(--blur);
  color: var(--text);
}
/* Default padding for pop-out MODALS (any panel with a title bar), so a new one
   doesn't have to set its own. HUD chrome (hand, status, player list — panels with
   no .panel-head) is untouched, and any modal can still override with its own value. */
.panel:has(> .panel-head) {
  padding: 10px 12px;
}
/* Hover readout over a deck/dispenser — how many are left inside. Follows the cursor. */
#hoverCount {
  position: fixed;
  z-index: 40;
  pointer-events: none;
  transform: translate(14px, -140%);
  padding: 2px 8px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--r);
  backdrop-filter: var(--blur);
  color: var(--text);
  font: var(--fs)/1.35 var(--font);
  white-space: nowrap;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.35);
}
/* Desktop context guide: the complete gesture/key surface for the hovered or held piece. It sits
   above the bottom-left action cluster and never intercepts table input. */
#controlGuide {
  position: fixed;
  left: var(--inset);
  bottom: calc(var(--inset) + 266px);
  z-index: 7;
  width: min(310px, calc(100vw - 2 * var(--inset)));
  padding: 10px 12px;
  pointer-events: none;
  color: var(--text);
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  backdrop-filter: var(--blur);
  box-shadow: var(--shadow);
  font: var(--fs-sm)/1.3 var(--font);
}
#controlGuide[hidden] {
  display: none;
}
#controlGuide > strong {
  display: block;
  color: var(--accent);
  font-size: var(--fs);
}
.controlGuideMeta {
  display: block;
  margin-top: 1px;
  color: var(--text);
  opacity: 0.65;
}
.controlGuideList {
  display: grid;
  gap: 4px;
  margin-top: 8px;
}
.controlGuideList > div {
  display: grid;
  grid-template-columns: minmax(92px, auto) 1fr;
  align-items: baseline;
  gap: 10px;
}
.controlGuideList kbd {
  justify-self: start;
  padding: 1px 5px;
  color: var(--text);
  background: var(--field);
  border: 1px solid var(--border);
  border-radius: 4px;
  font: 600 11px/1.35 var(--font-mono, ui-monospace, monospace);
  white-space: nowrap;
}
@media (hover: none), (pointer: coarse) {
  #controlGuide {
    display: none !important;
  }
}
.panel-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.close-x {
  cursor: pointer;
  opacity: 0.7;
  user-select: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: none; /* it's a <button> now — strip the default button chrome */
  border: 0;
  padding: 0;
  color: inherit;
  backdrop-filter: none;
}
.close-x:hover {
  opacity: 1;
}
/* ---- Shared content layout -------------------------------------------------
   Keep form rhythm and action alignment independent from feature containers. */
.button-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
}
.button-row--end {
  justify-content: flex-end;
}
.button-row--compact .ico {
  width: var(--ico-size-sm);
  height: var(--ico-size-sm);
}
.button-row--compact button {
  min-width: 4.5em;
  padding: 4px 9px;
  font-size: var(--fs-sm);
  white-space: nowrap;
}
.field-group {
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
}
.field-label {
  display: block;
  margin: 0;
  color: var(--text);
  font-size: var(--fs-sm);
  font-weight: 600;
  line-height: 1.35;
}
.help-text,
.status-text {
  margin: 6px 0;
  font-size: var(--fs-sm);
  line-height: 1.5;
}
.help-text {
  opacity: 0.65;
}
.status-text {
  min-height: 1.5em;
}

/* ---- Shared form controls -------------------------------------------------
   `.control` is the canonical opt-in for generated UI. Native text-like fields
   and selects share the same states without requiring a feature selector. */
:where(
    .control,
    input[type='text'],
    input[type='email'],
    input[type='password'],
    input[type='search'],
    input[type='url'],
    input[type='tel'],
    input[type='number'],
    textarea,
    select
  ) {
  box-sizing: border-box;
  min-height: var(--control-min-height);
  padding: var(--control-pad-block) var(--control-pad-inline);
  color: var(--text);
  background-color: var(--control-bg);
  border: 1px solid var(--control-border);
  border-radius: var(--control-radius);
  font: var(--fs)/1.2 var(--font);
  transition:
    border-color 0.15s ease,
    box-shadow 0.15s ease,
    background-color 0.15s ease,
    opacity 0.15s ease;
}
:where(
    .control,
    input[type='text'],
    input[type='email'],
    input[type='password'],
    input[type='search'],
    input[type='url'],
    input[type='tel'],
    input[type='number'],
    textarea,
    select
  ):focus-visible {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 2px var(--accent-soft);
}
:where(
    .control,
    input[type='text'],
    input[type='email'],
    input[type='password'],
    input[type='search'],
    input[type='url'],
    input[type='tel'],
    input[type='number'],
    textarea,
    select
  ):disabled {
  cursor: not-allowed;
  opacity: 0.45;
}
:where(input, textarea)::placeholder {
  color: color-mix(in srgb, var(--text) 48%, transparent);
}
textarea.control,
textarea {
  resize: vertical;
}
.control--compact {
  min-height: calc(26px * var(--ui-elements-scale));
  padding: calc(4px * var(--ui-elements-scale)) calc(7px * var(--ui-elements-scale));
  font-size: var(--fs-sm);
}

/* ---- Pop-out panel skeleton — every edge-docked/floating panel shares this
   fixed, vertically-stacked frame; each panel's own #id rule adds only what's
   unique to it (edge anchor, width, gap/max-height overrides). Change the shared
   look here, once, instead of in every panel. Docked panels override position via
   `#toolsDock > #x` and floated ones via `.panel.floating`, both more specific. */
.popout {
  position: fixed;
  display: flex;
  flex-direction: column;
  padding: 10px 12px;
}

/* ===== Shared-region clusters (UI_Redesign phase 1) =====
   A button group ("cluster") opens ONE anchored region; one pane shows at a time.
   Desktop: an overlay anchored under the cluster (position set in JS). Mobile: a
   full-width bottom sheet. Independent across clusters; accordion within a cluster. */
#regionBL {
  width: 140px;
} /* Interactions holds only My Seat + Lean In — no need for the full 300px region */
.region {
  position: fixed;
  z-index: 9;
  width: 300px;
  max-height: 70vh;
  display: flex;
  flex-direction: column;
  background: var(--panel);
  backdrop-filter: var(--blur);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  box-shadow: 0 12px 34px var(--shadow-soft);
  overflow: hidden;
}
.region[hidden] {
  display: none !important;
}
.region .pane {
  display: none;
  flex: 1;
  min-height: 0;
  flex-direction: column;
  overflow: auto;
  padding: 12px;
}
.region .pane.on {
  display: flex;
}
.regionHead {
  display: flex;
  align-items: center;
  margin-bottom: 8px;
}
.regionHead b {
  color: var(--accent);
  font-size: var(--fs-sm);
  text-transform: uppercase;
  letter-spacing: 0.05em;
}
.regionHead .regionClose {
  margin-left: auto;
}
.region #notesText {
  flex: 1;
  min-height: 200px;
  resize: none;
}
.region .pane[data-pane='chat'] #chatLog {
  flex: 1;
  max-height: none;
  min-height: 120px;
}
.region #chatInputRow {
  flex: none;
}
/* top-right cluster sits left of the right rail (min 270px); on mobile the rail collapses (phase 6) */
.cluster-tr {
  position: fixed;
  z-index: 7;
  display: flex;
}
.region #scoreTable {
  width: 100%;
}
.region #roomNotes {
  width: 100%;
  resize: vertical;
  min-height: 90px;
}
@media (max-width: 720px) {
  .cluster-tr {
    right: var(--inset);
  }
}
/* corner Interactions region: stacked full-width buttons */
.region .pane[data-pane='interactions'] {
  gap: 6px;
}
.region .pane[data-pane='interactions'] button {
  width: 100%;
  justify-content: flex-start;
}
/* The sheet bound is wider than the old 720px phone breakpoint and includes ANY coarse
   pointer, so tablets and touch laptops get the touch layout rather than a desktop overlay
   they cannot place precisely. Keep this query in step with SHEET_MQ in client.js. */
@media (max-width: 900px), (pointer: coarse) {
  /* Mobile: the region is a bottom sheet with three stops — peek / two-thirds / full
     (7e slice 1, mockup 10d). JS owns --sheet-h and the stop class; CSS owns the rest. */
  .region,
  .sheet {
    left: 0 !important;
    right: 0;
    top: auto !important;
    bottom: 0;
    width: auto;
    max-height: none;
    height: var(--sheet-h, 66vh);
    border-radius: 16px 16px 0 0;
    display: flex;
    flex-direction: column;
    padding: 0 0 env(safe-area-inset-bottom, 0);
    overscroll-behavior: contain;
    box-shadow: 0 -18px 44px rgba(0, 0, 0, 0.5);
  }
  .region.sheetAnim,
  .sheet.sheetAnim {
    transition: height 0.22s cubic-bezier(0.32, 0.72, 0, 1);
  }
  .region.sheetDrag,
  .sheet.sheetDrag {
    user-select: none;
  }
  /* The grabber is the drag surface; it is also the whole affordance that says "pull me". */
  .sheetGrab {
    flex: none;
    display: grid;
    place-items: center;
    padding: 9px 0 5px;
    touch-action: none;
    cursor: grab;
  }
  .sheetGrab::before {
    content: '';
    width: 38px;
    height: 4px;
    border-radius: 2px;
    background: var(--border);
  }
  .region.sheetDrag .sheetGrab::before,
  .sheet.sheetDrag .sheetGrab::before,
  .region.at-peek .sheetGrab::before {
    background: var(--accent);
  }
  /* One pane fills the sheet: header pinned, body scrolls, footer pinned. */
  .region .pane.on {
    flex: 1;
    min-height: 0;
    display: flex;
    flex-direction: column;
    overflow-y: auto;
  }
  .region .pane > .regionHead {
    position: sticky;
    top: 0;
    z-index: 2;
    background: inherit;
    margin: 0;
    padding: 2px 12px 8px;
  }
  .region .pane > #chatInputRow {
    position: sticky;
    bottom: 0;
    z-index: 2;
    background: inherit;
    padding-block: 8px;
  }
  /* At peek only the header, the tail of the body and the footer are meant to be read. */
  .region.at-peek .pane.on {
    overflow-y: hidden;
  }
  .region.at-peek .pane.on > :not(.regionHead):not(.paneFoot):not(#chatInputRow) {
    min-height: 0;
  }
}

/* ---- A small uppercase accent "mini-label" (section headings inside panels).
   Add class="miniLabel" to a new one; the element-scoped headings share it here. ---- */
.miniLabel,
.libPane h4 {
  color: var(--accent);
  font-size: var(--fs-sm);
  text-transform: uppercase;
  letter-spacing: 0.5px;
}
.miniLabel {
  opacity: 0.85;
}

/* ---- A solid inset "tile" row (member rows, unclaimed rows). Add class="tile"
   to reuse; the existing rows share the surface here and add their own layout. ---- */
.tile,
.memberRow,
.unclaimed-row {
  background: var(--panel-solid);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  padding: 6px 8px;
  font-size: var(--fs-sm);
  color: var(--text);
}

/* ---- The "toggled/active" highlight for non-chip buttons (accent border + text). ---- */
#wbTools button.on,
#leanBtn.on,
#skyList button.on {
  border-color: var(--accent);
  color: var(--accent);
}

/* ===== base ===== */
* {
  box-sizing: border-box;
}
html,
body {
  margin: 0;
  height: 100%;
  /* Without this every element that never sets a colour inherits the UA default —
     black — regardless of the dark surface it sits on. That is how the toast, and the
     region close button's ✕, ended up unreadable. */
  color: var(--text);
  overflow: hidden;
  overscroll-behavior: none; /* no mobile pull-to-refresh / bounce interfering with touch gestures */
  touch-action: manipulation; /* keep pan + pinch-zoom, but kill iOS double-tap-to-zoom on the UI (canvas + handcards keep their stricter touch-action:none) */
  background: var(--bg);
}
#app {
  position: fixed;
  inset: 0;
}
.tableLoading {
  position: fixed;
  inset: 0;
  z-index: 10000;
  display: grid;
  place-items: center;
  background:
    radial-gradient(
      circle at 50% 42%,
      color-mix(in srgb, var(--accent) 10%, transparent),
      transparent 34%
    ),
    var(--bg);
  opacity: 1;
  transition: opacity 260ms ease;
}
.tableLoading.is-ready {
  pointer-events: none;
  opacity: 0;
}
.tableLoadingCard {
  display: grid;
  justify-items: center;
  gap: 10px;
  color: var(--muted);
  text-align: center;
}
.tableLoadingCard img {
  width: 64px;
  height: 64px;
  margin-bottom: 6px;
}
.tableLoadingCard strong {
  color: var(--text);
  font-size: 18px;
  letter-spacing: 0.02em;
}
.tableLoadingCard > span:not(.tableLoadingSpinner) {
  font-size: 13px;
}
.tableLoadingSpinner {
  width: 26px;
  height: 26px;
  margin-top: 10px;
  border: 2px solid color-mix(in srgb, var(--text) 16%, transparent);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: tableLoadingSpin 0.8s linear infinite;
}
@keyframes tableLoadingSpin {
  to {
    transform: rotate(360deg);
  }
}
@media (prefers-reduced-motion: reduce) {
  .tableLoading {
    transition: none;
  }
  .tableLoadingSpinner {
    animation-duration: 1.6s;
  }
}
canvas {
  display: block;
}
canvas.measuring {
  cursor: crosshair;
} /* Measure tool active */
canvas.selecting {
  cursor: crosshair;
} /* Select tool active — felt-drag boxes a selection */

/* Touch context menu: a floating column of a piece's verbs, opened by long-press. */
#pieceMenu {
  position: fixed;
  z-index: 1000;
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 172px;
  max-width: 240px;
  padding: 6px;
}
#pieceMenu[hidden] {
  display: none;
}
#pieceMenu button {
  width: 100%;
  text-align: left;
  min-height: 40px; /* comfortable finger target */
}
/* Context menus keep explicit action names on touch and in compact UI. */
#pieceMenu button .lbl {
  display: inline;
}
#pieceMenu .button--danger {
  color: var(--danger);
}
/* Right-click raises this menu on desktop now, where 40px rows read as a panel rather than a
   menu — the same call already made for .overflowMenu .sheetItem. The query is the inverse of
   SHEET_MQ in client.js, which is what decides flat-list vs radial in the first place: keep the
   two in step. */
@media not all and (max-width: 900px) {
  @media (pointer: fine) {
    #pieceMenu button {
      min-height: 0;
      padding: 5px 8px;
    }
  }
}

/* Touch controls clustered on each edge: rotate ⟲/⟳ and raise/lower ▲/▼ of the held piece or
   selection. One cluster per edge so either hand can reach it. Buttons hide individually. */
.holdControls {
  position: fixed;
  top: 50%;
  transform: translateY(-50%);
  z-index: 900;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.holdLeft {
  left: 14px;
}
.holdRight {
  right: 14px;
}
.holdControls[hidden] {
  display: none;
}
.holdControls button {
  width: calc(52px * var(--ui-elements-scale));
  height: calc(52px * var(--ui-elements-scale));
  padding: 0;
  font-size: calc(20px * var(--ui-elements-scale));
  border-radius: 50%;
}
.holdControls button[hidden] {
  display: none;
}

/* Selection bars stack in one bottom-anchored column: recolor on top, actions below (nearest the
   thumb). Flexbox spaces them, so they never overlap no matter how the actions bar wraps. */
#selBars {
  position: fixed;
  left: 50%;
  bottom: var(--inset);
  transform: translateX(-50%);
  z-index: 6;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
}
#selBars #selTools {
  position: static;
  left: auto;
  bottom: auto;
  transform: none;
} /* positioned by #selBars now */
#selActions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  gap: 6px;
  max-width: 92vw;
}
#selActions[hidden] {
  display: none;
}
#selActions button {
  min-height: 40px;
}
/* Marquee box painted during a shift/select drag. JS tints it with MY UI accent inline
   (the selection is private); these accent values are just the pre-inline fallback. */
#marquee {
  position: fixed;
  z-index: 30;
  border: 1px solid var(--accent);
  background: var(--accent-soft);
  pointer-events: none;
}
.selectTool.on {
  color: var(--accent);
  border-color: var(--accent);
}
b {
  color: var(--accent);
}
a {
  color: var(--accent);
  text-decoration-thickness: 1px;
} /* readable links on the dark theme (was default blue) */ /* bold text uses the gold accent everywhere; more specific rules (e.g. .helpSec b) can override */
/* Any element carrying the `hidden` attribute stays hidden, overriding whatever
   display its own rule sets (several panels are flex, so this needs !important). */
[hidden] {
  display: none !important;
}

/* ===== top-left toolbar ===== */
.uiRow2 {
  flex-basis: 100%;
  display: flex;
} /* Chat + Notes on their own row below the nav */
#ui {
  position: fixed;
  top: var(--inset);
  left: var(--inset);
  display: flex;
  flex-wrap: wrap;
  font-family: var(--font);
}

.grp {
  display: inline-block;
  gap: 4px;
  min-width: 128px;
}
.grp button {
  padding: 10px 12px;
  width: 100%;
  box-sizing: border-box;
}
.menu {
  position: relative;
  display: inline-flex;
  width: auto;
}
.grp {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  flex-direction: column;
  padding: 6px;
  width: 100%;
  border: 1px solid var(--border);
  border-radius: var(--r);
  backdrop-filter: var(--blur);
  z-index: 5;
}
:where(button, .button) {
  appearance: none;
  -webkit-appearance: none;
  box-sizing: border-box;
  background: var(--panel);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: var(--r);
  padding: calc(9px * var(--ui-elements-scale)) calc(14px * var(--ui-elements-scale));
  font: 500 var(--fs)/1.2 var(--font);
  cursor: pointer;
  text-decoration: none;
  backdrop-filter: var(--blur);
  transition:
    border-color 0.15s,
    background-color 0.15s,
    color 0.15s,
    opacity 0.15s,
    transform 0.05s;
  user-select: none;
  -webkit-user-select: none;
  -webkit-touch-callout: none; /* a long-press shouldn't select the label or pop a callout */
}
:where(button, .button):not(:disabled, [aria-disabled='true']):hover {
  border-color: var(--accent);
}
:where(button, .button):not(:disabled, [aria-disabled='true']):active {
  transform: translateY(1px);
}
.button--primary {
  background: var(--accent-soft);
  border-color: var(--accent);
}
.button--primary:not(:disabled, [aria-disabled='true']):hover {
  background: color-mix(in srgb, var(--accent) 34%, transparent);
}
.button--danger {
  border-color: var(--danger-border);
  color: var(--danger);
} /* reusable danger button */
.button--danger:not(:disabled, [aria-disabled='true']):hover {
  background: color-mix(in srgb, var(--danger) 14%, transparent);
  border-color: var(--danger);
}
:where(button, .button)[aria-pressed='true'] {
  color: var(--accent);
  background: var(--accent-soft);
  border-color: var(--accent);
}
:where(button, .button):disabled,
.button[aria-disabled='true'] {
  cursor: not-allowed;
  opacity: 0.45;
  transform: none;
}
:where(button, .button)[aria-busy='true'] {
  cursor: progress;
}
.button--icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 0;
  aspect-ratio: 1;
  padding: calc(7px * var(--ui-elements-scale));
}
#status {
  font: var(--fs-sm) var(--font);
  padding: 8px 12px;
  border-radius: var(--r);
}

/* ===== inspect overlay (top-center hint + drawn-card actions) ===== */
#inspectHint {
  position: fixed;
  top: var(--inset);
  left: 50%;
  transform: translateX(-50%);
  padding: 8px 14px;
  border-radius: var(--r);
  font: var(--fs-sm) var(--font);
  z-index: 20;
}
#inspectColorRow {
  position: fixed;
  top: calc(var(--inset) + 40px);
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  align-items: center;
  gap: 16px;
  z-index: 20;
  padding: 6px 14px;
  border-radius: var(--r);
  font: var(--fs-sm) var(--font);
}
#inspectColorRow label {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
#inspectColorRow input[type='color'] {
  width: 40px;
  height: 26px;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: 4px;
  background: none;
  cursor: pointer;
}
/* Recolor-selection bar greys out when the selection mixes incompatible palettes. */
#selTools.disabled {
  opacity: 0.5;
}
#deckBrowseActions,
#drawActions,
#wbTools,
#wbStatus,
#trayTools,
#selTools {
  position: fixed;
  bottom: calc(var(--inset) + 84px);
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  gap: 8px;
  align-items: center;
  flex-wrap: wrap;
  justify-content: center;
  max-width: min(680px, 94vw);
  padding: 10px 12px;
  z-index: 20;
}
#deckBrowseActions .da-note,
#drawActions .da-note,
#wbTools .da-note,
#wbStatus .da-note,
#trayTools .da-note {
  color: var(--text);
  opacity: 0.7;
  font: var(--fs-sm) var(--font);
}
#drawActions kbd {
  font: 600 11px var(--font);
  background: var(--field);
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 1px 5px;
  margin-left: 4px;
}

/* ===== How to Play modal ===== */
.modalCard.helpCard {
  width: min(720px, 94vw);
  max-height: 86vh;
  display: flex;
  flex-direction: column;
  text-align: left;
}
.helpScroll {
  overflow-y: auto;
  margin: 6px 0 12px;
  padding-right: 4px;
}
.helpCard .libPane {
  min-height: 0; /* without this the pane grows to fit and the CARD scrolls, sticky tabs and all */
  overflow-y: auto;
}
.helpSec {
  margin-bottom: 14px;
}
.helpSec h4 {
  color: var(--accent-soft);
  margin: 0 0 5px;
  font: 600 var(--fs) var(--font);
}
.helpSec h4 ~ h4 {
  margin-top: 14px; /* a second heading in one section (GM abilities → Room controls) needs the
                       gap the section margin gives between sections */
}
.helpSec ul {
  margin: 0 0 0 18px;
  padding: 0;
}
.helpSec li {
  margin: 4px 0;
  line-height: 1.45;
  color: var(--text);
}
.helpSec b {
  font-weight: 600;
}
/* A control named in the help text carries its own icon, so the reader is looking for the same
   glyph the toolbar shows. Inline rather than flex: these sit mid-sentence. */
.helpSec b[data-icon] .ico,
.helpSec li > .ico {
  display: inline-block;
  vertical-align: -0.18em;
  margin-right: 4px;
  width: var(--ico-size-sm, 1em);
  height: var(--ico-size-sm, 1em);
}

/* ===== hand bar (bottom-center) ===== */
#handRow {
  position: fixed;
  bottom: var(--inset);
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  align-items: flex-end;
  gap: 8px;
  z-index: 5;
  max-width: calc(100vw - 2 * var(--inset));
}
.handFlank {
  flex: 0 0 auto;
}
.handFlank[hidden] {
  display: none;
}
#handFlankStack {
  display: flex;
  flex-direction: column; /* Show Hand above Rearrange, stacked on the left flank */
  gap: 6px;
  flex: 0 0 auto;
}
#hand {
  display: none;
  gap: 6px;
  padding: 8px;
  max-width: min(1200px, calc(100vw - 2 * var(--hand-side)));
  align-items: flex-start;
}
#hand.hand-dragging {
  visibility: hidden;
} /* hidden while a card is being dragged out */
.handScroll {
  display: flex;
  gap: 6px;
  overflow-x: auto;
  min-width: 0;
  padding-bottom: 2px;
  scrollbar-width: none;
}
.handScroll::-webkit-scrollbar {
  display: none;
}
.handScrollBtn {
  flex-shrink: 0;
  align-self: center;
  padding: 4px;
  opacity: 0.7;
}
.handScrollBtn:hover {
  opacity: 1;
}
.handScrollBtn:disabled {
  opacity: 0.25;
}
.handcard {
  touch-action: none;
  position: relative;
  flex-shrink: 0;
  width: var(--card-w);
  aspect-ratio: 44/62;
  background: var(--card);
  border: 1px solid var(--card-line);
  border-radius: var(--r-sm);
  display: flex;
  align-items: center;
  justify-content: center;
  font: bold calc(var(--card-w) * 0.5) var(--font-card);
  cursor: pointer;
  box-shadow: var(--shadow);
  transition: transform 0.1s;
}
#hand.reordering .handcard {
  cursor: grab;
}
#hand.reordering .handcard.dragging {
  cursor: grabbing;
  transform: scale(1.03);
  box-shadow:
    0 0 0 3px var(--accent),
    0 8px 20px var(--shadow-soft); /* accent ring = clearly picked up */
  z-index: 3;
}
.handcard.img {
  background-size: cover;
  background-position: center;
}
/* A tile (domino) hand slot: 1:2 and a touch narrower, so the tile fills it (same height as a card). */
.handcard.tile {
  width: calc(var(--card-w) * 0.705);
  aspect-ratio: 1 / 2;
}
/* A square word-tile hand slot: 1:1, a compact chunky tile (card-width across). */
.handcard.tileSq {
  width: var(--card-w);
  aspect-ratio: 1 / 1;
  align-self: center;
}
/* Custom-image card shapes in the hand: match the tabletop silhouette. */
.handcard.shape-square {
  border-radius: 0;
}
.handcard.shape-hex {
  aspect-ratio: 0.866 / 1; /* pointy-top regular hexagon (√3 : 2), points top/bottom */
  border-radius: 0;
  align-self: center;
  clip-path: polygon(50% 0, 100% 25%, 100% 75%, 50% 100%, 0 75%, 0 25%);
}
.handcard.txt {
  font: 600 calc(var(--card-w) * 0.15)/1.1 var(--font);
  padding: 3px;
  text-align: center;
  overflow: hidden;
  color: var(--ink);
}
.handcard:hover {
  transform: translateY(-6px);
}
#hand.selecting .handcard {
  cursor: pointer;
}
.handcard.sel {
  outline: 3px solid var(--accent);
  outline-offset: 1px;
  transform: translateY(-10px);
}
.handToggle {
  cursor: pointer;
  font-size: var(--fs-sm);
  padding: 6px 12px;
}
.handToggle.hide {
  align-self: flex-start;
  padding: 2px 7px;
  opacity: 0.6;
  flex-shrink: 0;
}
.handToggle.hide:hover {
  opacity: 1;
}
.handToggle.face {
  align-self: flex-start;
  padding: 2px 7px;
  opacity: 0.8;
}
.handToggle.face.on {
  opacity: 1;
  color: var(--accent);
}
.cardEye {
  position: absolute;
  top: 3px;
  right: 3px;
  padding: 2px;
  opacity: 0.55;
  background: rgba(0, 0, 0, 0.35);
  border-radius: 5px;
  color: #fff;
  z-index: 2;
}
.cardEye:hover {
  opacity: 1;
}
#hand.collapsed {
  padding: 4px;
}

/* ===== right-edge toggled pop-outs: private notes, whiteboard, chat ===== */
/* (all three share the same frame; positioning skeleton comes from .popout) */
#notesText {
  width: 100%;
  resize: vertical;
  padding: 8px;
  font: var(--fs-sm)/1.5 var(--font);
}

/* ===== shared timer (bottom-right, toggled) ===== */
#timer {
  right: var(--inset);
  bottom: var(--inset);
  width: min(260px, 88vw);
  gap: 8px;
}
#timerReadout {
  color: var(--text);
}
.timerRow {
  color: var(--text);
  font-size: var(--fs-sm);
}
#timerMode {
  background: none;
  color: var(--text);
  padding: 5px 6px;
  font: var(--fs-sm) var(--font);
}
#timerMode option {
  background: var(--panel-solid);
  color: var(--text);
}
#timerDur {
  width: 60px;
  padding: 5px 6px;
  font: var(--fs-sm) var(--font);
}

/* ===== show-cards panel (left edge, toggled) ===== */
/* left-edge toggled pop-outs: show-cards + drop-hand (share one frame) */
.showLabel {
  color: var(--accent);
  font-size: var(--fs-sm);
  margin-top: 2px;
}
.chipRow {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}
.chip {
  padding: 5px 10px;
  font-size: var(--fs-sm);
  background: var(--field);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: var(--r);
  cursor: pointer;
}
.chip.on {
  background: var(--accent-soft);
  border-color: var(--accent);
}
/* .segRow — a compact segmented control (radio chips), e.g. the card Shape picker */
.segRow {
  display: inline-flex;
  gap: 4px;
}
.segRow .chip.seg {
  padding: 4px 8px;
}
/* .chk — a checkbox-style toggle chip: a themed square that fills when on */
.chip.chk::before {
  content: '';
  display: inline-block;
  width: 12px;
  height: 12px;
  margin-right: 7px;
  vertical-align: -2px;
  border: 1px solid var(--border);
  border-radius: 3px;
  box-sizing: border-box;
}
.chip.chk.on::before {
  background: var(--accent);
  border-color: var(--accent);
}

/* Select variants share the field primitive. The arrow is built from gradients
   so it follows --accent instead of embedding a fixed colour in an SVG URL. */
:where(select, .control--select) {
  padding-right: calc(25px * var(--ui-elements-scale));
  cursor: pointer;
  appearance: none;
  -webkit-appearance: none;
  -moz-appearance: none;
  background-image:
    linear-gradient(45deg, transparent 50%, var(--accent) 50%),
    linear-gradient(135deg, var(--accent) 50%, transparent 50%);
  background-repeat: no-repeat;
  background-position:
    right calc(12px * var(--ui-elements-scale)) center,
    right calc(8px * var(--ui-elements-scale)) center;
  background-size: calc(4px * var(--ui-elements-scale)) calc(4px * var(--ui-elements-scale));
}
:where(select, .control--select) option {
  color: var(--text);
  background: var(--panel-solid);
}
:where(select[multiple], select[size]:not([size='1']), .control--multiselect) {
  padding-right: var(--control-pad-inline);
  background-image: none;
  cursor: default;
}
:where(select[multiple], select[size]:not([size='1']), .control--multiselect) option {
  padding: calc(4px * var(--ui-elements-scale)) calc(6px * var(--ui-elements-scale));
  border-radius: calc(var(--control-radius) - 2px);
}
:where(select[multiple], select[size]:not([size='1']), .control--multiselect) option:checked {
  color: var(--text);
  background: linear-gradient(var(--accent-soft), var(--accent-soft));
}

/* Native checkboxes use the same tokens and focus/disabled states as fields. */
:where(input[type='checkbox'], .checkbox__input) {
  appearance: none;
  -webkit-appearance: none;
  display: inline-grid;
  flex: 0 0 auto;
  place-content: center;
  width: calc(17px * var(--ui-elements-scale));
  height: calc(17px * var(--ui-elements-scale));
  margin: 0;
  color: var(--text);
  background: var(--control-bg);
  border: 1px solid var(--control-border);
  border-radius: calc(var(--control-radius) - 2px);
  cursor: pointer;
  transition:
    background-color 0.15s ease,
    border-color 0.15s ease,
    opacity 0.15s ease;
}
:where(input[type='checkbox'], .checkbox__input)::before {
  content: '';
  width: 62%;
  height: 62%;
  background: var(--panel-solid);
  clip-path: polygon(14% 44%, 0 59%, 42% 100%, 100% 20%, 83% 6%, 40% 67%);
  transform: scale(0);
  transition: transform 0.12s ease;
}
:where(input[type='checkbox'], .checkbox__input):checked {
  background: var(--accent);
  border-color: var(--accent);
}
:where(input[type='checkbox'], .checkbox__input):checked::before {
  transform: scale(1);
}
:where(input[type='checkbox'], .checkbox__input):focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
:where(input[type='checkbox'], .checkbox__input):disabled {
  cursor: not-allowed;
  opacity: 0.45;
}
.checkbox {
  display: inline-flex;
  align-items: center;
  gap: calc(7px * var(--ui-elements-scale));
  cursor: pointer;
}
#spawnHiddenControl {
  min-height: max(44px, var(--control-min-height));
  padding-inline: var(--control-pad-inline);
}

/* Number stepper — a themed field flanked by − / + buttons. Any input[type=number]
   is wrapped in .stepper by the client on load, so this is the universal number style. */
.stepper {
  display: inline-flex;
  align-items: stretch;
  vertical-align: middle;
  background: none;
  border: none;
  box-shadow: inset 0 0 0 1px var(--border);
  border-radius: var(--r-sm);
  overflow: hidden;
}
.stepper input[type='number'] {
  border: none;
  background: transparent;
  color: var(--text);
  text-align: center;
  font: var(--fs)/1 var(--font);
  padding: 5px 4px;
  -moz-appearance: textfield;
}
.stepper input[type='number']::-webkit-outer-spin-button,
.stepper input[type='number']::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}
.stepper input[type='number']:focus {
  outline: none;
}
.stepBtn {
  border: none;
  background: transparent;
  color: var(--text);
  opacity: 0.6;
  cursor: pointer;
  padding: 0 11px;
  font-size: 15px;
  line-height: 1;
  user-select: none;
  transition:
    opacity 0.12s ease,
    color 0.12s ease;
}
.stepBtn:hover {
  opacity: 1;
  color: var(--accent);
  background: none;
}

/* Stacked labeled field rows (e.g. table size): "Width  [− field +]" per row */
.sizeRow {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 10px 0;
}
.sizeRow > label {
  min-width: 48px;
  color: var(--text);
}

/* Library spawn cards: quantity stepper + color swatches + multi-select */
.cardCtrls {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  margin: 4px 0;
}
.qtyStep input[type='number'] {
  width: 30px;
}
/* Optional leading caption inside a stepper (e.g. dispenser "Amount"). */
.stepCap {
  white-space: nowrap;
}
.swatchRow {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
}
.swatch {
  width: 16px;
  height: 16px;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: 4px;
  cursor: pointer;
  box-sizing: border-box;
}
.swatch.on {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}
.swatch.neutral {
  background: repeating-linear-gradient(
    135deg,
    var(--field),
    var(--field) 4px,
    var(--border) 4px,
    var(--border) 5px
  );
}
.ownTog {
  align-self: flex-start;
}
.spawnBar {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 8px;
}
.spawnSelBtn {
  background: var(--accent-soft);
  color: var(--accent);
  border: 1px solid var(--accent);
  border-radius: var(--r);
  padding: 5px 12px;
  cursor: pointer;
  font: var(--fs)/1 var(--font);
}
.spawnSelBtn:hover {
  filter: brightness(1.12);
}
.libList.selecting .libCard {
  cursor: pointer;
}
.libList.selecting .button-row--compact {
  display: none;
} /* per-card Spawn hides while selecting */
.libCard.sel {
  outline: 2px solid var(--accent);
  outline-offset: -1px;
}
.libControls {
  display: flex;
  align-items: center;
  gap: 8px;
}
.libSearchWrap {
  flex: 1 1 auto;
  max-width: 340px;
  margin: 0;
  min-width: 0;
}
.libSearch {
  /* surface + radius via the shared control rules */
  width: 100%;
  box-sizing: border-box;
  padding: 6px 10px;
  font: var(--fs)/1 var(--font);
}
.libSearch:focus {
  outline: none;
  border-color: var(--accent);
}
.libSearch::placeholder {
  opacity: 0.55;
}

/* Add-modal orientation row (rotate an uploaded model 90° per click) */
.rotRow {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-top: 8px;
}
.rotRow .rotLabel {
  color: var(--accent);
  font-size: var(--fs-sm, 0.85em);
  margin-right: 2px;
}
.rotRow button {
  padding: 4px 11px;
}

/* Public chat panel (Tools) — its frame is the shared right-edge pop-out above */
#chatLog {
  min-height: 90px;
  font: var(--fs-sm)/1.35 var(--font);
}
.chatMsg {
  word-break: break-word;
}
#chatInput {
  /* surface + radius via the shared control rules */
  padding: 6px 8px;
  font: var(--fs)/1 var(--font);
}
#chatInput:focus {
  outline: none;
  border-color: var(--accent);
}
#chatBtn.hasUnread::after {
  content: '';
  display: inline-block;
  width: 7px;
  height: 7px;
  margin-left: 6px;
  border-radius: 50%;
  background: var(--accent);
  vertical-align: middle;
}

/* Minimalist accent scrollbars (app-wide) */
* {
  scrollbar-width: thin;
  scrollbar-color: var(--accent) transparent;
} /* Firefox */
::-webkit-scrollbar {
  width: 9px;
  height: 9px;
}
::-webkit-scrollbar-track {
  background: transparent;
}
::-webkit-scrollbar-thumb {
  background-color: var(--accent);
  border-radius: 6px;
  border: 2px solid transparent;
  background-clip: padding-box;
} /* padding-box floats a slim thumb */
::-webkit-scrollbar-thumb:hover {
  border-width: 1px;
  filter: brightness(1.12);
}
::-webkit-scrollbar-corner {
  background: transparent;
}

/* Themed range sliders: black track, accent thumb (app-wide) */
input[type='range'] {
  -webkit-appearance: none;
  appearance: none;
  height: 18px;
  background: transparent;
  cursor: pointer;
}
input[type='range']:focus {
  outline: none;
}
input[type='range']::-webkit-slider-runnable-track {
  height: 5px;
  background: var(--track);
  border-radius: 4px;
}
input[type='range']::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 14px;
  height: 14px;
  margin-top: -5px;
  background: var(--accent);
  border-radius: 50%;
  border: none;
  box-shadow: 0 0 0 1px var(--shadow-soft);
}
input[type='range']::-moz-range-track {
  height: 5px;
  background: var(--track);
  border-radius: 4px;
}
input[type='range']::-moz-range-thumb {
  width: 14px;
  height: 14px;
  background: var(--accent);
  border: none;
  border-radius: 50%;
  box-shadow: 0 0 0 1px var(--shadow-soft);
}

/* Lobby accent-color picker (personal, per-device) */
#accentPicker {
  display: flex;
  align-items: center;
  gap: 5px;
}
.accDot {
  width: 16px;
  height: 16px;
  border-radius: 50%;
  cursor: pointer;
  border: 2px solid transparent;
  box-sizing: border-box;
}
.accDot:hover {
  transform: scale(1.15);
}
.accDot.on {
  border-color: var(--text);
}
#accentCustom {
  width: 24px;
  height: 24px;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: 4px;
  background: none;
  cursor: pointer;
}
#accentCustom::-webkit-color-swatch-wrapper {
  padding: 2px;
}
#accentCustom::-webkit-color-swatch {
  border: none;
  border-radius: 2px;
}

/* ===== members panel (GM tools) ===== */
/* Customize Table — a centered modal (see the modal group above); its body scrolls if it
   outgrows the viewport, so the head/close stay put on short screens. */
/* Customize Table + Scale & Grid — movable pop-out panels (position:fixed comes from
   .popout; JS makes them draggable and remembers where you put them). Both start near
   the top-left so they don't cover the table centre while you calibrate. */
#memberList {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.memberRow {
  /* surface via .tile group */
  display: flex;
  align-items: stretch;
  justify-content: space-between;
  gap: 5px;
}

/* Unclaimed hands (loaded save whose owner hasn't returned) — atop the Members panel */
.unclaimed {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.unclaimed:empty {
  display: none;
}
.unclaimed-head {
  font-size: var(--fs-sm);
  text-transform: uppercase;
  letter-spacing: 0.5px;
  opacity: 0.6;
}
.unclaimed-row {
  /* surface via .tile group; accent border marks it as awaiting action */
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 6px;
  border-color: var(--accent);
}
.unclaimed-name {
  font-weight: 600;
}
.prow.turn-waiting {
  background: var(--accent-soft);
  font-style: italic;
  opacity: 0.8;
}

/* ===== library editor panel ===== */
/* Library is a centered overlay (see the modal group above); .libraryCard is its
   wide card — most of the viewport, ~200px of dimmed backdrop on each side. */
.modalCard.libraryCard {
  width: calc(100vw - 400px);
  max-width: none;
  max-height: 86vh;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 10px;
  text-align: left;
}
/* Sit just below the create modals it opens (z 10), above the editor chrome. */
.modalCard:has(.libSticky) {
  padding: 0;
} /* padding moves onto the sticky + panes (below) so the sticky is a normal full-width flex item with no negative-margin flow deficit */
.modalCard:has(.libSticky) .libPane {
  padding: 0 22px 20px;
}
.modalCard .libSticky {
  position: sticky;
  top: 0;
  z-index: 5;
  background: var(--panel-solid);
  padding: 18px 22px 14px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
/* One tab (and pane) per asset type. */
.libTabs {
  display: flex;
  gap: 2px;
  border-bottom: 1px solid var(--border);
}
.libTab {
  background: var(--panel);
  border: none;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  padding: 8px 16px;
  color: var(--text);
  opacity: 0.55;
  cursor: pointer;
  font: var(--fs) var(--font);
}
.libTab:hover {
  opacity: 0.85;
}
.libTab.on {
  opacity: 1;
  color: var(--accent);
  border-bottom-color: var(--accent);
}
/* Timer's mode tabs: drop the divider bar and center them in the panel */
#timerMode {
  border-bottom: none;
  justify-content: center;
}
#timerMode .libTab {
  margin-bottom: 0;
}
.libPane {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
/* Add-to-Library builder */
#addModal .libraryCard {
  width: min(900px, calc(100vw - 80px));
} /* narrower than the full-width library */
.addDeckCols {
  display: flex;
  gap: 44px;
  align-items: flex-start;
}
.addCol {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.libPane h4 {
  margin: 0 0 2px;
} /* shared look via .miniLabel group */
/* Add-modal two-column pane: preview/upload tiles (left) + controls (right) */
.addSplit {
  display: flex;
  gap: 20px;
  align-items: flex-start;
}
.addSplit > .addPreview {
  flex: 0 0 42%;
  gap: 10px;
}
.addSplit .addPreview .uploadSq {
  width: 100%;
}
.addSplit .addPreview .cardPrev {
  width: 100%;
  height: auto;
  aspect-ratio: 5 / 7;
}
/* Deck panes: previews side-by-side (no scroll) + right controls not full-width */
[data-pane='imgdecks'] .addPreview,
[data-pane='txtdecks'] .addPreview {
  flex: 0 0 auto;
  flex-direction: row;
  gap: 10px;
}
[data-pane='imgdecks'] .addPreview .uploadSq {
  width: 250px;
}
[data-pane='txtdecks'] .addPreview .cardPrev {
  width: 250px;
}
[data-pane='txtdecks'] .addCol input[type='text'],
[data-pane='txtdecks'] .addCol textarea {
  max-width: 340px;
}
/* Board panes: half-width name field + left-aligned Save buttons */
[data-pane='modelboards'] .addCol input[type='text'],
[data-pane='imgboards'] .addCol input[type='text'] {
  max-width: 50%;
}
/* Add-modal: hold every tab at the tallest tab's height (Text Decks) so switching doesn't resize the modal; Save row pinned bottom-right */
#addModal .libPane {
  display: flex;
  flex-direction: column;
  min-height: 560px;
}
#addModal .libPane > .addSplit,
#addModal .libPane > .addDeckCols {
  flex: 1;
  align-items: stretch;
}
#addModal .addSplit > .addPreview {
  align-self: flex-start;
}
#addModal .addSplit .addCol .row {
  margin-top: auto;
}
#addModal .addSplit .addCol > input[type='text'],
#addModal .addSplit .addCol > textarea {
  flex: 0 0 auto;
} /* natural height — don't stretch to fill; the gap goes to .row above */
.addRow {
  display: flex;
  gap: 10px;
  align-items: center;
}
.addRow input[type='file'] {
  flex: 0 1 auto;
  min-width: 0;
} /* don't stretch — keep the preview beside the button */
#adObjDispCustom .addRow .stepper {
  flex: 0 0 auto;
}
#adObjDispCustom .addRow .stepper input[type='number'] {
  width: 6ch;
}
.addColorRow {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
}
.addColorRow span {
  font-size: var(--fs-sm);
  opacity: 0.75;
}
.cardPrev {
  width: 90px;
  height: 126px;
  flex: none;
  border-radius: 10px;
  border: 1px solid var(--border);
  background: var(--panel) center/contain no-repeat;
}
.colliderRow {
  display: flex;
  gap: 4px;
}
.colliderBtn {
  width: 34px;
  height: 34px;
  padding: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--panel-solid);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  color: var(--text);
  opacity: 0.55;
  cursor: pointer;
}
.colliderBtn:hover {
  opacity: 0.85;
}
.colliderBtn.on {
  opacity: 1;
  color: var(--accent);
  border-color: var(--accent);
}
.colliderBtn svg {
  display: block;
}
/* click-to-upload tile — the preview square IS the upload button */
.uploadSq {
  position: relative;
  border: 1.5px dashed var(--border);
  border-radius: 8px;
  background: var(--panel) center/contain no-repeat;
  cursor: pointer;
  display: flex;
  align-items: center;
  overflow: hidden;
}
.uploadSq:hover {
  border-color: var(--accent);
}
.uploadSq.filled {
  border-style: solid;
  align-items: flex-end;
}
.uploadSq .sqLabel {
  width: 100%;
  text-align: center;
  font-size: var(--fs-sm);
  line-height: 1.3;
  padding: 3px 4px;
  margin: 0;
  color: var(--text);
  opacity: 0.75;
  pointer-events: none;
}
.uploadSq.filled .sqLabel {
  background: rgba(20, 20, 20, 0.6);
  color: #fff;
  opacity: 1;
}
.uploadSq:not(.filled) .sqLabel::before {
  content: '\FF0B  ';
}
.sqCard {
  width: 96px;
  aspect-ratio: 5 / 7;
}
.sqBoard {
  width: 120px;
  aspect-ratio: 1;
}
.sqWide {
  width: 190px;
  aspect-ratio: 2 / 1;
}
.skyCross {
  display: grid;
  grid-template-columns: repeat(4, 64px);
  gap: 5px;
  grid-template-areas: '.    top    .     .' 'left front  right back' '.    bottom .     .';
}
.skyCross .uploadSq {
  aspect-ratio: 1;
}
/* Skybox pane: fill-width panorama tile + name/buttons bottom-aligned across both columns */
[data-pane='sky'] .addDeckCols {
  align-items: stretch;
}
[data-pane='sky'] .addCol input[type='text'] {
  width: 100%;
} /* both name fields sit inside a wrapper div now, so they need an explicit width to
     fill the column rather than falling back to the input's default size */
[data-pane='sky'] .sqWide + *,
[data-pane='sky'] .skyCross + * {
  margin-top: auto;
} /* push the whole name group (label + field + buttons) to the bottom together */
[data-pane='sky'] .sqWide {
  width: 100%;
}
/* The add-modal header's <b> is the modal title (.panel-title), not a mini label —
   it was only ever this size because a more specific #addModal rule overrode it. */
#addHead {
  font-size: var(--fs-sm);
  opacity: 0.6;
  margin: 0;
}
/* shared look via .miniLabel group */
.libList {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
}
.libEmpty {
  opacity: 0.45;
  font-size: var(--fs-sm);
  padding: 2px 4px;
}
.libCard {
  flex-direction: column;
  gap: 8px;
  font-size: var(--fs-sm);
  background: var(--panel-solid);
  border: 1px solid var(--border);
  border-radius: var(--r);
}
.libPreview {
  height: 132px;
  border-radius: var(--r-sm);
  background: var(--panel);
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  overflow: hidden;
}
.libPreview.empty {
  opacity: 0.5;
}
.libThumb {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  border-radius: 3px;
}
.libPreview.deckPreview .libThumb {
  max-width: 46%;
}
.libMeta {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 6px;
}
.libName {
  font-weight: 600;
}
.libBadge {
  font-size: var(--fs-sm);
  text-transform: uppercase;
  letter-spacing: 0.5px;
  border-radius: 4px;
  padding: 0 5px;
}
.libBadge.pub {
  color: var(--ok);
  border: 1px solid var(--ok);
}
.libBadge.priv {
  color: var(--text);
  opacity: 0.5;
  border: 1px solid var(--border);
}

/* ===== shared modal anatomy =====
   HTML overlays use `.modal-backdrop > .modal`; native <dialog> elements use
   `.modal` directly and get their backdrop from `::backdrop`. */
.modal-backdrop {
  position: fixed;
  inset: 0;
  background: var(--overlay);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 10;
}

.lightingBody {
  max-height: min(68vh, 650px);
  overflow-y: auto;
}
.lightingGlobeStage {
  display: grid;
  justify-items: center;
  gap: 4px;
  padding: 8px 0 4px;
  text-align: center;
}
.lightingGlobeStage p {
  margin: 2px 12px 8px;
  font-size: var(--fs-sm);
}
.lightingGlobe {
  position: relative;
  width: 190px;
  height: 160px;
  cursor: grab;
  touch-action: none;
  user-select: none;
  outline: none;
}
.lightingGlobe:active {
  cursor: grabbing;
}
.lightingGlobe:focus-visible .lightingSphere {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}
.lightingSphere {
  position: absolute;
  left: 35px;
  top: 5px;
  width: 120px;
  height: 120px;
  border-radius: 50%;
  background:
    radial-gradient(
      circle at var(--light-x, 68%) var(--light-y, 28%),
      var(--key-color, #fff) 0 3%,
      transparent 30%
    ),
    radial-gradient(
      circle at var(--light-x, 68%) var(--light-y, 28%),
      color-mix(in srgb, var(--key-color, #fff) 72%, var(--ambient-color, #fff)) 0,
      color-mix(in srgb, var(--ambient-color, #fff) 32%, #29313a) 52%,
      #11161b 100%
    );
  box-shadow:
    inset -12px -14px 25px rgba(0, 0, 0, 0.5),
    0 10px 24px rgba(0, 0, 0, 0.28);
  filter: brightness(var(--globe-brightness, 1));
}
.lightingSun {
  position: absolute;
  width: 15px;
  height: 15px;
  margin: -7.5px;
  border-radius: 50%;
  left: var(--sun-x, 68%);
  top: var(--sun-y, 28%);
  background: var(--key-color, #fff);
  border: 2px solid rgba(255, 255, 255, 0.9);
  box-shadow: 0 0 10px var(--key-color, #fff);
}
.lightingGround {
  position: absolute;
  left: 23px;
  bottom: 4px;
  width: 144px;
  height: 25px;
  border-radius: 50%;
  background: color-mix(in srgb, var(--ambient-color, #fff) 18%, #20262d);
  transform: perspective(90px) rotateX(58deg);
}
.lightingShadow {
  position: absolute;
  left: 52px;
  top: 7px;
  width: 70px;
  height: 12px;
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.72);
  opacity: var(--shadow-opacity, 0.7);
  filter: blur(var(--shadow-blur, 4px));
  transform-origin: 10px 50%;
  transform: rotate(var(--shadow-angle, 130deg));
}
.globeNorth,
.globeEast,
.globeSouth,
.globeWest {
  position: absolute;
  font-size: 10px;
  opacity: 0.65;
}
.globeNorth {
  left: 91px;
  top: -8px;
}
.globeEast {
  right: 16px;
  top: 61px;
}
.globeSouth {
  left: 91px;
  top: 126px;
}
.globeWest {
  left: 15px;
  top: 61px;
}
.lightingDirectionGrid {
  display: grid;
  grid-template-columns: 1fr 72px 16px 1fr 72px 16px;
  gap: 6px;
  align-items: center;
  margin: 4px 0 12px;
  font-size: var(--fs-sm);
}
.lightingDirectionGrid input {
  min-width: 0;
  width: 100%;
}
.lightingControl {
  display: grid;
  grid-template-columns: 116px 1fr 38px;
  gap: 9px;
  align-items: center;
  margin: 9px 0;
}
.lightingControl label {
  font-size: var(--fs-sm);
}
.lightingControl output {
  opacity: 0.65;
}
.lightingControl input[type='color'] {
  width: 38px;
  height: 30px;
  padding: 2px;
}
.lightingDefaults,
.lightingActions {
  margin-top: 14px;
}
.sceneSaveCard {
  display: grid;
  gap: 12px;
  width: min(390px, 92vw);
}
.sceneSaveCard .modal__body {
  display: grid;
  gap: 12px;
}
.sceneSaveCard .checkRow {
  display: flex;
  gap: 10px;
  align-items: flex-start;
}
.sceneSaveCard .checkRow small {
  display: block;
  opacity: 0.65;
}

#fogAuraForm {
  --fs: max(var(--fs-base), calc(var(--fs-base) * var(--ui-text-scale)));
  --fs-sm: max(var(--fs-sm-base), calc(var(--fs-sm-base) * var(--ui-text-scale)));
}
#fogAuraForm .help-text {
  opacity: 1;
  color: var(--text);
}
#fogAuraForm .checkRow {
  min-height: 44px;
  align-items: center;
}
#fogAuraEnabled::before {
  width: calc(10px * var(--ui-elements-scale));
  height: calc(10px * var(--ui-elements-scale));
}
@media (pointer: coarse) {
  #fogAuraRadius {
    font-size: max(16px, var(--fs));
  }
}

@media (max-width: 520px) {
  .lightingDirectionGrid {
    grid-template-columns: 1fr 78px 16px;
  }
  .lightingControl {
    grid-template-columns: 100px 1fr 38px;
  }
}
:where(.modal, .modalCard) {
  background: var(--panel-solid);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: var(--r-xl);
  padding: 20px 22px;
  width: min(340px, 92vw);
  font: var(--fs)/1.5 var(--font);
}
.modal__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  min-width: 0;
}
.modal__title {
  margin: 0;
  color: var(--text);
  font-size: 16px;
  font-weight: 600;
  line-height: 1.25;
}
.modal__close {
  flex: 0 0 auto;
  margin-left: auto;
}
.modal__body {
  min-width: 0;
}
.modal__footer {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: 8px;
  margin-top: 18px;
}
/* Room Settings: a see-through tabbed modal so the table & grid stay visible for live preview (UI_Redesign phase 3) */
#roomSettingsModal {
  background: rgba(0, 0, 0, 0.25);
} /* lighter backdrop than --overlay */
.modalCard.roomSettingsCard {
  background: rgba(28, 33, 39, 0.74);
  backdrop-filter: blur(3px);
  width: min(480px, 92vw);
}

/* Combined Library modal (parallel test): Room-Settings-style see-through card, source toggle + badges */
.modalCard.lib2Card {
  background: rgba(28, 33, 39, 0.7);
  backdrop-filter: blur(4px);
}

/* Room Info dock: Room Notes section (UI_Redesign phase 4a) */
.dockLabel {
  font-size: var(--fs-sm);
  margin: 2px 0 4px;
}

/* Room Info dock: Members section (UI_Redesign phase 4b) */
#memberSection {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
#memberSection #memberList {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 3px;
}
.pendingDot {
  display: inline-block;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--accent);
  margin-left: 6px;
  vertical-align: middle;
  box-shadow: 0 0 0 0 var(--accent);
  animation: pulse 1.6s infinite;
}
.pendingDot[hidden] {
  display: none;
}
#libraryModal.src-custom .lib2-bi {
  display: none;
} /* Custom  → hide built-in lists */
#libraryModal.src-builtin .lib2-cu {
  display: none;
} /* Built-In → hide custom lists */
/* separate the two sources when both show */
/* little source badge, only in All view */
#libraryModal.src-all .lib2-bi > .libCard,
#libraryModal.src-all .lib2-cu > .libCard {
  position: relative;
}
#libraryModal.src-all .lib2-bi > .libCard::after,
#libraryModal.src-all .lib2-cu > .libCard::after {
  position: absolute;
  top: 6px;
  right: 6px;
  z-index: 1;
  font-size: 9px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  padding: 1px 6px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--panel-solid);
  color: var(--muted);
  pointer-events: none;
}
#libraryModal.src-all .lib2-bi > .libCard::after {
  content: 'Built-In';
}
#libraryModal.src-all .lib2-cu > .libCard::after {
  content: 'Custom';
  color: var(--accent);
  border-color: var(--accent);
}
.modalCard h3 {
  margin: 0 0 12px;
  font-size: 16px;
}
.modalCard label {
  display: block;
  margin: 10px 0 4px;
  color: var(--accent);
}
/* Add-to-Library modal: the "--- x ---" flourish is decorative, so it lives in CSS
   rather than the label text (keeps the label's wording clean). Plus control widths
   and the note margin that used to be inline styles. */
#addModal .addCol > label::before {
  content: '--- ';
}
#addModal .addCol > label::after {
  content: ' ---';
}
#addModal input[type='number'] {
  width: 80px;
}
#addModal .addRow .note {
  margin: 0;
}
.modalCard input[type='color'] {
  width: 40px;
  height: 32px;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  background: none;
}
/* text/number inputs, textareas, and selects share one field style */
.modalCard input[type='text'],
.modalCard textarea,
.modalCard input[type='number'],
.modalCard select {
  width: 100%;
}
.modalCard input[type='text'],
.modalCard textarea,
.modalCard input[type='number'] {
  flex: 1;
} /* inputs stretch in their row; select sizes to content */
.modalCard input[type='file'] {
  width: 100%;
  font: var(--fs-sm) var(--font);
  color: var(--text);
}
.modalCard .row {
  margin-top: 18px;
}
.modalCard .note {
  font-size: var(--fs);
  opacity: 0.7;
  margin-top: 6px;
}
#skyList {
  max-height: 220px;
}
/* only admins curate the skybox library */

/* ===== player panel (top-right) ===== */
#players {
  border-bottom: none;
  border-radius: var(--r-lg) var(--r-lg) 0 0;
}
.prow {
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 2px 5px;
  border-radius: var(--r-sm);
}
.rolebadge {
  margin-left: auto;
  font-size: var(--fs-sm);
  text-transform: uppercase;
  letter-spacing: 0.5px;
  color: var(--accent);
  border: 1px solid var(--accent);
  border-radius: 4px;
  padding: 0 5px;
}

/* Library creation controls: shown to admins, hidden for everyone else.
   admin-wrap uses display:contents so wrapping a block doesn't disturb layout. */
body.not-admin .admin-only {
  display: none !important;
}

/* Room button pulses while a join is pending */
#roomBtn.pulse {
  animation: memberPulse 1.4s ease-in-out infinite;
  border-color: var(--accent);
  color: var(--accent);
}
@keyframes memberPulse {
  0%,
  100% {
    box-shadow: 0 0 0 0 var(--accent-soft);
  }
  50% {
    box-shadow: 0 0 0 4px var(--accent-soft);
    border-color: var(--accent);
  }
}
.prow.turn {
  background: var(--accent-soft);
  font-weight: 600;
}
.dot {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  display: inline-block;
  flex: 0 0 auto;
}
.pav {
  width: 18px;
  height: 18px;
  border-radius: 50%;
  object-fit: cover;
  flex: 0 0 auto;
}

.mav {
  width: 46px;
  height: 46px;
  border-radius: 50%;
  background: var(--avatar) center/cover no-repeat;
  flex: 0 0 auto;
  border: 1px solid var(--border);
}
/* A clickable avatar circle with a little pencil badge, for uploading. */
.editable {
  position: relative;
  cursor: pointer;
}
.editable:hover {
  border-color: var(--accent);
}
.editable::after {
  content: '✎';
  position: absolute;
  right: -5px;
  bottom: -5px;
  width: 20px;
  height: 20px;
  line-height: 20px;
  font-size: 15px;
  text-align: center;
  color: var(--bg);
  background: var(--accent);
  border: 2px solid var(--bg);
  border-radius: 50%;
  box-sizing: content-box;
  pointer-events: none;
}

/* ============================================================
   Shared page chrome + landing / admin layouts
   (consolidated here out of index.html and admin.html)
   ============================================================ */

/* generic helpers reused across pages */
.accent {
  color: var(--accent);
}
.muted {
  opacity: 0.55;
  font-size: var(--fs-sm);
}

/* top bar — shared by the landing and admin pages */
#topbar {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 14px 18px;
  z-index: 10;
}
#topbar .brand {
  display: flex;
  align-items: center;
  gap: 10px;
  text-decoration: none;
  color: var(--text);
}
#topbar .brand img {
  display: block;
  height: 36px;
  width: auto;
}
#topbar .brand .brandMark {
  display: none;
  height: 36px;
}
#topbar .brand .brandSuffix {
  font: 600 15px var(--font);
  color: var(--text);
  opacity: 0.7;
}
#topbar .brand .brandSuffix::before {
  content: '·';
  margin-right: 10px;
  opacity: 0.5;
}
@media (max-width: 560px) {
  #topbar .brand .brandFull {
    display: none;
  }
  #topbar .brand .brandMark {
    display: block;
  }
}
#topbar a:not(.button) {
  color: var(--accent);
  text-decoration: none;
  font-size: var(--fs-sm);
}
.push-right {
  margin-left: auto;
}
body.admin #topbar {
  background: var(--panel);
  border-bottom: 1px solid var(--border);
}

/* ---- landing page (index.html) ---- */
body.landing {
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: auto;
}
#landing {
  width: min(440px, 92vw);
  margin: 9vh auto;
  background: var(--panel-solid);
  border: 1px solid var(--border);
  border-radius: var(--r-xl);
  padding: 26px 26px 22px;
  color: var(--text);
  font: var(--fs)/1.5 var(--font);
}
#landing h1 {
  margin: 0 0 4px;
  font-size: 22px;
}
#landing .tag {
  margin: 0 0 20px;
  color: var(--text);
  opacity: 0.6;
  font-size: var(--fs-sm);
}
#landing h2 {
  font-size: 15px;
  margin: 0 0 10px;
  color: var(--accent);
}
#landing label {
  display: block;
  margin: 12px 0 4px;
  font-size: var(--fs-sm);
  opacity: 0.85;
}
#landing input[type='text'],
#landing input[type='email'],
#landing input[type='password'] {
  width: 100%;
  padding: 9px 11px;
  font: var(--fs) var(--font);
}
#landing .row {
  display: flex;
  gap: 10px;
  align-items: center;
  margin-top: 16px;
}
#landing .row.between {
  justify-content: space-between;
}
#landing form {
  margin: 0;
} /* forms are layout-neutral wrappers here */
#landing .hgroup {
  display: flex;
  align-items: center;
  gap: 10px;
} /* inline button cluster (e.g. Full labels + Log out) */
#landing .uppercase {
  text-transform: uppercase;
} /* room-code fields: display as entered, uppercased */
#landing .grow {
  flex: 1;
} /* an input that shares a .row with a button */
#landing a {
  color: var(--accent);
  cursor: pointer;
  text-decoration: none;
  font-size: var(--fs-sm);
}
#landing .err {
  color: var(--danger);
  font-size: var(--fs-sm);
  min-height: 1.2em;
  margin-top: 8px;
}
#landing .note {
  color: var(--accent);
  font-size: var(--fs-sm);
  min-height: 1.2em;
  margin-top: 8px;
}
#landing .hint {
  font-size: var(--fs-sm);
  opacity: 0.6;
  margin-top: 12px;
}
#landing .divider {
  border: none;
  border-top: 1px solid var(--border);
}
#landing ul {
  list-style: none;
  margin: 8px 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
#landing .roomRow {
  display: flex;
  justify-content: space-between;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  padding: 8px 10px;
}
#landing .who {
  display: flex;
  align-items: center;
  gap: 10px;
}
#landing .avatar {
  width: 74px;
  height: 74px;
  border-radius: 50%;
  background: var(--avatar) center/cover no-repeat;
  border: 1px solid var(--border);
  flex: 0 0 auto;
}
#landing .check {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-top: 12px;
  font-size: var(--fs-sm);
}
#landing .check input {
  width: auto;
}

/* ---- admin console (admin.html) ---- */
body.admin {
  display: block;
  overflow: auto;
}
#admin {
  max-width: 940px;
  margin: 84px auto 48px;
  padding: 0 20px;
  color: var(--text);
  font: var(--fs)/1.5 var(--font);
}
#admin h2 {
  color: var(--accent);
  font-size: 16px;
  margin: 28px 0 10px;
}
#admin table {
  width: 100%;
  border-collapse: collapse;
}
#admin th,
#admin td {
  text-align: left;
  padding: 8px 10px;
  border-bottom: 1px solid var(--border);
  font-size: var(--fs-sm);
  vertical-align: middle;
}
#admin th {
  color: var(--accent);
  font-weight: 600;
}
#admin tr.deleted td {
  opacity: 0.5;
}
#denied {
  text-align: center;
  margin: 22vh auto;
  color: var(--text);
  font: var(--fs)/1.6 var(--font);
}
#denied a {
  color: var(--accent);
  display: block;
  margin-top: 16px;
  text-decoration: none;
}

/* ===== scoreboard + room notes panel ===== */
#scoreTable {
  width: 100%;
  border-collapse: collapse;
}
#scoreTable td {
  padding: 4px;
  border-bottom: 1px solid var(--border);
  font-size: var(--fs-sm);
  vertical-align: middle;
}
.scoreName input {
  box-sizing: border-box;
  font: var(--fs-sm) var(--font);
}
.scoreVal {
  width: 46px;
}
.scoreActs {
  text-align: right;
  white-space: nowrap;
  width: 1%;
}
.scoreEmpty {
  text-align: center;
}
#scoreEdit {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.scoreAddRow input {
  padding: 6px 8px;
  font: var(--fs-sm) var(--font);
}
#scoreClear {
  font-size: var(--fs-sm);
  padding: 3px 8px;
}
/* shared look via .miniLabel group */
#roomNotes {
  width: 100%;
  box-sizing: border-box;
  resize: vertical;
  padding: 8px;
  font: var(--fs-sm) var(--font);
}
#roomNotes:read-only {
  opacity: 0.7;
  background: var(--panel-solid);
  cursor: default;
}

/* Room code — top-right, shown to GM+/owner/admin only (gated in client.js) */
#roomCode {
  border-radius: var(--r);
  padding: 7px 12px;
  font: var(--fs)/1 var(--font);
  letter-spacing: 0.5px;
  cursor: pointer;
  user-select: none;
}
#roomCode:hover {
  border-color: var(--accent);
  color: var(--accent);
}

/* ===== Right rail — players panel on top, Tools menu + its panels below ===== */
#rightStack {
  position: fixed;
  right: var(--inset);
  z-index: 6;
  width: min(270px, 80vw);
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: var(--gap);
}
/* Your Turn pill (UI_Redesign): turn indicator + click-to-advance */
.turnPill {
  flex: 0 1 auto;
  min-width: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  font: var(--fs) var(--font);
  color: var(--accent);
  white-space: nowrap;
  overflow: hidden;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--r);
  padding: 6px calc(9px * var(--ui-elements-scale));
  backdrop-filter: var(--blur);
  cursor: pointer;
}
.turnPill[hidden] {
  display: none;
}
.turnPill:hover {
  border-color: var(--accent);
}
.turnPill.myturn {
  border-color: var(--accent);
  box-shadow:
    inset 0 0 0 1px var(--accent),
    0 0 12px -3px var(--accent);
} /* your turn: emphasized + clearly actionable */
.turnPill:not(.myturn) .ico {
  opacity: 0.45;
} /* chevron dimmed when it is not yours to advance */
#turnMini {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  display: inline;
} /* always show the turn text, even in compact UI / icon-only mode (id beats the .lbl-hiding class rules) */
/* Top-right corner: avatar + static name beside the Your Turn pill */
/* Top-right identity box (image 2): Your Turn + avatar + name in one glassy pill */
#identityBar {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
  background: var(--panel);
  backdrop-filter: var(--blur);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  padding: 5px 8px;
  box-shadow: 0 6px 18px var(--shadow-soft);
}
#identityBar #myAv {
  margin-left: auto;
} /* push avatar + name to the right, turn indicator on the left */
#identityBar .turnPill {
  background: rgba(255, 255, 255, 0.05);
  backdrop-filter: none;
} /* the Your Turn box reads as a control inside the glass, not glass-on-glass */
.myName {
  font: var(--fs-base) var(--text);
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 96px;
}

/* Room Info dock (image 1): one glassy container, header (toggle + owner-derived name + code), titled sections */
#roomInfo {
  background: var(--panel);
  backdrop-filter: var(--blur);
  border: 1px solid var(--border);
  border-radius: var(--r-xl);
  overflow: hidden;
  display: flex;
  flex-direction: column;
  min-height: 0;
}
#roomInfoHead {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 11px;
}
#roomInfoHead #railToggle {
  flex: 0 0 auto;
  padding: 0px;
  background: none;
  border: none;
  color: var(--text);
  cursor: pointer;
  display: inline-flex;
}
#roomInfoHead #railToggle:hover {
  color: var(--accent);
}
/* Name over code, both flush to the same left edge. The column takes the row's slack so a long
   room name ellipsises instead of shoving the code out of the panel. On the sheet layout the code
   is display:none here (it lives in the Room info sheet), so this is the desktop shape only. */
#roomInfoNames {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  flex: 1 1 auto;
  min-width: 0;
}
#roomInfoHead #roomTitle {
  color: var(--accent);
  cursor: pointer;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 100%;
}
#roomInfoHead #roomCode {
  color: var(--muted);
  cursor: pointer;
  white-space: nowrap;
  font-size: var(--fs-sm);
  padding: 2px 0; /* no side padding: the code's text has to start where the name's does */
}
#roomInfoHead #roomCode:hover {
  color: var(--accent);
}
#roomInfoBody {
  display: flex;
  flex-direction: column;
}
.dockSection {
  background: rgba(255, 255, 255, 0.03);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  padding: 8px 10px;
  display: flex;
  flex-direction: column;
  gap: 5px;
}
.dockSection #players {
  display: flex;
  flex-direction: column;
  gap: 4px;
  color: var(--text);
}
.dockSection #roomNotes {
  width: 100%;
  box-sizing: border-box;
  resize: vertical;
  min-height: 60px;
}
#roomInfo.collapsed #roomInfoBody {
  display: none;
} /* collapse to just the header (name + code) */

#toolsDock {
  display: flex;
  flex-direction: column;
  gap: var(--gap);
  width: 100%;
}
/* ===== Left rail — Interactions menu + its panels, bottom-left ===== */
#leftRail {
  position: fixed;
  bottom: var(--inset);
  left: var(--inset);
  z-index: 6;
  width: min(240px, 70vw);
  display: flex;
  flex-direction: column;
  gap: var(--gap);
}
#interactDock {
  display: flex;
  flex-direction: column-reverse;
  gap: var(--gap);
  width: 100%;
}
/* Bottom-left hamburger bar: [🖐️ Interactions] [🔧 Tools]. Emoji-only on touch / narrow screens. */
#hamBar {
  position: fixed;
  left: var(--inset);
  bottom: var(--inset);
  z-index: 8;
  display: flex;
  flex-direction: column-reverse; /* Multi-Select at the bottom; Interactions + Tools stack above */
  align-items: flex-start;
  gap: var(--gap);
}
#hamBarRight {
  position: fixed;
  right: var(--inset);
  bottom: var(--inset);
  z-index: 8;
  display: flex;
  flex-direction: column-reverse;
  align-items: flex-end; /* mirror of #hamBar, aligned to the right edge */
  gap: var(--gap);
}
@media (max-width: 720px), (max-height: 500px), (pointer: coarse) {
  body:not(.ui-full) .lbl {
    display: none;
  }
} /* compact: labels collapse to icons on mobile (full mode keeps them)

/* ===== Phone layout: consistent, reactive UI ============================== */
@media (max-width: 720px), (pointer: coarse) {
  .grp {
    max-height: 70vh;
    overflow-y: auto;
  } /* dropdowns can't run off-screen */
  /* dice fit a compact grid, not a tall column */
  #hand {
    --card-w: calc(clamp(50px, 20vw, 80px) * var(--ui-elements-scale));
  } /* smaller hand cards on phones */
  .panel.popout {
    /* every pop-out opens as one centered modal, not scattered */
    position: fixed !important;
    top: 50% !important;
    left: 50% !important;
    right: auto !important;
    bottom: auto !important;
    transform: translate(-50%, -50%) !important;
    width: min(340px, 92vw) !important;
    max-height: 85vh;
    overflow-y: auto;
    margin: 0 !important;
  }
}

/* ===== sound + credits panels (fixed pop-outs, like Notes/Whiteboard) ===== */
@media (max-width: 720px) {
  /* no room beside the rail — center it over the table instead */
  /* Slimmer player block so the rail is lighter on a phone. */
  .mav {
    width: 36px;
    height: 36px;
  }
  #players,
  #status {
    padding: 6px 8px;
  }
}
/* open on top of the Sound panel, which stays open underneath */
#nowPlaying,
#creditsBody {
  overflow-wrap: anywhere;
}
#nowPlaying,
#tracksLink {
  margin-top: 6px;
} /* were inline styles */
#wbAngle {
  width: 100%;
} /* whiteboard angle slider — was an inline style */
.volRow {
  display: flex;
  align-items: center;
  gap: 8px;
}
.volRow input[type='range'] {
  flex: 1;
  min-width: 0;
}

/* Music modal — fixed top-right, just left of the rail; collapses to its "Music" button */

/* Hand wings — Show (left) / Drop (right) tabs perched on the hand's top corners */
.volMute {
  flex: 0 0 auto;
  background: none;
  border: none;
  padding: 0 2px;
  font-size: var(--fs);
  cursor: pointer;
  line-height: 1;
}

.trackItem {
  display: block;
  width: 100%;
  text-align: left;
  margin: 2px 0;
}
.trackItem.on {
  color: var(--accent);
  font-weight: bold;
}

/* ===== Movable / resizable pop-out panels (desktop) =========================
   .movable  — drag handle affordance on the title bar (added by JS on desktop)
   .floating — popped out of the dock into a free, viewport-fixed position
   .resizable— a native resize grip (every movable panel, once floated)         */
/* Measure just needs to stack + space its rows; padding comes from the modal base. */
/* Reactive kind picker: an auto-fit grid reflows the buttons evenly as the panel
   is resized (4-across when wide, 2×2 when narrow) and each button stretches to
   fill its cell — so they stay uniform AND responsive, no equalize pass needed. */
#measureKinds {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(104px, 1fr));
  gap: 6px;
}
#measureKinds .chip {
  width: auto;
  text-align: center;
}
.panel.movable > .panel-head {
  cursor: move;
  touch-action: none;
  user-select: none;
}
.panel.floating {
  position: fixed !important; /* beat the more-specific `#toolsDock > #x { position: static }` dock rule */
  transform: none !important; /* drop the translateY(-50%) centering — we set an explicit top */
  margin: 0 !important;
  z-index: 40;
}
.panel.resizable {
  resize: both;
  overflow: auto; /* scroll (don't clip) content when a panel is shrunk; keeps the resize grip live */
  min-width: 200px;
  min-height: 120px;
  max-width: 96vw;
  max-height: 92vh;
}
/* ---- Reactive internals: only while floated (.resizable), so the docked layout
   is untouched. Two levers: button rows reflow into an even grid, and each
   list/log panel's main region flexes to fill the resized height. -------------- */

/* Button/chip rows reflow into an even, auto-fitting grid that fills the width. */
.panel.resizable .chipRow {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(96px, 1fr));
}
.panel.resizable .chipRow > button {
  width: auto;
  text-align: center;
}

/* List/log panels: clip the panel and let the primary region scroll + grow with it. */
.panel.resizable#chat,
.panel.resizable#notes {
  overflow: hidden;
}
.panel.resizable#chat #chatLog,
.panel.resizable#notes #notesText {
  flex: 1 1 auto;
  min-height: 0;
  overflow: auto;
  max-height: none;
}
.panel.resizable#notes #notesText {
  resize: none;
} /* the panel grip governs size, not the textarea's own */

@media (pointer: coarse) {
} /* nothing to reset on touch (drag is desktop-only) */

/* Landing / lobby: a sticky (in-flow) top bar so the accent swatches scroll with you but never
   overlap the card, and the card flows normally so you can always scroll up to your name. */
@media (max-width: 720px), (pointer: coarse) {
  body.landing {
    display: block;
  } /* normal scroll flow, not flex-centered */
  #topbar {
    position: sticky;
    top: 0;
    flex-wrap: wrap;
    padding: 10px 12px;
    gap: 8px;
  }
  #accentPicker {
    flex-wrap: wrap;
  }
  #landing {
    margin: 16px auto;
  }
}
/* Tabler SVG icons via an inline <symbol> sprite, referenced by <use>. Stroke inherits currentColor,
   so an icon takes the button's text colour — .danger red, hover accent, disabled, etc. — for free. */
.icon-sprite {
  position: absolute;
  width: 0;
  height: 0;
  overflow: hidden;
}
.ico {
  width: var(--ico-size);
  height: var(--ico-size);
  flex: 0 0 auto;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}
:is(button, .button):has(.ico) {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
}
/* a small connector between two icons (Scale & Grid) */
/* the dice "add" mark: small next to the big number */
[class*='ico-number-'] {
  width: calc(var(--ico-size) * 1.4 / 1.2);
  height: calc(var(--ico-size) * 1.4 / 1.2);
} /* dice number reads large */
#roll .ico,
#selRoll .ico,
#trayRoll .ico {
  transform: rotate(45deg);
} /* tilt "roll" dice so they look like they're rolling */
body:not(.ui-full) :is(button, .button):has(.ico) .lbl {
  display: none;
} /* icon-only: the label lives in the hover hint + aria-label */

/* UI roles that never show text (even in full mode) use the icon-button variant. */
.button--icon .lbl {
  display: none;
}
/* every trash button is red */
.memberRow .button-row--compact .lbl {
  display: inline !important;
} /* member role buttons always show text (icons alone are ambiguous) */
label:has(.ico) {
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.trayDie:has(.ico) {
  gap: 2px;
} /* plus+number reads as "+4" */
.pop-menu.trayDieMenu {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  width: min(300px, 84vw);
  box-sizing: border-box;
}
.trayDieMenu .trayDie {
  width: 100%;
  height: calc(28px * var(--ui-elements-scale));
  min-width: 0;
  min-height: 0;
  aspect-ratio: auto;
  padding: calc(4px * var(--ui-elements-scale)) calc(6px * var(--ui-elements-scale));
  font-size: var(--fs-sm);
  white-space: nowrap;
}
.trayDieMenu .trayDie .ico {
  width: var(--ico-size-sm);
  height: var(--ico-size-sm);
}
.trayDieMenu .trayDie [class*='ico-number-'] {
  width: calc(var(--ico-size-sm) * 1.15);
  height: calc(var(--ico-size-sm) * 1.15);
}

/* Snappy themed hover-hint for icon buttons (see the #tip logic in client.js). */
#tip {
  position: fixed;
  z-index: 1000;
  padding: 4px 9px;
  font: var(--fs-sm) var(--font);
  color: var(--text);
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--r);
  backdrop-filter: var(--blur);
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.35);
  pointer-events: none;
  white-space: nowrap;
}
#tip[hidden] {
  display: none;
}

/* Reusable pop-out groups: a trigger button + a popover menu (dice palette, color swatches, …), so a
   dense toolbar stays short and reveals a grid on demand. Opens upward (bars sit low); a pick inside a
   data-close group closes it. */
.pop-group {
  position: relative;
  display: inline-flex;
}
.pop-menu {
  position: absolute;
  bottom: calc(100% + 6px);
  left: 0;
  z-index: 30;
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  width: max-content;
  max-width: min(320px, 84vw);
  max-height: 60vh;
  overflow: auto;
  padding: 8px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  backdrop-filter: var(--blur);
}
.pop-menu[hidden] {
  display: none;
}
.pop-menu .swatchRow {
  flex-wrap: wrap;
}
#inspectColorRow .pop-menu {
  top: calc(100% + 6px);
  bottom: auto;
} /* inspect sits at the top on desktop → open downward */

/* Inspect + dice-tray toolbars dock to the bottom edge on small screens so they never cover the piece
   or tray in the middle. The dense groups are behind pop-menus now, so the bars themselves stay short. */
@media (max-width: 720px), (pointer: coarse) {
  #deckBrowseActions,
  #drawActions,
  #trayTools,
  #inspectColorRow {
    top: auto !important;
    bottom: var(--inset) !important;
    max-width: 96vw;
  }
  #drawActions {
    flex-wrap: nowrap;
    overflow-x: auto;
    justify-content: flex-start;
  } /* card actions: single scrolling row */
  #inspectColorRow .pop-menu {
    top: auto;
    bottom: calc(100% + 6px);
  } /* bottom-docked → popover opens upward */
}

/* Touch: enlarge tap targets. Scoped to coarse pointers, so the desktop layout is byte-identical. */
@media (pointer: coarse) {
  button,
  .button,
  .chip,
  .stepBtn {
    min-height: 40px;
  }
  .close-x {
    min-width: 40px;
    min-height: 40px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
  }
  .swatch {
    min-width: 24px;
    min-height: 24px;
  }
}

/* ===== Customize Table — streamlined, reactive layout ======================= */
/* The whole body column caps its width and centres in a wide modal, so a wide panel
   gains side margins and the controls stop growing past a comfortable size — rather
   than every field stretching edge-to-edge. (Capping/centring the flex ROWS directly
   makes them size to content and overflow when narrow, so it's done on this wrapper.) */
/* Layout for the two pop-out bodies (Customize Table + Scale & Grid), scoped via .tableBody. */
.secLabel {
  color: var(--accent);
  font-size: var(--fs-sm);
  font-weight: 600;
  margin: 12px 0 4px;
  letter-spacing: 0.3px;
  text-align: center;
}
.tableBody .secLabel:first-of-type {
  margin-top: 2px;
}
/* Rows are tighter than the old modal, and field labels are small + quiet (matching the
   app's compact panels) instead of full-size bright text. */
.tableBody .sizeRow {
  gap: 8px;
  margin: 6px 0;
}
.tableBody .sizeRow > label {
  flex: 0 0 78px;
  width: 78px;
  white-space: nowrap;
  font-size: var(--fs-sm);
  color: var(--text);
  opacity: 0.72;
} /* fixed column so every control starts at the same x */
/* Calibrate row: "Calibrate" label + a compact input + Set, with the prompt tucked under it (aligned to the control column). */
.tableBody .calibRow {
  flex-wrap: wrap;
}
.tableBody .calibRow > input[type='text'] {
  flex: 0 1 96px;
}
.tableBody .calibHint {
  flex: 1 1 100%;
  margin-left: 86px;
  font-size: var(--fs-sm);
}
.tableBody .dimGrid > label {
  opacity: 0.85;
}
/* Steppers keep a legible min width so a − value + trio never collapses, even two to a row. */
.tableBody .sizeRow > .stepper {
  flex: 1 1 auto;
  min-width: 68px;
  max-width: 132px;
}
.tableBody .sizeRow > input[type='text'] {
  flex: 1 1 auto;
  max-width: 180px;
  min-width: 0;
}
.tableBody .sizeRow > #scaleUnits,
.tableBody .sizeRow > #gridStyles,
.tableBody .sizeRow > #gridAnchors {
  flex: 1 1 auto;
  min-width: 0;
}
.tableBody .stepper input[type='number'] {
  flex: 1 1 auto;
  width: auto;
  min-width: 44px;
  text-align: center;
}
.tableBody .sizeRow.calibRow > input[type='text'] {
  flex: 0 0 92px;
  max-width: 92px;
} /* keep Calibrate + Set on one line; the prompt wraps below */
.tableBody .stepBtn {
  padding: 0 6px;
} /* trimmer than the default so two steppers fit a row */
.tableBody .dimGrid .stepper input[type='number'] {
  min-width: 28px;
} /* Width/Depth hold short integers — keep them snug in the slim panel */
.tableBody .sizeRow > .muted {
  flex: 0 0 auto;
} /* unit suffix ("in") hugs its stepper */
/* Width × Depth share one row (label over field, × between). */
.dimGrid {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  gap: 4px 8px;
  align-items: center;
}
.dimGrid > label {
  font-size: var(--fs-sm);
  text-align: center;
}
.dimGrid .dimX {
  align-self: center;
  text-align: center;
  color: var(--text);
  opacity: 0.55;
}
.dimGrid .stepper {
  width: 100%;
}
/* Grid style / snap-anchor chips and the measurement units: one horizontal row whose chips
   share the width equally and shrink to fit (instead of stacking on a narrow panel). */
#scaleUnits,
#gridStyles,
#gridAnchors {
  display: flex;
  flex-wrap: nowrap;
  gap: 6px;
}
#scaleUnits .chip,
#gridStyles .chip,
#gridAnchors .chip {
  flex: 1 1 0;
  min-width: 0;
  text-align: center;
}
#scaleUnits .chip {
  padding: 4px 4px;
  font-size: var(--fs-sm);
} /* the unit buttons a touch smaller — four across */

/* Hand width vs. the bottom-corner controls: reserve less when the hamburgers are icon-only. */
@media (pointer: coarse) {
  :root {
    --hand-side: calc(160px * var(--ui-elements-scale));
  }
} /* scales inherit from :root (same on desktop + coarse) */
@media (max-width: 720px) {
  #hand {
    max-width: 94vw;
  }
}
/* ============================================================================
   Phase 7a — chrome polish.  APPEND to the end of public/styles.css.
   Overrides only: no markup, no JS, no new tokens. Every value comes from the
   existing token set, so the accent picker and the two --ui-*-scale knobs keep
   working. Delete this block to revert.
   Companion: docs/UI_Redesign_Phase7.md · mockups 2a, 3a–3d, 4d
   ========================================================================= */

/* ---- 1 · Clusters own the glass; the buttons inside go flat ---------------
   Today every button is its own glass pill (button { background: var(--panel) }),
   so eleven buttons read as eleven objects. Move the panel treatment up to the
   container and the group becomes one object with items in it — the single
   biggest change in perceived density, and it costs no markup.
   NOTE: #ui holds both rows (nav + .uiRow2), so this makes ONE shell with a
   hairline between the rows. Splitting them into two shells needs a wrapper —
   deferred to 7b, which edits #ui anyway. */
#ui,
#clusterTR,
#hamBar,
#hamBarRight {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--r-xl);
  backdrop-filter: var(--blur);
  padding: calc(4px * var(--ui-elements-scale));
  gap: calc(2px * var(--ui-elements-scale));
}
.uiRow2 {
  gap: calc(2px * var(--ui-elements-scale));
  margin-top: calc(4px * var(--ui-elements-scale));
  padding-top: calc(4px * var(--ui-elements-scale));
  border-top: 1px solid var(--border);
}
#ui > button,
#ui > .button,
#ui .menu > button,
.grp > button,
.uiRow2 > button,
#clusterTR > button,
#hamBar > button,
#hamBarRight > button {
  background: none;
  border-color: transparent;
  backdrop-filter: none;
  border-radius: var(--r);
  padding: calc(7px * var(--ui-elements-scale)) calc(11px * var(--ui-elements-scale));
}
/* Hover becomes a tint rather than a border colour change — a border that only
   appears on hover makes the whole row twitch. */
#ui > button:hover,
#ui > .button:hover,
#ui .menu > button:hover,
.grp > button:hover,
.uiRow2 > button:hover,
#clusterTR > button:hover,
#hamBar > button:hover,
#hamBarRight > button:hover {
  background: var(--accent-soft);
  border-color: transparent;
  color: var(--accent);
}
#ui > button:active,
#ui .menu > button:active,
.grp > button:active,
.uiRow2 > button:active,
#clusterTR > button:active,
#hamBar > button:active,
#hamBarRight > button:active {
  transform: none; /* a 1px nudge inside a fixed shell reads as a glitch */
  background: color-mix(in srgb, var(--accent) 30%, transparent);
}
/* Danger keeps red on hover — the accent tint must not overwrite it. */
#ui .button--danger:hover,
.grp > .button--danger:hover,
#clusterTR .button--danger:hover {
  background: color-mix(in srgb, var(--danger) 18%, transparent);
  color: var(--danger);
}

/* The GM menu is a popover sitting ON the new shell — give it the solid panel so
   it doesn't stack blur on blur. */
.grp {
  background: var(--panel-solid);
}

/* ---- 2 · Group separators inside the top-left cluster --------------------
   .menu is inline-flex, so a border-left divides "app" (Lobby/Settings/How to
   Play) from "content" (GM Controls, Library, Add, Save Scene) with no wrapper. */
#ui > .menu {
  margin-left: calc(4px * var(--ui-elements-scale));
  padding-left: calc(4px * var(--ui-elements-scale));
  border-left: 1px solid var(--border);
}

/* ---- 3 · Icons carry the accent, labels stay neutral --------------------
   .ico sets stroke: currentColor, so giving .ico its own colour tints the glyph
   without touching the label. This is the accent rule: accent = interactive and
   state, neutral = text and surface. */
.ico {
  color: var(--accent);
}
button:hover .ico,
.button:hover .ico {
  color: var(--accent);
}
.button--danger .ico,
.chip.danger .ico {
  color: var(--danger);
}
button:disabled .ico {
  color: inherit;
}

/* ---- 4 · Keyboard focus ------------------------------------------------
   There is currently no :focus-visible rule anywhere in this file, so keyboard
   focus falls back to the browser default ring over a dark glass panel. */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.libCard.sel:focus-visible {
  outline-offset: -1px; /* keep the existing selected-card inset */
}

/* ---- 5 · Accent text below 12px ---------------------------------------
   `b { color: var(--accent) }` and `.dockLabel` put accent on text as small as
   11px (--fs-sm), where the gold-on-glass pair misses contrast. Accent text is
   fine at >=12px; below that, use weight and opacity for hierarchy instead. */
.muted b,
.note b,
.da-note b,
figcaption b,
.libMeta b,
.hint b {
  color: var(--text);
}
.dockLabel {
  color: var(--text);
  opacity: 0.5;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

/* ---- 6 · Room Info dock rhythm ----------------------------------------
   The dock's three sections (Players / Room notes / Members) currently sit at
   the same spacing as the rows inside them, so the groups don't read as groups. */
#roomInfoBody {
  gap: calc(13px * var(--ui-elements-scale));
  padding: calc(2px * var(--ui-elements-scale)) calc(11px * var(--ui-elements-scale))
    calc(11px * var(--ui-elements-scale));
}
#roomInfoBody .dockSection {
  display: flex;
  flex-direction: column;
}

/* ---- 7 · One chip grammar ---------------------------------------------
   Chips are the app's second button system (library tabs, measure kinds, drop
   options, toggles). Same hover tint as the clustered buttons so a chip and a
   toolbar button don't behave differently. */
.chip:hover {
  background: var(--accent-soft);
  border-color: var(--accent);
}
.chip.on .ico {
  color: var(--accent);
}

/* ---- 8 · Pull .libControls out of the chip soup ------------------------
   wireControls injects Select + Spawn selected + search directly after the tab
   strip, where it reads as a fourth row of chips. One rule separates the
   "acting on this list" row from the "choosing a list" rows. (Mockup 6a.) */
.libControls {
  margin: calc(6px * var(--ui-elements-scale)) 0 0;
  padding-top: calc(6px * var(--ui-elements-scale));
  border-top: 1px solid var(--border);
}

/* ---- 9 · Compact mode: separators do the work labels did ---------------
   With .lbl hidden the buttons in a cluster become a run of identical squares.
   Squaring them up and keeping the group divider visible is what makes the
   run scannable. (Mockup 4d.) */
@media (max-width: 720px), (max-height: 500px), (pointer: coarse) {
  body:not(.ui-full) :is(#ui, .uiRow2, #clusterTR, #hamBar, #hamBarRight) > button {
    padding: calc(8px * var(--ui-elements-scale));
    aspect-ratio: 1;
    justify-content: center;
  }
}

/* ============================================================================
   Check after applying
   - Every accent value in #accentPicker (esp. #d14b4b and #5b8ad6): hover tints
     and the focus ring should still read on the dark glass.
   - Both --ui-elements-scale extremes: the new cluster padding is scaled, so a
     large setting shouldn't collide the top-left shell with #inspectHint.
   - #ui wrapping at narrow widths — it is flex-wrap: wrap inside the new shell.
   - The GM menu (.grp) opening over the shell: solid panel, no double blur.
   - Compact mode on a phone: rule 9 squares the buttons; confirm the seat
     clusters still clear the hand row.
   - .grp menu items are flattened too (they sit inside .menu, one level deeper
     than #ui's children) — check Reset still reads red on hover.
   - Emoji-labelled controls (#wbPen, #wbEraser, .volMute) have no .ico, so rule
     3 never reaches them: they stay emoji while everything else is Tabler.
   - #inspectHint / #inspectColorRow are fixed at top: var(--inset), centred —
     check they clear the now-taller top-left shell at a large elements scale.
   ========================================================================= */

/* ============================================================================
   Phase 7b — top-left fold.  Applies on top of the 7a block above.
   Companion: docs/phase7b.md · mockup 2a
   ========================================================================= */

/* Mirror of the .admin-only rule above (see body.not-admin) for GM-gated items,
   so each menu item is gated by its own rule rather than by which menu it sits in. */
body.not-gm .gm-only {
  display: none;
}

/* Group label inside a .grp menu — same treatment as .dockLabel after 7a. */
.grpLabel {
  padding: calc(6px * var(--ui-elements-scale)) calc(12px * var(--ui-elements-scale))
    calc(3px * var(--ui-elements-scale));
  font-size: var(--fs-sm);
  color: var(--text);
  opacity: 0.5;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}
/* A rule above the label so "Admin" reads as a section, not a stray line.
   :not(:first-child) keeps it clean when the GM items are hidden for a non-GM admin. */
.grpLabel:not(:first-child) {
  margin-top: calc(4px * var(--ui-elements-scale));
  border-top: 1px solid var(--border);
}
/* Reset sits last and needs the same separation. */
#roomGrp #roomReset {
  position: relative;
  margin-top: calc(8px * var(--ui-elements-scale));
}
#roomGrp #roomReset::before {
  content: '';
  position: absolute;
  top: calc(-5px * var(--ui-elements-scale));
  left: 0;
  right: 0;
  height: 1px;
  background: var(--border);
  pointer-events: none;
}
/* The overflow trigger is an icon button, not a labelled one. */
#moreBtn .lbl {
  display: none;
}
#moreGrp {
  min-width: calc(150px * var(--ui-elements-scale));
}

/* ============================================================================
   Phase 7c (slices 1–2) — source badges + curation overflow.
   Companion: docs/UI_Redesign_Phase7.md · mockups 6a, 6b, 7b
   ========================================================================= */

/* ---- source + visibility badges ---------------------------------------- */
.libBadges {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  flex: 0 0 auto;
}
.libBadge.src {
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
.libBadge.src.bi {
  background: var(--field); /* quiet: built-in is the default case */
  color: var(--text);
  opacity: 0.65;
  border-color: transparent;
}
.libBadge.src.cu {
  background: var(--accent-soft);
  color: var(--accent);
  border-color: transparent;
}

/* ---- curation overflow: desktop popover -------------------------------- */
.overflowGroup {
  position: relative;
  display: inline-flex;
}
.overflowMenu {
  right: 0;
  left: auto;
  z-index: 60; /* placed as a fixed layer (see overflowMenu in icons.js) — stay above the card rows */
  min-width: 176px;
  flex-direction: column;
  gap: 1px;
  padding: 4px;
}
/* Desktop popover rows are a dense menu, not the touch sheet's 44px targets:
   .sheetItem inherits the app's button padding + .ico at --ico-size (a toolbar
   size), which made the portaled menu read as a panel. */
.overflowMenu .sheetItem {
  padding: 5px 8px;
  font-size: var(--fs-sm);
  min-height: 0;
  min-width: 0;
  border-radius: var(--r-sm);
}
.overflowMenu .sheetItem .ico {
  --ico-size: 1.1em;
  width: 1.1em;
  height: 1.1em;
  flex: 0 0 auto;
}
.overflowMenu .sheetItem {
  width: 100%;
  justify-content: flex-start;
  gap: 8px;
  background: none;
  border-color: transparent;
  backdrop-filter: none;
  text-align: left;
}
.overflowMenu .sheetItem:hover {
  background: var(--accent-soft);
  color: var(--accent);
}
.overflowMenu .sheetItem.danger:hover {
  background: color-mix(in srgb, var(--danger) 18%, transparent);
  color: var(--danger);
}
.overflowMenu .sheetNote {
  font-size: 0.9em;
}
.sheetNote {
  margin-left: auto;
  font-size: var(--fs-sm);
  opacity: 0.5;
}

/* ---- curation overflow: touch sheet ------------------------------------
   Lives inside #libraryModal, so the MutationObserver in client.js (~5216) icons
   its rows, and closing the modal takes the sheet with it. */
.sheet-backdrop {
  position: absolute;
  inset: 0;
  z-index: 30;
  display: flex;
  align-items: flex-end;
  background: var(--overlay);
}
.sheet {
  width: 100%;
  max-height: 80%;
  overflow: auto;
  padding: 0 8px 12px;
  background: var(--panel-solid);
  border-top: 1px solid var(--border);
  border-radius: var(--r-xl) var(--r-xl) 0 0;
  box-shadow: 0 -18px 44px rgba(0, 0, 0, 0.5);
}
.sheetGrab {
  width: 38px;
  height: 4px;
  margin: 9px auto 3px;
  border-radius: 2px;
  background: var(--border);
}
.sheetHead {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 8px 12px 12px;
}
.sheetHead b {
  color: var(--text); /* the asset name is not an accent-worthy interactive thing */
}
.sheetHead span {
  font-size: var(--fs-sm);
  opacity: 0.5;
}
.sheet .sheetItem {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  min-height: 48px; /* thumb target floor */
  justify-content: flex-start;
  background: none;
  border-color: transparent;
  backdrop-filter: none;
  text-align: left;
}
.sheet .sheetItem:hover {
  background: var(--accent-soft);
}
.sheetDanger {
  margin-top: 6px;
  padding-top: 6px;
  border-top: 1px solid var(--border);
}
.sheetDanger .sheetItem,
.sheetDanger .sheetItem .ico {
  color: var(--danger);
}
.sheetConfirmNote,
.sheetConfirmActions {
  display: none;
}
.sheetDanger.confirming {
  margin: 6px 4px 0;
  padding: 10px 8px;
  border: 1px solid var(--danger-border);
  border-radius: var(--r);
  background: color-mix(in srgb, var(--danger) 10%, transparent);
}
.sheetDanger.confirming .sheetItem {
  pointer-events: none; /* it becomes the confirm's heading */
}
.sheetDanger.confirming .sheetConfirmNote {
  display: block;
  padding: 2px 4px 10px;
  font-size: var(--fs-sm);
  opacity: 0.75;
}
.sheetDanger.confirming .sheetConfirmActions {
  display: flex;
  gap: 8px;
}
.sheetConfirmActions button {
  flex: 1;
  min-height: 44px;
  justify-content: center;
}

/* ============================================================================
   Phase 7c (slices 3–4) — cross-pane search + Amount stepper.
   Companion: docs/UI_Redesign_Phase7.md · mockups 7c, 7d
   ========================================================================= */

/* ---- search field: clear button ---------------------------------------- */
.libSearchWrap {
  position: relative;
  display: flex;
  align-items: center;
}
.libSearchClear {
  position: absolute;
  right: 4px;
  padding: 4px;
  background: none;
  border-color: transparent;
  backdrop-filter: none;
  opacity: 0.6;
}
.libSearchClear:hover {
  opacity: 1;
  border-color: transparent;
  background: var(--accent-soft);
}
.libSearch {
  padding-right: calc(28px * var(--ui-elements-scale)); /* room for the clear button */
}

/* ---- per-tab hit counts ------------------------------------------------- */
.tabCount {
  margin-left: 6px;
  font-size: var(--fs-sm);
  color: var(--accent);
}
.tabCount:empty {
  display: none;
}
.libTab.noHits {
  opacity: 0.4;
} /* a tab with no matches stays in place rather than disappearing */

/* ---- search summary + result group headings ----------------------------- */
.libSummary {
  padding: 2px 2px 6px;
  font-size: var(--fs-sm);
  opacity: 0.6;
}
/* Stack the revealed panes as sections instead of one tall column of one list. */
.searching .libPane {
  padding-bottom: calc(4px * var(--ui-elements-scale));
}

/* ---- Amount stepper (dispensers) ---------------------------------------
   Two steppers on one card need telling apart: quantity stays bare, Amount keeps a
   tinted cap label. .stepCap already exists — this gives it the accent treatment. */
.stepCap {
  align-self: stretch;
  display: flex;
  align-items: center;
  padding: 0 8px;
  background: var(--accent-soft);
  color: var(--accent);
  font-size: var(--fs-sm);
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.stepper:has(.stepCap) {
  box-shadow: inset 0 0 0 1px var(--accent);
  overflow: hidden;
}
.infiniteNote {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 9px;
  border-radius: var(--r-sm);
  background: var(--field);
  font-size: var(--fs-sm);
  opacity: 0.7;
}
.infiniteNote .ico {
  color: var(--accent);
}

/* ============================================================================
   Phase 7f — lobby + admin.
   Companion: docs/UI_Redesign_Phase7.md · mockups 4a, 4b, 4c
   ========================================================================= */

/* ---- lobby: three cards instead of one divider stack -------------------- */
#landing .lcard {
  margin: 0 0 12px;
  padding: 14px 16px;
  background: var(--field);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
}
#landing .lcard h2 {
  margin: 0 0 4px;
} /* the card edge does the separating the h2's margin used to */
#landing .lcard > form > .row:last-child,
#landing .lcard > .row:last-child {
  margin-bottom: 0;
}
/* The remaining divider (below the signed-in header) is the only one left. */
#landing .divider {
  margin: 16px 0;
}

/* ---- lobby: room rows --------------------------------------------------- */
#landing .roomRow {
  gap: 10px;
}
#landing .roomInfoCol {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
  flex: 1;
}
#landing .roomMeta {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: var(--fs-sm);
}
#landing .roomMeta code {
  font-family: ui-monospace, monospace;
  letter-spacing: 0.06em;
  opacity: 0.85;
}
/* Pending: a live pulse, because a lobby socket really is open on this row. */
#landing .roomWaiting {
  display: flex;
  align-items: center;
  gap: 7px;
  font-size: var(--fs-sm);
  color: var(--accent);
}
#landing .roomWaiting i {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--accent);
  animation: roomPulse 1.6s ease-in-out infinite;
}
@keyframes roomPulse {
  0%,
  100% {
    opacity: 0.35;
    box-shadow: 0 0 0 0 var(--accent-soft);
  }
  50% {
    opacity: 1;
    box-shadow: 0 0 0 4px var(--accent-soft);
  }
}
#landing .roomRow {
  align-items: center;
}
#landing .roomRow .button-row--compact {
  flex: 0 0 auto;
  flex-wrap: nowrap;
  align-items: center;
  gap: 4px;
}
/* The compact action-row button carries
   min-width: 4.5em (line ~129) for labelled buttons, which an icon-only button
   inherits — three of them overflowed the row and pushed Enter over the text.
   An icon button is sized by its icon, everywhere. */
.button-row--compact .button--icon,
.button-row--compact .overflowTrigger {
  min-width: 0;
  width: calc(30px * var(--ui-elements-scale));
  height: calc(30px * var(--ui-elements-scale));
  padding: 0;
  justify-content: center;
}
/* A sheet opened outside a modal host (the lobby) covers the viewport. */
.sheet-backdrop.fixed {
  position: fixed;
}

/* ---- admin: themed tables ---------------------------------------------- */
#admin table {
  font-size: var(--fs);
}
#admin thead th {
  font-size: var(--fs-sm);
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text);
  opacity: 0.55; /* a column header is not an interactive accent thing */
}
#admin tbody tr:hover {
  background: color-mix(in srgb, var(--text) 4%, transparent);
}
#admin td .button-row--compact {
  justify-content: flex-end;
  gap: 4px;
}
/* Pending count as a badge rather than muted text in the heading. */
#pendingBadge:not(:empty) {
  display: inline-flex;
  align-items: center;
  margin-left: 8px;
  padding: 2px 8px;
  border-radius: 999px;
  background: var(--accent-soft);
  color: var(--accent);
  font-size: var(--fs-sm);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  opacity: 1;
}

/* ---- admin: storage maintenance cards --------------------------------- */
#cleanup,
#textureCache {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  padding: 12px 14px;
  background: var(--field);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
}
#textureCache {
  margin-top: 10px;
}
#cleanupResult,
#textureCacheResult {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: var(--fs-sm);
}
#cleanup .cleanupNote,
#textureCache .cleanupNote {
  flex: 1 0 100%;
  margin: 0;
  font-size: var(--fs-sm);
  opacity: 0.55;
  order: 9; /* the controls read first; the explanation sits under them */
}

/* ---- 7f follow-up: bring the lobby closer to mockups 4a / 4b ------------ */

/* The signed-in header: an identity line, not a hero. The 74px avatar is the
   editor's own affordance elsewhere; here it is a 34px chip. */
#landing .who .avatar {
  width: calc(30px * var(--ui-elements-scale));
  height: calc(30px * var(--ui-elements-scale));
  border-radius: var(--r);
}
#landing .row.between {
  align-items: center;
  gap: 10px;
}
#landing .who {
  min-width: 0;
  font-size: var(--fs-sm);
}
/* Default UI / Log out are secondary — chip-sized, and never wrapping mid-label. */
#topbar .landing-account-actions {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 10px;
}
#landing .account-row {
  flex-wrap: wrap;
}
#landing .account-row .who {
  flex: 1 1 160px;
}
#landing .account-row .who > span:last-child {
  min-width: 0;
  overflow-wrap: anywhere;
}
#landing .account-row .hgroup {
  flex-wrap: wrap;
  flex-shrink: 0;
  max-width: 100%;
  margin-left: auto;
}
#landing .hgroup button {
  min-width: 0;
  padding: 6px 10px;
  font-size: var(--fs-sm);
  white-space: nowrap;
}
#landing .hgroup .ico {
  width: var(--ico-size-sm);
  height: var(--ico-size-sm);
}

/* Card internals: tighter label→field rhythm, and the primary action on the
   field's row rather than floating below it. */
#landing .lcard label {
  margin: 10px 0 4px;
  font-size: var(--fs-sm);
}
#landing .lcard label:first-of-type {
  margin-top: 0;
}
#landing .lcard .row {
  margin-top: 12px;
  justify-content: flex-end;
}
#landing .lcard .check {
  margin-top: 10px;
}
#landing .lcard .check .chip {
  font-size: var(--fs-sm);
}
#landing .button--primary {
  min-width: 0;
}

/* Quick join (4b): one full-width primary; Log in lives in the top bar. */
#quickJoinView h1 {
  font-size: 24px;
}
#quickJoinView .tag {
  margin-bottom: 14px;
}
#quickJoinView label {
  margin: 12px 0 4px;
  font-size: var(--fs-sm);
}
#quickJoinView .row {
  margin-top: 18px;
}
#quickJoinView #qjBtn {
  flex: 1;
  justify-content: center;
  padding: 10px 14px;
}
#quickJoinView .hint {
  margin-top: 14px;
  text-align: center;
}
#topbar .topbarBtn {
  padding: 6px 10px;
  font-size: var(--fs-sm);
  white-space: nowrap;
}

/* The room row's ••• matches Enter exactly: same padding and font metrics, and
   align-self: stretch pins its height to the row's tallest item (Enter) rather
   than to an icon-derived square. Library cards keep the compact square. */
#landing .roomRow .button-row--compact .overflowTrigger {
  align-self: stretch;
  width: auto;
  height: auto;
  padding: 4px 9px;
  min-width: calc(30px * var(--ui-elements-scale));
}

/* ============================================================================
   Library cards → the 6a layout: a horizontal row per asset instead of a tall
   tile. Preview left, name + badges, controls, actions — so card heights match
   regardless of name length, and two fit per row at a readable size.
   ========================================================================= */

/* The old source badge was a ::after, and only in All view — the real .libBadge
   from 7c covers every view, so retire the pseudo one (it was doubling up). */
#libraryModal.src-all .lib2-bi > .libCard::after,
#libraryModal.src-all .lib2-cu > .libCard::after {
  content: none;
}

.libList {
  grid-template-columns: repeat(auto-fill, minmax(330px, 1fr));
  gap: 10px;
}
.libCard {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  grid-template-areas:
    'prev meta'
    'prev ctrls'
    'prev acts';
  align-content: start;
  column-gap: 10px;
  row-gap: 6px;
  padding: 10px;
}
.libCard .libPreview {
  grid-area: prev;
  width: calc(66px * var(--ui-elements-scale));
  height: calc(66px * var(--ui-elements-scale));
  align-self: start;
}
.libPreview.empty {
  font-size: 24px;
}
.libCard .libMeta {
  grid-area: meta;
  align-items: center;
  flex-wrap: wrap;
  row-gap: 4px;
}
.libCard .libName {
  min-width: 0;
  flex: 1 1 auto;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap; /* equal card heights beat showing a 40-character name in full */
}
.libCard .cardCtrls {
  grid-area: ctrls;
  margin: 0;
  gap: 5px;
}
.libCard .button-row--compact {
  grid-area: acts;
  gap: 5px;
}
.libCard .button-row--compact button {
  min-width: 0;
  padding: 5px 10px;
}
/* Snap / Stand / Own colors: the icon already carries the state with .on — the
   extra .chk square made three controls read as a form. */
.cardCtrls .chip.chk::before {
  display: none;
}
.cardCtrls .chip {
  padding: 4px 8px;
}
.cardCtrls .qtyStep input[type='number'] {
  width: 5ch;
  min-width: 42px;
}

/* ============================================================================
   Library modal → the 6a window, in this app's tokens.
   Shell, sticky header, source segmented control, chip tabs, card surfaces.
   ========================================================================= */

/* ---- the window ---------------------------------------------------------- */
#libraryModal .modalCard.libraryCard.lib2Card {
  width: min(1180px, calc(100vw - 80px));
  max-height: min(84vh, 900px);
  border-radius: var(--r-xl);
  border: 1px solid var(--border);
  box-shadow:
    0 0 0 1px rgba(0, 0, 0, 0.4),
    0 24px 60px rgba(0, 0, 0, 0.65); /* a floating window, not a full-bleed sheet */
  gap: 0;
  overflow: hidden; /* the panes scroll, the shell does not */
}

/* ---- sticky header: title row · tabs · controls -------------------------- */

/* ---- source: three chips → one segmented control ------------------------ */
#lib2Source {
  display: inline-flex;
  gap: 0;
  border: 1px solid var(--border);
  border-radius: var(--r);
  overflow: hidden;
}
#lib2Source .chip {
  border: none;
  border-radius: 0;
  background: none;
  padding: 5px 12px;
  font-size: var(--fs-sm);
  opacity: 0.65;
}
#lib2Source .chip + .chip {
  border-left: 1px solid var(--border);
}
#lib2Source .chip:hover {
  background: var(--accent-soft);
  opacity: 1;
}
#lib2Source .chip.on {
  background: var(--accent-soft);
  color: var(--accent);
  opacity: 1;
  box-shadow: inset 0 0 0 1px var(--accent);
}

/* ---- tabs: pill chips with counts, no underline rail -------------------- */

/* ---- controls row ------------------------------------------------------- */
#libraryModal .libControls {
  margin: 0;
  padding-top: 10px;
}
#libraryModal .libSearch {
  border-radius: var(--r);
}

/* ---- card surfaces: quiet fill + hairline, accent ring when selected ---- */
#libraryModal .libCard {
  background: color-mix(in srgb, var(--text) 4%, transparent);
  border-color: color-mix(in srgb, var(--text) 9%, transparent);
  border-radius: var(--r-lg);
}
#libraryModal .libCard:hover {
  border-color: color-mix(in srgb, var(--text) 18%, transparent);
}
#libraryModal .libCard.sel {
  background: var(--accent-soft);
  border-color: var(--accent);
  outline: none; /* the fill + border carry it; the 2px outline doubled up */
}
#libraryModal .libPreview {
  background: color-mix(in srgb, var(--text) 5%, transparent);
  border: 1px solid color-mix(in srgb, var(--text) 8%, transparent);
}
/* Both source lists in one visual grid under All — no divider between feeds. */
#libraryModal .lib2-cu:not(:empty) {
  border-top: none;
  margin-top: 0;
  padding-top: 0;
}

/* ============================================================================
   Card controls at mockup scale (6a). The oversized Snap/Stand came from .ico
   inheriting --ico-size (~22px, tuned for toolbar buttons) inside what is meant
   to be a dense chip row — so the chips wrapped onto three lines. Icons stay,
   just at label size.
   ========================================================================= */
#libraryModal .cardCtrls {
  align-items: center;
  gap: 5px;
}
#libraryModal .cardCtrls .chip {
  gap: 5px;
  padding: 4px 8px;
  border-radius: var(--r-sm);
  line-height: 1.2;
}
#libraryModal .cardCtrls .chip .ico,
#libraryModal .libCard .button-row--compact .ico {
  width: var(--ico-size-sm);
  height: var(--ico-size-sm);
}
/* Stepper: same height as the chips beside it, tighter than the toolbar default. */
#libraryModal .cardCtrls .stepper {
  border-radius: var(--r-sm);
}
#libraryModal .cardCtrls .stepBtn {
  padding: 2px 7px;
  font-size: var(--fs-sm);
}
#libraryModal .cardCtrls .qtyStep input[type='number'] {
  width: 5ch;
  min-width: 42px;
  font-size: var(--fs-sm);
}
#libraryModal .cardCtrls .stepCap {
  font-size: var(--fs-sm);
  padding: 0 6px;
}
/* Swatches: a compact run, so the whole ctrls row fits one line. */
#libraryModal .cardCtrls .swatchRow {
  gap: 3px;
}
#libraryModal .cardCtrls .swatch {
  width: 15px;
  height: 15px;
  border-radius: 4px;
}
#libraryModal .cardCtrls .swatch.on {
  outline-width: 1px;
  outline-offset: 1px;
}
/* Library card colour swatches — inline where there is room, behind a trigger where
   there is not. The menu stays in normal flow rather than floating: .libPane scrolls,
   and an absolutely positioned popover would be clipped at the pane edge. */
#libraryModal .cardCtrls .swatchPop > .pop-menu {
  position: static;
  display: flex;
  width: auto;
  max-width: none;
  max-height: none;
  overflow: visible;
  padding: 0;
  background: none;
  border: 0;
  border-radius: 0;
  backdrop-filter: none;
}
/* The trigger owns the row at every size: the swatch run drops onto its own line
   beneath the controls when opened. display:contents lets the menu become a flex child
   of .cardCtrls so it can take a full-width line without the group boxing it in. */
#libraryModal .cardCtrls {
  flex-wrap: wrap;
}
#libraryModal .cardCtrls .swatchPop {
  display: contents;
}
#libraryModal .cardCtrls .swatchPop[hidden] {
  display: none;
}
#libraryModal .cardCtrls .swatchPop > .pop-menu {
  flex: 1 0 100%;
  margin-top: 4px;
}
#libraryModal .cardCtrls .swatchPop > .pop-trigger {
  padding: 2px 6px;
}

/* Spawn / Edit: one compact row, not a stack of full-width buttons. */
#libraryModal .libCard .button-row--compact button {
  padding: 4px 10px;
  font-size: var(--fs-sm);
}
/* Two-up at this window width, so a card's controls have room for one line. */
#libraryModal .libList {
  grid-template-columns: repeat(auto-fill, minmax(390px, 1fr));
}

/* ============================================================================
   Phase 7h — table HUD to mockup 2a.
   Two top-left pills, the top-right tools cluster stacked above the identity
   box in one right column, and a dock dense enough to read as three groups.
   ========================================================================= */

/* ---- top-left: two pills, each sized to its own content ----------------- */
#ui {
  background: none;
  border: 0;
  padding: 0;
  backdrop-filter: none;
  flex-direction: column;
  align-items: flex-start;
  width: max-content;
  gap: var(--gap);
}
#ui .uiRow1,
#ui .uiRow2 {
  display: flex;
  align-items: center;
  width: max-content;
  gap: calc(2px * var(--ui-elements-scale));
  padding: calc(4px * var(--ui-elements-scale));
  margin: 0;
  background: var(--panel);
  border: 1px solid var(--border);
  border-top: 1px solid var(--border); /* undo 7a's inter-row hairline */
  border-radius: var(--r-xl);
  backdrop-filter: var(--blur);
}
#ui .uiRow1 > button,
#ui .uiRow1 > .menu > button {
  background: none;
  border-color: transparent;
  backdrop-filter: none;
  border-radius: var(--r);
  padding: calc(7px * var(--ui-elements-scale)) calc(11px * var(--ui-elements-scale));
}
#ui .uiRow1 > button:hover,
#ui .uiRow1 > .menu > button:hover {
  background: var(--accent-soft);
  color: var(--accent);
}
#ui .uiRow1 > .menu {
  margin-left: calc(4px * var(--ui-elements-scale));
  padding-left: calc(4px * var(--ui-elements-scale));
  border-left: 1px solid var(--border);
}
#ui .uiRow1 > .menu + .menu {
  margin-left: 0;
  padding-left: 0;
  border-left: 0; /* GM Controls and ••• are one group */
}

/* ---- top-right: one column — tools, identity, dock ---------------------- */
:root {
  --tr-cluster-h: calc(42px * var(--ui-elements-scale));
}
.cluster-tr {
  top: var(--inset);
  right: var(--inset);
  gap: calc(2px * var(--ui-elements-scale));
}
#rightStack {
  top: calc(var(--inset) + var(--tr-cluster-h) + var(--gap));
  max-height: calc(100vh - 2 * var(--inset) - var(--tr-cluster-h) - var(--gap));
}
/* Score / Music / Measure go icon-only (their labels live in the #tip); Timer
   keeps its text because that text is the live value. */
#scoreBtn .lbl,
#audioBtn .lbl,
#measureBtn .lbl,
#fogBtn .lbl {
  display: none;
}

.pane[data-pane='fog'] {
  font: var(--fs)/1.5 var(--font);
}

.pane[data-pane='fog'] .chipRow {
  margin-block: var(--gap);
}

#rightStack:has(#regionTR:not([hidden]) .pane[data-pane='fog'].on) {
  z-index: 9; /* above the fixed selection/seat buttons while the sheet is open */
}

@media (pointer: coarse) {
  .pane[data-pane='fog'] button,
  .pane[data-pane='fog'] input:not([type='checkbox']),
  .pane[data-pane='fog'] select {
    min-height: 44px;
  }
}

.pane[data-pane='fog'] .fogField {
  display: grid;
  gap: var(--gap);
  margin-block: var(--gap);
}

.pane[data-pane='fog'] .fogField > input,
.pane[data-pane='fog'] .fogField > select {
  width: 100%;
  min-width: 0;
}

.fogThicknessLabels {
  display: flex;
  justify-content: space-between;
  gap: var(--gap);
}

.fogEnable {
  display: flex;
  align-items: center;
  gap: var(--gap);
  min-height: 44px;
}

canvas.fog-painting {
  cursor: crosshair;
}
#clusterTR > button {
  padding: calc(7px * var(--ui-elements-scale));
}
#timerBtn {
  padding-inline: calc(10px * var(--ui-elements-scale));
}
#timerBtn .lbl {
  display: inline;
}

/* ---- the dock: three legible groups ------------------------------------- */
/* Players: one line per player — name, score, role tag. */
#players {
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 3px;
}
#players > * {
  display: flex;
  align-items: center;
  gap: 7px;
  min-width: 0;
  padding: 5px 7px;
  border-radius: var(--r-sm);
  font-size: var(--fs-sm);
  white-space: nowrap;
}
#players > * > span:first-child {
  overflow: hidden;
  text-overflow: ellipsis;
}
#players .rolebadge {
  margin-left: auto;
}
.prow[draggable='true'] {
  cursor: grab;
}
.prow.dragging {
  opacity: 0.45;
}
.turnOrderControls {
  display: flex;
  gap: 2px;
  margin-left: auto;
}
.rolebadge + .turnOrderControls {
  margin-left: 0;
}
.turnOrderControls button {
  min-width: 25px;
  min-height: 25px;
  padding: 0 5px;
  background: none;
  border-color: transparent;
}
.turnOrderControls button:hover:not(:disabled) {
  color: var(--accent);
  border-color: var(--accent);
}

/* Members: the Helper / GM / Kick row was wrapping one button per line because
   compact actions carried a 4.5em minimum plus toolbar-sized icons. */
#memberList,
.unclaimed {
  gap: 5px;
}
#memberList > li,
.memberRow {
  border-radius: var(--r-sm);
  font-size: var(--fs-sm);
}
#memberList button,
.memberRow button,
.unclaimed-row button {
  min-width: 0;
  padding: 3px 8px;
  font-size: var(--fs-sm);
  white-space: nowrap;
}
#memberList .ico,
.memberRow .ico,
.unclaimed-row .ico {
  width: var(--ico-size-sm);
  height: var(--ico-size-sm);
}

/* ============================================================================
   Dock interior → 2a: groups inside one glass dock, not boxes inside a box.
   ========================================================================= */
#roomInfoBody .dockSection {
  background: none;
  border: 0;
  border-radius: 0;
  padding: 0;
  gap: calc(5px * var(--ui-elements-scale));
}
/* The label is the group's separator now, so give it a hairline above — except
   the first, which sits under the dock header. */
#roomInfoBody .dockSection + .dockSection {
  padding-top: calc(11px * var(--ui-elements-scale));
  border-top: 1px solid var(--border);
}

/* Player rows: only "you" gets a surface; the rest are plain lines. */
#players > * {
  background: none;
  border: 0;
}
#players > .me,
#players > *[class*='self'] {
  background: color-mix(in srgb, var(--text) 5%, transparent);
}

/* Member identity keeps its own row; moderation actions must fit the narrow dock. */
#memberList > li,
.memberRow {
  flex-direction: column;
  align-items: stretch;
  min-width: 0;
  gap: 8px;
  padding: 8px;
  background: color-mix(in srgb, var(--text) 4%, transparent);
  border-color: color-mix(in srgb, var(--text) 8%, transparent);
}
.memberIdentity {
  display: flex;
  flex-direction: column;
  min-width: 0;
  gap: 2px;
  overflow-wrap: anywhere;
}
.memberName {
  font-size: var(--fs);
  font-weight: 600;
}
#memberList .button-row--compact,
.memberRow .button-row--compact {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 3px;
  width: 100%;
}
/* Keep explicit labels in both compact and full UI modes. */
#memberList .button-row--compact .ico,
.memberRow .button-row--compact .ico {
  display: none;
}
#memberList .button-row--compact button,
.memberRow .button-row--compact button {
  min-width: 0;
  min-height: 15px;
  padding: 0 4px;
  line-height: 1.2;
  font-size: var(--fs-sm);
  white-space: normal;
  overflow-wrap: anywhere;
}
@media (pointer: coarse) {
  #memberList .button-row--compact button,
  .memberRow .button-row--compact button {
    min-height: 22px;
  }
}

/* ============================================================================
   2a, last two details: the identity pill stands alone, and the tools cluster
   is only as wide as its buttons.
   ========================================================================= */

/* #identityBar is now a sibling of #roomInfo in #rightStack (markup), so it
   reads as its own pill rather than the dock's header. */
#rightStack > #identityBar {
  flex: 0 0 auto;
  border-radius: var(--r-xl);
}
/* The dock starts at its own header again. */
#roomInfo > #roomInfoHead:first-child {
  border-radius: var(--r-xl) var(--r-xl) 0 0;
}

/* Shrink-to-fit, pinned right — a cluster shell should hug its items. */
.cluster-tr {
  width: max-content;
  max-width: min(270px, 80vw);
  flex-wrap: wrap;
  justify-content: flex-end;
  margin-left: auto;
}

/* ============================================================================
   Phase 7i — region panes + Show/Drop, on one anatomy (mockups 8a-8f).
   Header (title + ✕ on a hairline) · body · pinned footer where the pane has a
   persistent action. Every pane in #regionTL / #regionTR and both hand popouts.
   ========================================================================= */

/* ---- shared shell ------------------------------------------------------- */
.region .pane,
.panel.popout {
  padding: 0;
  gap: 0;
}
.regionHead,
.panel.popout > .panel-head {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 11px 12px;
  margin: 0;
  border-bottom: 1px solid var(--border);
}
.regionHead b,
.panel.popout > .panel-head b {
  flex: 1;
  color: var(--text);
  font-size: var(--fs);
  font-weight: 600;
}
.regionHead .close-x,
.panel.popout > .panel-head .close-x {
  flex: 0 0 auto;
}
/* Every direct child after the head is body padding — except a footer row. */
.region .pane > :not(.regionHead):not(.paneFoot),
.panel.popout > :not(.panel-head):not(.paneFoot) {
  margin-inline: 12px;
}
.region .pane > :nth-child(2),
.panel.popout > :nth-child(2) {
  margin-top: 11px;
}
.region .pane > :last-child:not(.paneFoot),
.panel.popout > :last-child:not(.paneFoot) {
  margin-bottom: 12px;
}
/* A caveat/hint line under a head (notes, measure): quiet, with an icon. */
.paneNote {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: var(--fs-sm);
}
.paneNote .ico {
  width: var(--ico-size-sm);
  height: var(--ico-size-sm);
  color: var(--accent);
  opacity: 1;
}

/* ---- chat (8a) ---------------------------------------------------------- */
#chatLog {
  display: flex;
  flex-direction: column;
  gap: 10px;
  max-height: 260px;
  overflow-y: auto;
  font-size: var(--fs-sm);
}
#chatLog b {
  color: var(--accent);
  font-size: var(--fs-sm);
} /* the sender is the only accent in the log */
#chatInputRow {
  display: flex;
  align-items: center;
  gap: 7px;
  margin: 0;
  padding: 9px 10px;
  border-top: 1px solid var(--border);
}
#chatInput {
  flex: 1;
  min-width: 0;
}
#chatSend {
  flex: 0 0 auto;
  min-width: 0;
  width: calc(32px * var(--ui-elements-scale));
  height: calc(32px * var(--ui-elements-scale));
  padding: 0;
  justify-content: center;
  color: var(--accent);
  border-color: var(--accent);
  background: none;
}
#chatSend:hover {
  background: var(--accent-soft);
}

/* ---- notes (8b) --------------------------------------------------------- */
#notesText {
  min-height: 150px;
  line-height: 1.6;
}

/* ---- scoreboard (8c): the − / value / + trio becomes one stepper -------- */
#scoreTable,
#scoreRows tr {
  display: flex;
  flex-direction: column;
}
#scoreRows tr {
  flex-direction: row;
  align-items: center;
  gap: 8px;
  padding: 5px 6px;
  border-radius: var(--r-sm);
}
#scoreRows tr:hover {
  background: color-mix(in srgb, var(--text) 4%, transparent);
}
#scoreRows td {
  border: 0;
  padding: 0;
}
.scoreName {
  flex: 1;
  min-width: 0;
  order: 1;
}
.scoreName input {
  width: 100%;
  padding: 3px 6px;
  background: none;
  border-color: transparent;
  font-size: var(--fs-sm);
}
.scoreName input:hover,
.scoreName input:focus-visible {
  background: var(--field);
  border-color: var(--border);
}
.scoreActs {
  display: contents;
} /* lets − / value / + interleave as one control */
.scoreVal {
  order: 3;
  min-width: 26px;
  text-align: center;
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  color: var(--accent);
}
.scoreActs button:nth-child(1) {
  order: 2;
  border-radius: var(--r-sm) 0 0 var(--r-sm);
  border-right: 0;
}
.scoreActs button:nth-child(2) {
  order: 4;
  border-radius: 0 var(--r-sm) var(--r-sm) 0;
  border-left: 0;
}
.scoreActs button:nth-child(3) {
  order: 5;
  margin-left: 4px;
  border-radius: var(--r-sm);
}
.scoreActs button {
  min-width: 0;
  padding: 2px 8px;
  font-size: var(--fs-sm);
  background: none;
}
.scoreEmpty {
  font-size: var(--fs-sm);
  padding: 6px 0;
}
#scoreEdit {
  border-top: 1px solid var(--border);
  padding: 9px 10px;
  margin: 0 !important;
  display: flex;
  flex-direction: column;
  gap: 7px;
}
.scoreAddRow {
  display: flex;
  align-items: center;
  gap: 7px;
}
.scoreAddRow input {
  flex: 1;
  min-width: 0;
}
#scoreEdit button {
  min-width: 0;
  padding: 4px 10px;
  font-size: var(--fs-sm);
}
#scoreClear {
  align-self: flex-start;
}

/* ---- music (8d) --------------------------------------------------------- */
#musicBody {
  display: flex;
  flex-direction: column;
  gap: 10px;
}
#musicBody .chipRow {
  gap: 6px;
}
#musicBody .chip {
  padding: 6px 9px;
}
#musicBody .chip .ico {
  width: var(--ico-size-sm);
  height: var(--ico-size-sm);
}

/* ---- measure (8e): the instructions become one hint block --------------- */
.pane[data-pane='measure'] #measureKinds {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 6px;
}
.pane[data-pane='measure'] #measureKinds .chip {
  justify-content: center;
  padding: 8px;
}
.pane[data-pane='measure'] p.muted {
  padding: 9px 10px;
  border-radius: var(--r);
  background: var(--accent-soft);
  border: 1px solid color-mix(in srgb, var(--accent) 24%, transparent);
  font-size: var(--fs-sm);
  line-height: 1.5;
}
.pane[data-pane='measure'] .chipRow:last-of-type .chip {
  flex: 1;
  justify-content: center;
}

/* ---- timer (8f): the readout is the pane -------------------------------- */
#timerMode {
  display: flex;
  border: 1px solid var(--border);
  border-radius: var(--r);
  overflow: hidden;
}
#timerMode .libTab {
  flex: 1;
  justify-content: center;
  border-radius: 0;
  border: 0;
  padding: 7px 10px;
}
#timerMode .libTab + .libTab {
  border-left: 1px solid var(--border);
}
#timerReadout {
  text-align: center;
  font:
    600 40px/1 ui-monospace,
    monospace;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.02em;
}
.timerRow {
  display: flex;
  align-items: center;
  gap: 7px;
}
.timerRow button {
  min-width: 0;
  padding: 8px 12px;
}
#timerToggle {
  flex: 1;
  justify-content: center;
  color: var(--accent);
  border-color: var(--accent);
}
#timerDurRow {
  margin-top: 2px;
}

/* ---- Show / Drop hand popouts ------------------------------------------ */
.showLabel {
  font-size: var(--fs-sm);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text);
  opacity: 0.5;
  margin: 0;
}

/* ============================================================================
   7i fixes — pane overflow and text contrast.
   The horizontal scrollbars came from margin-inline: 12px on children that are
   themselves width: 100% (textarea, table, inputs) = 100% + 24px.
   ========================================================================= */
.region .pane,
.panel.popout {
  overflow-x: hidden;
}
.region .pane > *,
.panel.popout > * {
  box-sizing: border-box;
  max-width: calc(100% - 24px);
}
.region .pane > .regionHead,
.region .pane > #chatInputRow,
.region .pane > #scoreEdit,
.panel.popout > .panel-head {
  max-width: 100%;
}
#notesText,
#scoreTable,
.region #roomNotes {
  width: auto;
  min-width: 0;
}

/* ---- contrast: body text near-full, only the meaningful bits accented ---- */
.paneNote {
  opacity: 1;
  color: color-mix(in srgb, var(--text) 78%, transparent);
}
.scoreEmpty {
  opacity: 1;
  color: color-mix(in srgb, var(--text) 62%, transparent);
}
#nowPlaying {
  opacity: 1;
  color: var(--text);
  font-size: var(--fs-sm);
}
#nowPlaying b {
  color: var(--accent);
}
.pane[data-pane='measure'] p.muted {
  color: color-mix(in srgb, var(--text) 92%, transparent);
  opacity: 1;
}
.pane[data-pane='measure'] p.muted b {
  color: var(--accent); /* the shape names are the highlighted parts */
  font-weight: 600;
}
/* Same rule anywhere else a .muted line is doing real work inside a pane. */
.region .pane .muted,
.panel.popout .muted {
  opacity: 1;
  color: color-mix(in srgb, var(--text) 72%, transparent);
}

/* ---- chat: timestamps + your own messages on the right ------------------ */
.chatMsg {
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.chatHead {
  display: flex;
  align-items: baseline;
  gap: 7px;
}
.chatFrom {
  color: var(--accent);
  font-weight: 600;
  font-size: var(--fs-sm);
}
.chatTime {
  margin-left: auto;
  font-family: ui-monospace, monospace;
  font-size: var(--fs-sm);
  color: color-mix(in srgb, var(--text) 42%, transparent);
}
.chatText {
  line-height: 1.5;
  color: color-mix(in srgb, var(--text) 90%, transparent);
}
.chatMsg.me {
  align-items: flex-end;
}
.chatMsg.me .chatHead {
  flex-direction: row-reverse;
}
.chatMsg.me .chatTime {
  margin-left: 0;
  margin-right: auto;
}
.chatMsg.me .chatFrom {
  color: color-mix(in srgb, var(--text) 55%, transparent);
}
.chatMsg.me .chatText {
  padding: 6px 9px;
  border-radius: var(--r) var(--r) 3px var(--r);
  background: var(--accent-soft);
  border: 1px solid color-mix(in srgb, var(--accent) 28%, transparent);
  color: var(--text);
}

/* The focus ring is 2px + 2px offset, so a control sitting flush under a line
   of text eats it. Give the notes field its own breathing room and pull its
   ring in by a pixel. */
#notesText {
  margin-top: 10px;
}
.region .pane textarea:focus-visible,
.panel.popout textarea:focus-visible {
  outline-offset: 1px;
}

/* ============================================================================
   Phase 7j — Show strip (9b) + split Drop flank with a toast (9e).
   Both popouts are gone: the audience IS the control, and the orientation IS
   the button. #handRow is already fixed, so it's the containing block.
   ========================================================================= */

/* ---- show strip: faces above the hand ---------------------------------- */
#showStrip {
  position: absolute;
  left: 50%;
  bottom: calc(100% + 8px);
  transform: translateX(-50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 5px;
  z-index: 9;
}
#showStripChips {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 6px 8px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 24px;
  backdrop-filter: var(--blur);
  box-shadow: 0 6px 18px var(--shadow-soft);
}
#showStripChips button {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
  padding: 5px 11px;
  border-radius: 18px;
  border-color: transparent;
  background: none;
  backdrop-filter: none;
  font-size: var(--fs-sm);
  white-space: nowrap;
}
#showStripChips button:hover {
  background: var(--accent-soft);
  border-color: transparent;
}
#showStripChips button.on {
  color: var(--accent);
  border-color: var(--accent);
  box-shadow: 0 0 12px -4px var(--accent);
}
#showStripChips button .ico {
  width: var(--ico-size-sm);
  height: var(--ico-size-sm);
}
#showStripChips .stripAv {
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: var(--avatar);
  flex: 0 0 auto;
}
#showStripChips .stripSep {
  width: 1px;
  align-self: stretch;
  margin: 2px 2px;
  background: var(--border);
}
#showStatus {
  font-size: var(--fs-sm);
  color: var(--accent);
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.6);
}
#showStatus:empty {
  display: none;
}
#rearrangeBar {
  position: absolute;
  left: 50%;
  bottom: calc(100% + 8px);
  transform: translateX(-50%);
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 12px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 24px;
  backdrop-filter: var(--blur);
  box-shadow: 0 6px 18px var(--shadow-soft);
  z-index: 9;
  white-space: nowrap;
}
#rearrangeBar .rearrangeHint {
  font-size: var(--fs-sm);
  opacity: 0.7;
}
#rearrangeBtn[aria-pressed='true'] {
  color: var(--accent);
  border-color: var(--accent);
  box-shadow: 0 0 12px -4px var(--accent);
}

/* ---- drop: disclose the orientation choices from one obvious action ---- */
.handAction {
  position: relative;
}
.handAction > #dropBtn {
  height: 100%;
}
.dropSplit {
  position: absolute;
  right: 0;
  bottom: calc(100% + 8px);
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 4px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  backdrop-filter: var(--blur);
}
.dropSplit button {
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
  padding: 6px 9px;
  background: none;
  border-color: transparent;
  backdrop-filter: none;
  border-radius: var(--r-sm);
  font-size: var(--fs-sm);
  white-space: nowrap;
}
.dropSplit button:hover {
  background: var(--accent-soft);
  color: var(--accent);
  border-color: transparent;
}
.dropSplit button .ico {
  width: var(--ico-size-sm);
  height: var(--ico-size-sm);
}

/* ---- toast ------------------------------------------------------------- */
#toast {
  position: fixed;
  top: calc(var(--inset) + 4px);
  left: 50%;
  transform: translateX(-50%);
  z-index: 30;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 12px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--r);
  backdrop-filter: var(--blur);
  box-shadow: 0 8px 24px var(--shadow-soft);
  font-size: var(--fs-sm);
  color: var(--text);
  animation: toastIn 0.16s ease-out;
}
.toastAction {
  min-width: 0;
  padding: 3px 9px;
  background: none;
  border-color: var(--accent);
  color: var(--accent);
  font-size: var(--fs-sm);
}
.toastAction:hover {
  background: var(--accent-soft);
}
#toast .ico {
  width: var(--ico-size-sm);
  height: var(--ico-size-sm);
  color: var(--accent);
}
@keyframes toastIn {
  from {
    opacity: 0;
    transform: translate(-50%, -6px);
  }
}

/* Compact / touch: the flanks lose their labels like every other cluster. */
@media (max-width: 720px), (max-height: 500px), (pointer: coarse) {
  body:not(.ui-full) .dropSplit button .lbl,
  body:not(.ui-full) #showStripChips button .lbl {
    display: none;
  }
}

/* ---- 7i-b · the track list lives in the Music pane (8d) ----------------- */
.trackList {
  display: flex;
  flex-direction: column;
  gap: 2px;
  max-height: 190px;
  overflow-y: auto;
}
.trackList .trackItem {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  min-width: 0;
  padding: 6px 9px;
  text-align: left;
  background: none;
  border-color: transparent;
  backdrop-filter: none;
  border-radius: var(--r-sm);
  font-size: var(--fs-sm);
}
.trackList .trackItem:hover {
  background: var(--accent-soft);
  border-color: transparent;
}
.trackList .trackItem.on {
  background: var(--accent-soft);
  color: var(--accent);
}
.trackList .muted {
  padding: 6px 9px;
}

/* ============================================================================
   Phase 7e slice 2 — the touch top bar (mockup 10a).
   The bar was wide because it carried the room CODE, the timer, the turn pill and
   an avatar at once. On touch it keeps five things: disclosure, room name, timer
   value, turn state, avatar. The code moves to Room info; the pill reduces to its
   caret unless the turn is yours.
   ========================================================================= */
.timerMini {
  display: none;
}
@media (max-width: 900px), (pointer: coarse) {
  /* One row, but only as wide as its contents until slice 3 folds the top-left
     cluster into the drawer — a full-bleed bar would sit on top of it today. */
  #rightStack {
    /* top stays at the stacked value (below the top-right cluster) until slice 3
       folds that cluster into the drawer — at var(--inset) the two collide. */
    bottom: auto;
    max-width: calc(100vw - 2 * var(--inset));
    max-height: none;
    overflow: visible;
  }
  /* Room name leads and takes the slack; identity trails. */
  #rightStack > #roomInfo {
    background: none;
    backdrop-filter: none;
    border: 0;
    box-shadow: none;
  }
  #roomInfo > #roomInfoHead:first-child {
    padding: 0;
    border-radius: 0;
  }
  #roomInfoHead #roomTitle {
    overflow: hidden;
    text-overflow: ellipsis;
  }
  #roomInfoHead #roomCode {
    display: none;
  } /* lives in the Room info sheet now */
  #roomInfoBody {
    display: none;
  } /* opened as a sheet, not docked (slice 5) */
  #rightStack > #identityBar {
    flex: 0 0 auto;
    padding: 3px;
    padding-inline-start: 8px;
  }
  #identityBar #myName {
    display: none;
  } /* the avatar is the identity at this width */
  #identityBar #myAv {
    margin-left: 0;
  }
  .timerMini {
    display: inline-flex;
    align-items: center;
    font-family: var(--font-mono, ui-monospace, monospace);
    font-size: var(--fs-sm);
    color: var(--text);
    opacity: 0.75;
    white-space: nowrap;
  }
  .timerMini[hidden] {
    display: none;
  }
  /* Not your turn: the pill is just a state light, so drop the words and keep the caret. */
  .turnPill:not(.myturn) .lbl {
    display: none;
  }
  .turnPill:not(.myturn) {
    padding: 6px;
    border-radius: 50%;
  }
}

/* ============================================================================
   Phase 7e slice 3 — the ☰ drawer (mockup 10b).
   Both top clusters fold into one sheet of grouped rows. A sheet belongs to
   neither side, so it cannot open on the wrong one.
   ========================================================================= */
#drawerBtn,
.sheet {
  display: none;
}
@media (max-width: 900px), (pointer: coarse) {
  #ui,
  #clusterTR {
    display: none;
  } /* the drawer is the only way in now */
  #rightStack {
    /* the corner is free again */
  }
  #rightStack > #roomInfo {
    max-width: none;
  }
  #drawerBtn {
    display: inline-flex;
    order: -2;
    flex: 0 0 auto;
  }
  .sheet {
    display: flex;
    position: fixed;
    z-index: 9;
    background: var(--panel);
    backdrop-filter: var(--blur);
    border: 1px solid var(--border);
    border-bottom: 0;
    color: var(--text);
  }
  .sheet[hidden] {
    display: none;
  }
  .drawerBody {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    gap: 14px;
    padding: 4px 10px 0;
  }
  .drawerGroup {
    display: flex;
    flex-direction: column;
    gap: 2px;
  }
  .drawerGroup > .miniLabel {
    padding: 0 8px 2px;
  }
  .drawerRow {
    display: flex;
    align-items: center;
    gap: 11px;
    width: 100%;
    min-height: 44px;
    padding: 8px 10px;
    border: 0;
    border-radius: var(--r);
    background: none;
    color: var(--text);
    font-size: var(--fs);
    text-align: left;
  }
  .drawerRow:hover,
  .drawerRow:focus-visible {
    background: var(--accent-soft);
  }
  .drawerRow .ico {
    width: var(--ico-size-lg);
    height: var(--ico-size-lg);
    color: var(--accent);
    flex: 0 0 auto;
  }
  .drawerRow .drawerLbl {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .drawerRow.danger .ico,
  .drawerRow.danger .drawerLbl {
    color: var(--danger, #e0a3a3);
  }
  .drawerDot {
    min-width: 18px;
    height: 18px;
    padding: 0 5px;
    border-radius: 9px;
    background: var(--accent-soft);
    border: 1px solid var(--accent);
    color: var(--accent);
    font-size: var(--fs-sm);
    display: grid;
    place-items: center;
  }
  .drawerFoot {
    flex: none;
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 10px 14px calc(14px + env(safe-area-inset-bottom, 0px));
    border-top: 1px solid var(--border);
  }
}

/* ============================================================================
   Phase 7e slice 4 — one seat button (mockup 10e).
   Both corner clusters collapse into a single round button above the hand tab.
   Its popover is anchored to THAT button and flips by available space, so the
   "opens on the left even from the right cluster" bug has nowhere to live.
   ========================================================================= */
#seatBtn,
.seatPop {
  display: none;
}
@media (max-width: 900px), (pointer: coarse) {
  #hamBar,
  #hamBarRight {
    display: none;
  }
  #seatBtn {
    display: grid;
    place-items: center;
    position: fixed;
    left: var(--inset);
    bottom: calc(var(--inset) + 108px);
    z-index: 8;
    width: 58px;
    height: 58px;
    padding: 0;
    border-radius: 50%;
    background: var(--panel-solid);
    border: 1px solid var(--accent);
    color: var(--accent);
    box-shadow:
      0 0 26px -8px var(--accent),
      0 8px 20px var(--shadow-soft);
  }
  #seatBtn[hidden] {
    display: none;
  }
  #seatBtn .lbl {
    display: none;
  }
  #seatBtn .ico {
    width: var(--ico-size-xl);
    height: var(--ico-size-xl);
  }
  #seatBtn.on {
    background: var(--accent-soft);
  }
  .seatPop {
    display: flex;
    flex-direction: column;
    gap: 2px;
    position: fixed;
    z-index: 9;
    width: min(252px, calc(100vw - 2 * var(--inset)));
    max-height: 60vh;
    overflow-y: auto;
    padding: 6px;
    border-radius: var(--r-lg);
    background: var(--panel);
    backdrop-filter: var(--blur);
    border: 1px solid var(--accent);
    box-shadow: 0 18px 40px var(--shadow-soft);
  }
  .seatPop[hidden] {
    display: none;
  }
  .seatPop .miniLabel {
    padding: 6px 8px 3px;
  }
  .seatPop .drawerRow {
    min-height: 44px;
  }
  .seatPop .seatRule {
    height: 1px;
    margin: 5px 6px;
    background: var(--border);
  }
  /* The live #players node is borrowed while the popover is open. */
  .seatPop #players {
    display: flex;
    flex-direction: column;
    gap: 3px;
    padding: 2px 4px 4px;
  }
}

/* A button that exists only to be proxied by the drawer: never rendered itself,
   but not `hidden`, because `hidden` is how role gating speaks. */
.proxyOnly {
  display: none !important;
}

/* ============================================================================
   Phase 7e slice 5 — Room info sheet (mockup 10f).
   The code, the share action and the dock the top bar stopped carrying.
   ========================================================================= */
@media (max-width: 900px), (pointer: coarse) {
  .roomCard {
    display: flex;
    flex-direction: column;
    gap: 10px;
    padding: 12px;
    border-radius: var(--r-lg);
    border: 1px solid var(--border);
    background: var(--field);
  }
  .roomCardName {
    font-size: var(--fs-lg);
    color: var(--text);
  }
  .roomCardMeta {
    font-size: var(--fs-sm);
    opacity: 0.6;
  }
  .roomCodeRow {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 8px 10px;
    border-radius: var(--r);
    border: 1px solid var(--border);
  }
  .roomCodeVal {
    flex: 1;
    min-width: 0;
    font-family: var(--font-mono, ui-monospace, monospace);
    font-size: var(--fs-lg);
    letter-spacing: 0.12em;
    color: var(--accent);
    overflow: hidden;
    text-overflow: ellipsis;
  }
  /* The dock's own sections, borrowed into the sheet, sit in the body rhythm. */
  #roomSheet #roomInfoBody {
    display: flex;
    padding: 0;
  }
}

/* Slice 2 follow-up: the room name is the bar's only text, so give it the slack and
   drop the dock's collapse chevron — there is no dock to collapse at this width. */
@media (max-width: 900px), (pointer: coarse) {
  #roomInfoHead #railToggle {
    display: none;
  }
  #roomInfoHead {
    gap: 0;
  }
  #roomInfoHead #roomTitle {
    flex: 1 1 auto;
    min-width: 0;
  }
  #drawerBtn .lbl {
    display: none;
  } /* the hamburger needs no caption */
}

/* ============================================================================
   Slice 2 correction — ONE contiguous bar (mockup 10a), not three floating pills.
   #rightStack itself is the glass; its children are bare.
   ========================================================================= */
@media (max-width: 900px), (pointer: coarse) {
  #rightStack {
    top: var(--inset);
    left: var(--inset);
    right: var(--inset);
    width: auto;
    flex-direction: row;
    gap: 8px;
    padding: 5px 8px;
    border-radius: var(--r-xl);
  }
  #rightStack > #drawerBtn,
  #rightStack > #identityBar {
    background: none;
    backdrop-filter: none;
    border: 0;
    box-shadow: none;
    padding: 0;
  }
  #rightStack > #drawerBtn {
    width: 34px;
    height: 34px;
    display: grid;
    place-items: center;
  }
  #rightStack > #roomInfo {
    order: -1;
    flex: 1 1 auto;
    min-width: 0;
  }
  #rightStack > #identityBar {
    order: 2;
    /* timer, turn state and avatar hug the right edge */
    gap: 8px;
  }
  #rightStack > #toolsDock {
    order: 3;
    flex: 0 0 auto;
  }
  #identityBar .turnPill {
    background: none;
  }
}

/* Make the bar's three zones structural rather than flex-order dependent: the name
   column is the only one that grows, so identity can never sit on top of it. */
@media (max-width: 900px), (pointer: coarse) {
  #rightStack {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr) auto auto;
    align-items: center;
  }
  #rightStack > #drawerBtn {
    grid-column: 1;
  }
  #rightStack > #roomInfo {
    grid-column: 2;
    overflow: visible;
  }
  #rightStack > #identityBar {
    grid-column: 3;
    justify-self: end;
    margin-left: 0;
  }
  #rightStack > #toolsDock {
    grid-column: 4;
  }
  #roomInfoHead #roomTitle {
    display: block;
    font-size: var(--fs);
    color: var(--accent);
  }
}

/* ============================================================================
   Phase 7e slice 6 — the hand tray (mockup 10g).
   A peeking tab that pulls up into: show faces (9b), cards, drop pair (9e).
   Stacked, not flanked — that is what keeps 44px targets at 392px wide.
   ========================================================================= */
#handTab {
  display: none;
}
@media (max-width: 900px), (pointer: coarse) {
  #handRow {
    left: 0;
    right: 0;
    bottom: 0;
    transform: none;
    max-width: none;
    flex-direction: column;
    align-items: stretch;
    gap: 8px;
    padding: 0 12px calc(12px + env(safe-area-inset-bottom, 0px));
    background: var(--panel);
    backdrop-filter: var(--blur);
    border-top: 1px solid var(--border);
    border-radius: var(--r-xl) var(--r-xl) 0 0;
    box-shadow: 0 -14px 34px var(--shadow-soft);
  }
  #handTab {
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
    min-height: 44px;
    padding: 8px 2px;
    border: 0;
    background: none;
    color: var(--text);
    font-size: var(--fs);
  }
  #handTab[hidden] {
    display: none;
  }
  /* Three card edges, fanned — the tab says "cards" before it says anything else. */
  .handFan {
    display: flex;
    flex: 0 0 auto;
  }
  .handFan i {
    width: 26px;
    height: 36px;
    border-radius: 4px;
    background: var(--card-face, #e8e4dc);
    box-shadow: 0 4px 10px var(--shadow-soft);
  }
  .handFan i + i {
    margin-left: -16px;
  }
  .handTabLbl {
    flex: 1;
    min-width: 0;
    text-align: left;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .handCount {
    min-width: 20px;
    height: 20px;
    padding: 0 6px;
    border-radius: 10px;
    background: var(--accent-soft);
    border: 1px solid var(--accent);
    color: var(--accent);
    font-size: var(--fs-sm);
    display: grid;
    place-items: center;
  }
  #handTab .ico-caret {
    width: var(--ico-size-md);
    height: var(--ico-size-md);
    opacity: 0.5;
    transition: transform 0.18s ease;
  }
  #handRow.trayOpen #handTab .ico-caret {
    transform: rotate(180deg);
  }
  /* Collapsed: the tab IS the hand row. */
  #handRow:not(.trayOpen) #hand,
  #handRow:not(.trayOpen) #showStrip,
  #handRow:not(.trayOpen) #rearrangeBar,
  #handRow:not(.trayOpen) #dropFlank,
  #handRow:not(.trayOpen) #handFlankStack {
    display: none !important;
  }
  /* Open: clear action buttons disclose show/drop choices only when requested. */
  #handRow.trayOpen #handFlankStack {
    order: 1;
    display: flex;
  }
  #handRow.trayOpen #handFlankStack button {
    display: flex;
    justify-content: center;
    min-height: 44px;
  }
  #handRow.trayOpen #showStrip {
    position: static;
    left: auto;
    transform: none;
    order: 2;
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
  }
  #handRow.trayOpen #rearrangeBar {
    position: static;
    left: auto;
    transform: none;
    order: 2;
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
  }
  #handRow.trayOpen #hand {
    order: 3;
    display: flex;
    justify-content: center;
    flex-wrap: nowrap;
    overflow-x: auto;
    max-width: none;
    padding: 4px 0;
    /* Keep the artwork readable and leave a generous drag surface around the Inspect control.
       The strip already scrolls horizontally, so showing fewer full cards is the better touch
       trade-off than shrinking every target beneath a fingertip. */
    --card-w: calc(clamp(72px, 20vw, 88px) * var(--ui-elements-scale));
  }
  #handRow.trayOpen .cardEye {
    width: 30px;
    height: 30px;
    padding: 5px;
    top: 5px;
    right: 5px;
  }
  #handRow.trayOpen #dropFlank {
    order: 4;
    display: block;
  }
  #handRow.trayOpen #dropBtn {
    width: 100%;
    justify-content: center;
    min-height: 44px;
  }
  #handRow.trayOpen #dropChoices {
    position: static;
    margin-top: 8px;
  }
  #handRow.trayOpen #dropChoices button {
    flex: 1;
    justify-content: center;
    min-height: 44px;
  }
  #handRow.trayOpen #dropFlank button .lbl {
    display: inline;
  } /* room for words once it is a full-width pair */
  /* Leave the expanded private hand clear; restore the floating actions when it closes. */
  body.trayOpen #selectBtn,
  body.trayOpen #seatBtn,
  body.trayOpen #fabBtn {
    display: none;
  }
}

/* ============================================================================
   Phase 7e slice 7 — ⊕ and the radial menu (mockup 10j).
   One fan primitive: the FAB uses it for table actions, and long-press on a piece
   uses it instead of a vertical list, so touch has one context-menu shape.
   ========================================================================= */
#fabBtn,
#radial {
  display: none;
}
@media (max-width: 900px), (pointer: coarse) {
  #fabBtn {
    display: grid;
    place-items: center;
    position: fixed;
    left: var(--inset);
    bottom: calc(var(--inset) + 42px);
    z-index: 8;
    width: 58px;
    height: 58px;
    padding: 0;
    border-radius: 50%;
    background: var(--panel-solid);
    border: 1px solid var(--accent);
    color: var(--accent);
    box-shadow:
      0 0 26px -8px var(--accent),
      0 8px 20px var(--shadow-soft);
    transition: transform 0.18s ease;
  }
  #fabBtn[hidden] {
    display: none;
  }
  #fabBtn .lbl {
    display: none;
  }
  #fabBtn .ico {
    width: var(--ico-size-xl);
    height: var(--ico-size-xl);
  }
  #fabBtn.on {
    transform: rotate(45deg);
    background: var(--accent-soft);
  }
}
/* The radial itself works at any pointer type — long-press exists on desktop too. */
#radial {
  position: fixed;
  inset: 0;
  z-index: 12;
}
#radial[hidden] {
  display: none;
}
#radial.on {
  display: block;
}
#radial .radialScrim {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.28);
}
.radialItem {
  position: absolute;
  display: flex;
  align-items: center;
  gap: 8px;
  transform: translate(-50%, -50%) scale(0.7);
  opacity: 0;
  transition:
    transform 0.16s cubic-bezier(0.32, 0.72, 0, 1),
    opacity 0.16s ease;
}
#radial.on .radialItem {
  transform: translate(-50%, -50%) scale(1);
  opacity: 1;
}
.radialItem.flip {
  flex-direction: row-reverse;
}
.radialDot {
  width: 48px;
  height: 48px;
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: var(--panel-solid);
  border: 1px solid var(--accent);
  color: var(--accent);
  box-shadow: 0 8px 20px var(--shadow-soft);
}
.radialItem.danger .radialDot {
  border-color: var(--danger, #e0a3a3);
  color: var(--danger, #e0a3a3);
}
.radialDot .ico {
  width: var(--ico-size);
  height: var(--ico-size);
}

/* ============================================================================
   Fix: #rightStack must NOT carry backdrop-filter.
   #toolsDock — and with it #regionTL / #regionTR — lives inside #rightStack, and a
   filtered ancestor becomes the containing block for position:fixed descendants, so
   the region sheets were being positioned against the top bar instead of the viewport
   (they opened at the top of the screen, cut off). The glass moves to a pseudo-element,
   which paints the same and contains nothing.
   ========================================================================= */
@media (max-width: 900px), (pointer: coarse) {
  #rightStack {
    background: none;
    backdrop-filter: none;
    border: 0;
    box-shadow: none;
    isolation: isolate;
  }
  #rightStack::before {
    content: '';
    position: absolute;
    inset: 0;
    z-index: -1;
    border-radius: var(--r-xl);
    background: var(--panel);
    backdrop-filter: var(--blur);
    border: 1px solid var(--border);
    box-shadow: 0 6px 18px var(--shadow-soft);
  }
  #rightStack > #toolsDock {
    position: static;
  }
}

/* The radial item is a bare button — the round dot is the only chrome. Global button
   styling (background, border, padding, min-width) was drawing a box around each icon. */
#radial .radialItem {
  padding: 0;
  margin: 0;
  min-width: 0;
  min-height: 0;
  border: 0;
  border-radius: 50%;
  background: none;
  box-shadow: none;
  backdrop-filter: none;
}
#radial .radialItem:hover,
#radial .radialItem:focus-visible {
  background: none;
}
#radial .radialItem:focus-visible .radialDot {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* ============================================================================
   Phase 7e slice 8 — the library on a phone (mockup 10h).
   Portrait: the modal fills the screen and the grid goes single-column-ish.
   Landscape was the reported break — three stacked header rows (title+search,
   source, kind) over 390px-wide cards left barely one row of assets visible.
   ========================================================================= */
@media (max-width: 900px), (pointer: coarse) {
  #libraryModal .modalCard.libraryCard.lib2Card {
    width: calc(100vw - 16px);
    max-height: calc(100vh - 16px);
  }
  #libraryModal .modalCard:has(.libSticky) .libPane {
    padding: 10px 12px 14px;
  }
  #libraryModal .modalCard .libSticky {
    gap: 8px;
    padding: 10px 12px 8px;
  }
}
/* Landscape phones: one header row, four across. Header 88px → 44px. */
@media (orientation: landscape) and (max-height: 560px) {
  #libraryModal .modalCard .libSticky {
    flex-direction: row;
    align-items: center;
    flex-wrap: nowrap;
    gap: 8px;
    padding: 6px 10px;
    overflow-x: auto;
  }
  #libraryModal .libSticky .panel-head {
    flex: 0 0 auto;
    gap: 8px;
  }
  #libraryModal .panel-title {
    font-size: var(--fs);
  }
  #libraryModal .libSticky .panel-head .close-x {
    order: 9; /* stays last in the row, not pushed by margin-left:auto */
    margin-left: 0;
  }
  /* Search moves up beside the title; source + kind share the remaining width. */
  #libraryModal .libControls {
    order: 2;
    flex: 0 0 auto;
    margin: 0;
    padding-top: 0;
    border-top: 0;
  }
  #libraryModal .libSearch {
    width: 140px;
  }
  #libraryModal .libTabs {
    order: 3;
    flex: 1 1 auto;
    flex-wrap: nowrap;
    overflow-x: auto;
    gap: 2px;
  }
  #libraryModal .libTab {
    white-space: nowrap;
    padding: 4px 8px;
    font-size: var(--fs-sm);
  }
  #libraryModal .modalCard.libraryCard.lib2Card {
    max-height: calc(100vh - 12px);
  }
  #libraryModal .modalCard:has(.libSticky) .libPane {
    padding: 8px 10px 10px;
  }
  /* List-shaped cards, four across: thumb + name + source, controls on one line. */
  #libraryModal .libList {
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 8px;
  }
  #libraryModal .libCard {
    padding: 6px;
    gap: 6px;
  }
  #libraryModal .libPreview {
    width: 42px;
    height: 42px;
    flex: 0 0 auto;
  }
  #libraryModal .libCard .libMeta b {
    font-size: var(--fs-sm);
  }
  #libraryModal .libCard .cardCtrls {
    gap: 4px;
  }
  /* colour choice now lives behind the .swatchPop trigger here — see the
     "Library card colour swatches" block. It used to be display:none. */
}

/* ============================================================================
   Phase 7g — Add to Library builder (mockup 5b).
   Same shell as the Library (7c) so the two modals read as one family: sticky
   header + one tab grammar, drop targets that show what they hold, the
   conditional geometry rows owned by the Fit-to-image chip, and a stated
   destination beside Save / Save + Spawn.
   Every id and the input-inside-.uploadSq nesting are untouched — FILLERS and
   wireUploadSq read the form by id, so the shell moved and the fields did not.
   ========================================================================= */

/* ---- shell: header sticks, the pane scrolls (mirrors #libraryModal) ------ */
#addModal .modalCard.libraryCard.addCard {
  display: flex;
  flex-direction: column;
  padding: 0;
  overflow: hidden;
}

/* ---- captions: the dashed --- label --- decoration goes ----------------- */
#addModal .addCol > label::before,
#addModal .addCol > label::after {
  content: '';
}
#addModal .addCol > label,
#addModal .sqCap {
  font-size: var(--fs-sm);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--accent);
  opacity: 0.9;
  margin: 0;
}
#addModal .sqCap {
  display: flex;
  align-items: baseline;
  gap: 7px;
}
#addModal .sqField {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
#addModal .sqOpt {
  text-transform: none;
  letter-spacing: 0;
  color: var(--text);
  opacity: 0.45;
}
#addModal .sqCount {
  margin-left: auto;
  padding: 1px 6px;
  border-radius: 999px;
  border: 1px solid var(--border);
  font-size: var(--fs-sm);
  letter-spacing: 0.05em;
  color: var(--text);
  opacity: 0.7;
}

/* ---- fronts tile: a real gallery + count, not the first file's preview --- */
#addModal .sqFronts {
  width: 100%;
  display: block;
  padding: 8px;
  min-height: 92px;
  max-height: 188px;
  overflow-y: auto;
  background-image: none !important;
}
#addModal .sqFronts:not(.filled) {
  display: grid;
  place-items: center;
}
#addModal .sqFronts.filled .sqLabel {
  display: none;
}
#addModal .sqGrid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 6px;
}
#addModal .sqGrid > i {
  aspect-ratio: 3 / 4;
  border-radius: 5px;
  background: var(--field) center/cover no-repeat;
}
#addModal .sqGrid > i.more {
  display: grid;
  place-items: center;
  border: 1px solid var(--border);
  background: none;
  font: 500 10px/1 var(--font);
  font-style: normal;
  color: var(--text);
  opacity: 0.55;
}

/* ---- the conditional rows belong to Fit to image ------------------------ */
#addModal .fitChip {
  align-self: flex-start;
  display: inline-flex;
  align-items: baseline;
  gap: 7px;
}
#addModal .fitChip .chipNote {
  font-weight: 400;
  font-size: var(--fs-sm);
  color: var(--text);
  opacity: 0.5;
}
#addModal .fitGroup {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin: 2px 0 0 11px;
  padding-left: 15px;
  border-left: 1px solid var(--accent-soft);
}
/* nothing shown = the toggle is off; the group leaves with its rows */
#addModal .fitGroup:not(:has(> :not([hidden]))) {
  display: none;
}
#addModal .fitGroup .addColorRow > span:first-child {
  flex: 0 0 68px;
  opacity: 0.65;
}
#addModal .fitGroup #adImgThick {
  flex: 1;
  max-width: 220px;
}
#addModal .fitGroup #adImgThickVal {
  color: var(--accent);
  opacity: 1;
  font-variant-numeric: tabular-nums;
}

/* ---- the pane's foot: destination, then the action pair ----------------- */
#addModal .saveFoot {
  margin-top: auto;
  padding-top: 12px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
#addModal .addSplit .addCol .saveFoot .row {
  margin-top: 0;
}
#addModal .destNote {
  margin: 0;
  font-size: var(--fs-sm);
  line-height: 1.5;
  color: color-mix(in srgb, var(--text) 48%, transparent);
}
#addModal .destNote b {
  font-weight: 500;
  color: color-mix(in srgb, var(--text) 78%, transparent);
}
#addModal [data-pane='sky'] .destNote {
  margin-top: 8px;
}

/* ---- touch: full-viewport card, one column, tabs scroll ----------------- */
@media (max-width: 900px), (pointer: coarse) {
  #addModal .modalCard.libraryCard.addCard {
    width: calc(100vw - 16px);
    max-width: none;
    height: calc(100vh - 16px);
    max-height: calc(100vh - 16px);
  }
  #addModal .modalCard .libSticky {
    gap: 8px;
    padding: 10px 12px 8px;
  }
  #addModal .libSticky .libTabs {
    flex-wrap: nowrap;
    overflow-x: auto;
    scrollbar-width: none;
  }
  #addModal .modalCard:has(.libSticky) .libPane {
    padding: 12px;
    min-height: 0;
  }
  #addModal .libPane > .addSplit,
  #addModal .libPane > .addDeckCols {
    flex-direction: column;
    gap: 14px;
  }
  #addModal .addSplit > .addPreview {
    flex: 0 0 auto;
    flex-direction: row;
    align-items: flex-start;
    align-self: stretch;
  }
  #addModal .addSplit > .addPreview > .sqField {
    flex: 1;
    min-width: 0;
  }
  #addModal [data-pane='imgdecks'] .addPreview .uploadSq,
  #addModal [data-pane='txtdecks'] .addPreview .cardPrev {
    width: 100%;
  }
  #addModal .addCol input[type='text'],
  #addModal .addCol textarea {
    max-width: none;
  }
  #addModal .saveFoot .row button {
    flex: 1;
  }
}
/* Landscape phones: the preview column comes back beside the form — height, not
   width, is the constraint (same trigger as the library in 7e slice 8). */
@media (orientation: landscape) and (max-height: 560px) {
  #addModal .libPane > .addSplit {
    flex-direction: row;
  }
  #addModal .addSplit > .addPreview {
    flex: 0 0 34%;
    flex-direction: column;
  }
  #addModal .modalCard .libSticky {
    padding: 8px 10px 6px;
    gap: 6px;
  }
}

/* ============================================================================
   The 6a window treatment, generalized to the whole modal family.
   The Library was the only modal wearing it; Add to Library (7g), Room Settings
   and Settings share the same anatomy (.libraryCard > .libSticky > .libPane), so
   the treatment moves onto that anatomy instead of onto one id. Per-modal widths
   and backgrounds are left alone — only the chrome is shared.
   ========================================================================= */

/* ---- the window --------------------------------------------------------- */
:where(.modal, .modalCard).libraryCard {
  display: flex;
  flex-direction: column;
  gap: 0;
  padding: 0;
  border-radius: var(--r-xl);
  border: 1px solid var(--border);
  box-shadow:
    0 0 0 1px rgba(0, 0, 0, 0.4),
    0 24px 60px rgba(0, 0, 0, 0.65);
  max-height: min(84vh, 900px);
  overflow: hidden; /* the pane scrolls, the shell does not */
}

/* ---- sticky header: title row over the tab row ------------------------- */
:where(.modal__header-stack, .modalCard .libSticky) {
  position: sticky;
  top: 0;
  z-index: 2;
  display: flex;
  flex-direction: column;
  gap: 11px;
  padding: 14px 18px 12px;
  background: color-mix(in srgb, var(--panel-solid) 92%, transparent);
  border-bottom: 1px solid var(--border);
}
.modal__header-stack > .modal__header,
.libraryCard .libSticky .panel-head {
  display: flex;
  align-items: center;
  gap: 12px;
  justify-content: flex-start;
  padding: 0;
  border: none;
}
/* Modal header title. One class on the <b>, rather than three id- and
   descendant-scoped rules saying the same thing. */
:where(.modal__title, .panel-title) {
  color: var(--text);
  font-size: 16px;
  font-weight: 600;
}
.modal__header .modal__close,
.libraryCard .libSticky .panel-head .close-x {
  margin-left: auto;
}

/* ---- tabs: pill chips, no underline rail -------------------------------- */
.libraryCard .libTabs {
  gap: 3px;
  border-bottom: none;
  flex-wrap: wrap;
}
.libraryCard .libTab {
  background: none;
  border: 1px solid transparent;
  border-radius: var(--r-sm);
  margin-bottom: 0;
  padding: 6px 11px;
  font-size: var(--fs-sm);
  opacity: 0.7;
}
.libraryCard .libTab:hover {
  background: var(--accent-soft);
  opacity: 1;
}
.libraryCard .libTab.on {
  background: var(--accent-soft);
  border-color: color-mix(in srgb, var(--accent) 55%, transparent);
  color: var(--accent);
  opacity: 1;
}

/* ---- the pane ----------------------------------------------------------- */
.modal:has(> .modal__header-stack) > .modal__body,
.modalCard:has(.libSticky) .libPane {
  padding: 14px 18px 18px;
  overflow-y: auto;
  min-height: 0;
  flex: 1;
}

/* Settings + Room Settings: the section labels were centred for a narrow pop-out.
   In the window they read as headings, so they align left like every other pane
   caption, and the accent-caption grammar is the one the tile captions use. */
:is(#roomSettingsModal, #settingsModal) .secLabel,
:is(#roomSettingsModal, #settingsModal) .showLabel {
  text-align: left;
}
:is(#roomSettingsModal, #settingsModal) .showLabel > b {
  color: var(--accent);
  font-size: var(--fs-sm);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
:is(#roomSettingsModal, #settingsModal) .libPane .tableBody {
  gap: 10px;
}

/* The Add builder's own pane padding + shell height stay as 7g set them. */
#addModal .modalCard.libraryCard.addCard {
  max-height: min(680px, calc(100vh - 56px));
}
#addModal .modalCard:has(.libSticky) .libPane {
  padding: 16px 18px 18px;
}

/* ============================================================================
   Post-7e: the seat button and the ⊕ fan are the DEFAULT, not the touch layout.
   Slices 4 and 7 were gated behind the sheet bound; the two bottom hamburger
   bars are retired at every pointer type. Multi-Select, Seat and Table Actions
   form a matching button stack on the left edge. The bars stay in the DOM — the seat popover and the
   fan proxy real buttons out of them (proxyGated reads classes, not visibility).
   ========================================================================= */
#hamBar,
#hamBarRight {
  display: none;
}
#selectBtn,
#seatBtn,
#fabBtn {
  display: grid;
  place-items: center;
  position: fixed;
  left: var(--inset);
  z-index: 8;
  width: 58px;
  height: 58px;
  padding: 0;
  border-radius: 50%;
  background: var(--panel-solid);
  border: 1px solid var(--accent);
  color: var(--accent);
  box-shadow:
    0 0 26px -8px var(--accent),
    0 8px 20px var(--shadow-soft);
}
#selectBtn {
  bottom: calc(var(--inset) + 174px);
}
#seatBtn {
  bottom: calc(var(--inset) + 108px);
}
#fabBtn {
  bottom: calc(var(--inset) + 42px);
  transition: transform 0.18s ease;
}
#selectBtn[hidden],
#seatBtn[hidden],
#fabBtn[hidden] {
  display: none;
}
#selectBtn .lbl,
#seatBtn .lbl,
#fabBtn .lbl {
  display: none;
}
#selectBtn .ico,
#seatBtn .ico,
#fabBtn .ico {
  width: var(--ico-size-xl);
  height: var(--ico-size-xl);
}
#selectBtn.on,
#seatBtn.on {
  background: var(--accent-soft);
}
#fabBtn.on {
  transform: rotate(45deg);
  background: var(--accent-soft);
}
.seatPop {
  display: flex;
  flex-direction: column;
  gap: 2px;
  position: fixed;
  z-index: 9;
  width: min(252px, calc(100vw - 2 * var(--inset)));
  max-height: 60vh;
  overflow-y: auto;
  padding: 6px;
  border-radius: var(--r-lg);
  background: var(--panel);
  backdrop-filter: var(--blur);
  border: 1px solid var(--accent);
  box-shadow: 0 18px 40px var(--shadow-soft);
  color: var(--text); /* never inherit — the roster is borrowed from the dock */
}
.seatPop[hidden] {
  display: none;
}
.seatPop .miniLabel {
  padding: 6px 8px 3px;
}
.seatPop .drawerRow {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  min-height: 40px;
  padding: 8px 10px;
  border: 0;
  border-radius: var(--r-sm);
  background: none;
  color: var(--text);
  text-align: left;
}
.seatPop .drawerRow:hover,
.seatPop .drawerRow:focus-visible {
  background: var(--accent-soft);
}
.seatPop .drawerRow .ico {
  width: var(--ico-size-lg);
  height: var(--ico-size-lg);
  flex: 0 0 auto;
  color: var(--accent);
}
.seatPop .drawerRow .drawerLbl {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.seatPop .seatRule {
  height: 1px;
  margin: 5px 6px;
  background: var(--border);
}
/* The live #players node is borrowed while the popover is open — it arrives with the
   dock's rules and nothing else, so state the roster's own colour here. */
.seatPop #players {
  display: flex;
  flex-direction: column;
  gap: 3px;
  padding: 2px 4px 4px;
  background: none;
  color: var(--text);
}
.seatPop #players .prow,
.seatPop #players .prow > span,
.seatPop #players > * {
  color: var(--text);
}
.seatPop #players .prow .rolebadge {
  color: var(--accent);
}
@media (pointer: coarse) {
  .seatPop .drawerRow {
    min-height: 44px;
  }
}

/* ============================================================================
   Mobile modal reach: a centred card taller than the VISUAL viewport (100vh
   excludes the URL bar) pushed its own header — and the ✕ — above the top of the
   screen. The overlay pins to the top instead, and the cap is dvh. The library's
   portrait cards also go list-shaped: a 240px tile column made each card tall
   enough that a few of them outran the screen.
   ========================================================================= */
@media (max-width: 900px), (pointer: coarse) {
  .modal-backdrop {
    align-items: flex-start;
    padding: 8px 0;
    overflow-y: auto;
  }
  #libraryModal .modalCard.libraryCard.lib2Card,
  #addModal .modalCard.libraryCard.addCard {
    max-height: calc(100dvh - 16px);
  }
  :is(#roomSettingsModal, #settingsModal, #controlsModal) .modalCard {
    max-height: calc(100dvh - 16px);
  }
  #libraryModal .libList {
    grid-template-columns: repeat(auto-fill, minmax(min(100%, 172px), 1fr));
    gap: 8px;
  }
  #libraryModal .libCard {
    padding: 7px;
    gap: 8px;
  }
  #libraryModal .libPreview {
    width: 46px;
    height: 46px;
    flex: 0 0 auto;
  }
  #libraryModal .libCard .libMeta b {
    font-size: var(--fs-sm);
  }
  #libraryModal .libCard .cardCtrls {
    gap: 5px;
  }
}

/* A data-icon naming a symbol the sprite does not carry used to render an empty box.
   applyIcons now flags it, so a typo (or a symbol pasted without its id="i-name")
   shows as a dashed placeholder instead of nothing. */
.ico-missing {
  outline: 1px dashed var(--danger);
  outline-offset: 1px;
  opacity: 0.6;
}

/* ============================================================================
   Scale & Grid: the label column was fixed at 78px and nowrap, so "Objects
   Snap-To?" simply painted over the control beside it. The column now wraps to
   two lines at a slightly wider fixed width, the rows align to the top of it,
   and the pane's own controls run tighter than table-wide chrome so a 480px
   window holds a label + a two-chip row without collision.
   ========================================================================= */
:is(#roomSettingsModal, #settingsModal) .tableBody .sizeRow {
  align-items: center;
  gap: 10px;
  margin: 4px 0;
  flex-wrap: nowrap;
}
:is(#roomSettingsModal, #settingsModal) .tableBody .sizeRow > label {
  flex: 0 0 88px;
  width: 88px;
  white-space: normal;
  overflow-wrap: break-word;
  line-height: 1.25;
  font-size: var(--fs-sm);
  opacity: 0.8;
}
/* Chips inside the pane: label-sized, not toolbar-sized. */
:is(#roomSettingsModal, #settingsModal) .tableBody .chipRow {
  flex: 1 1 auto;
  min-width: 0;
  gap: 5px;
  flex-wrap: wrap;
}
:is(#roomSettingsModal, #settingsModal) .tableBody .chipRow .chip {
  flex: 0 1 auto;
  min-width: 0;
  padding: 5px 9px;
  font-size: var(--fs-sm);
}
:is(#roomSettingsModal, #settingsModal) .tableBody .chipRow .chip .lbl {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
:is(#roomSettingsModal, #settingsModal) .tableBody .chip .ico {
  width: var(--ico-size-sm);
  height: var(--ico-size-sm);
}
/* Number fields: room for a value, not for the whole row. */
:is(#roomSettingsModal, #settingsModal) .tableBody .sizeRow > input[type='number'] {
  flex: 1 1 0;
  min-width: 0;
  max-width: 110px;
}
:is(#roomSettingsModal, #settingsModal) .tableBody .sizeRow > .stepper {
  flex: 1 1 0;
  min-width: 64px;
  max-width: 128px;
}
:is(#roomSettingsModal, #settingsModal) .tableBody .sizeRow > input[type='text'] {
  flex: 1 1 0;
  min-width: 0;
  max-width: 130px;
}
/* The calibrate prompt wraps under the controls, aligned to the control column. */
:is(#roomSettingsModal, #settingsModal) .tableBody .calibRow {
  flex-wrap: wrap;
}
:is(#roomSettingsModal, #settingsModal) .tableBody .calibHint {
  flex: 1 1 100%;
  margin-left: 98px;
  margin-top: 2px;
  font-size: var(--fs-sm);
  opacity: 0.6;
}
:is(#roomSettingsModal, #settingsModal) .tableBody .dimX,
:is(#roomSettingsModal, #settingsModal) .tableBody .sizeRow > .muted {
  flex: 0 0 auto;
  opacity: 0.6;
}

/* Custom collider authoring runs in the dialog top layer above the asset form. */
.compoundEditor {
  border: 1px solid var(--accent);
  width: min(1100px, 94vw);
  max-height: 94dvh;
  padding: 20px;
  overflow: auto;
}
.compoundEditor::backdrop {
  background: rgba(0, 0, 0, 0.72);
}
.compoundToolbar {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}
.compoundEditorBody {
  display: grid;
  grid-template-rows: auto minmax(0, 1fr) auto;
  min-height: 0;
  overflow: hidden;
}
.compoundLayout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 260px;
  gap: 18px;
}
.compoundViewport {
  height: 420px;
  width: 100%;
}
.compoundViewport canvas {
  display: block;
  width: 100%;
  height: 100%;
  touch-action: none;
}
.compoundEditor aside select {
  width: 100%;
}
.compoundNumbers {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 6px;
}
.compoundNumbers input {
  width: 100%;
  min-width: 0;
  box-sizing: border-box;
}
.compoundEditor [data-field='properties'] > .field-label {
  margin: 10px 0 4px;
}
@media (max-width: 720px) {
  .compoundLayout {
    grid-template-columns: minmax(0, 1fr);
  }
  .compoundViewport {
    height: 300px;
  }
  .compoundEditor {
    padding: 12px;
  }
}

.compoundEditor .modal__footer {
  position: sticky;
  bottom: -12px;
  background: var(--panel-solid);
  padding: 10px 0;
  z-index: 1;
}

.objectColliderRow,
.objectColliderRow .colliderRow {
  flex-wrap: wrap;
}
.objectColliderRow .stepper {
  flex-shrink: 0;
}
.compoundIconButtons {
  gap: 6px;
}
.compoundIconButtons button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  padding: 6px;
}
.compoundIconButtons button[aria-pressed='true'] {
  color: var(--accent);
  border-color: var(--accent);
  background: var(--accent-soft);
}

.compoundToolbar > .field-group {
  padding-top: 8px;
}

.boardOutlineFit {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 8px;
}
.boardOutlineFit .stepper {
  width: 100%;
}
.boardOutlineFit input {
  min-width: 0;
  width: 100%;
}
.compoundIconButtons [data-add-shape] {
  width: 36px;
  height: 36px;
}

#compoundShapeCut {
  max-width: 80px;
}

/* Keep the preview and footer stationary while the controls scroll. */
.compoundEditor[open] {
  display: grid;
  grid-template-rows: auto minmax(0, 1fr) auto;
  height: min(850px, 94dvh);
  overflow: hidden;
  box-sizing: border-box;
}
.compoundLayout {
  min-height: 0;
}
.compoundLayout > div {
  display: flex;
  flex-direction: column;
  min-height: 0;
  overflow: auto;
}
.compoundLayout .compoundViewport {
  flex: 1 1 auto;
  min-height: 100px;
  height: auto;
}
.compoundLayout > aside {
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-gutter: stable;
  padding-right: 6px;
}
.compoundOutline {
  margin: 8px 0;
}
.compoundOutline summary {
  cursor: pointer;
  padding: 6px 0;
}
.compoundEditor .modal__footer {
  position: static;
}
@media (max-width: 720px) {
  .compoundLayout {
    grid-template-rows: minmax(0, 1fr) minmax(0, 1fr);
  }
}
.compoundIconButtons [data-add-shape] {
  width: 34px;
}
@media (max-width: 720px) {
  .compoundToolbar .field-group .compoundIconButtons button {
    width: 32px;
    height: 32px;
    padding: 4px;
  }
  .compoundLayout > div > p {
    display: none;
  }
}

.compoundPresets {
  margin: 12px 0;
  padding: 8px;
  border: 1px solid var(--border);
  border-radius: 6px;
}
.compoundPresets summary {
  cursor: pointer;
}
.compoundPresets .field-group {
  margin: 8px 0;
}
.compoundPresets input,
.compoundPresets select {
  width: 100%;
  box-sizing: border-box;
}
.compoundPresets canvas {
  width: 100%;
  max-width: 280px;
  background: #171c23;
}
.compoundDrawing .field-group {
  margin: 10px 0;
}
.compoundDrawing input[type='number'],
.compoundDrawing select {
  width: 100%;
}
.compoundLayout > div > [data-action='draw'] {
  align-self: flex-start;
  margin-top: 8px;
}

/* Participation restrictions retain camera, chat and read-only inspection. */
.participationNotice {
  position: fixed;
  top: calc(env(safe-area-inset-top, 0px) + 76px);
  inset-inline: 12px;
  margin-inline: auto;
  max-width: 42rem;
  z-index: 30;
  pointer-events: none;
  border-color: var(--accent);
}
.participation-disabled {
  opacity: 0.45;
}

#deckBrowseActions {
  width: min(680px, 94vw);
}

/* Shared collection management and personal filters stay inside the library dialog. */
#libraryModal .libraryBody {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
}
#libraryModal .libraryBody .libPane {
  flex: none;
  overflow: visible;
}
.libraryCollections { padding: var(--gap) var(--inset); }
.libraryCollections summary { cursor: pointer; }
.collectionFilters { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 220px), 1fr)); gap: var(--gap); }
.collectionFilter { display: flex; align-items: center; gap: var(--gap); min-width: 0; }
.collectionFilter > label { flex: 1; }
.collectionFilters { margin-block: var(--gap); }
:is(.libraryCollections, .libraryCollectionPicker) .collectionChoice { display: flex; margin: 0; color: var(--text); align-items: center; gap: var(--gap); overflow-wrap: anywhere; min-width: 0; min-height: var(--control-min-height); }
.collectionChoice input { flex-shrink: 0; }
.collectionManager { display: grid; gap: var(--gap); margin-top: var(--gap); }
.collectionManager[hidden] { display: none; }
.collectionInventory { display: grid; gap: var(--gap); max-height: 32vh; overflow: auto; }
.collectionManager .control { width: 100%; min-width: 0; box-sizing: border-box; }

.collectionControls {
  position: sticky;
  bottom: 0;
  z-index: 1;
  padding-block: var(--gap);
  background: var(--panel-solid);
  border-top: 1px solid var(--border);
}

/* Portable asset preview uses the library's single scrolling body. */
.libraryPackages { padding: var(--gap) var(--inset); }
.libraryPackages summary { cursor: pointer; }
.libraryPackages .control { width: 100%; min-width: 0; box-sizing: border-box; }
.libraryPackages .status-text { overflow-wrap: anywhere; }
.libraryPackages .button-row { flex-wrap: wrap; }

.packageMembers { max-height: min(24vh, 180px); overflow-y: auto; overflow-wrap: anywhere; padding-inline-start: var(--inset); }

/* Account placards reuse the Settings shell on desktop and touch. */
.placardEditor { display: flex; flex-wrap: wrap; align-items: center; gap: var(--gap); }
#placardPreview { width: 144px; max-width: 40%; height: auto; flex: 0 0 auto; }
#placardFields { flex: 1 1 180px; min-width: 0; margin: 0; padding: 0; border: 0; }
#placardFields legend { margin-bottom: 8px; font-weight: bold; }
#placardFields > label { display: block; margin: 8px 0 4px; }
#placardFields select { width: 100%; max-width: 100%; }
#placardFields .chipRow { margin-top: 12px; }
#placardFields .chipRow label { display: inline-flex; align-items: center; gap: 6px; }

/* Private freehand notecard editor; the native dialog contains focus and input. */
.notecard-dialog {
  width: min(960px, calc(100vw - 24px));
  max-width: calc(100vw - 24px);
  max-height: calc(100dvh - 24px);
  margin: auto;
  padding: 16px;
  color: var(--text);
  background: var(--panel-solid);
  border: 1px solid var(--border);
  overflow: auto;
}
.notecard-dialog:not([open]) { display: none; }
.notecard-dialog::backdrop { background: rgba(0, 0, 0, 0.65); }
.notecard-layout { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.1fr); grid-template-rows: min-content 1fr; grid-template-areas: "controls preview" "actions preview"; gap: 16px 20px; align-items: start; }
.notecard-controls { grid-area: controls; min-width: 0; }
.notecard-preview { grid-area: preview; min-width: 0; }
.notecard-actions { grid-area: actions; min-width: 0; }
.notecard-tools { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--gap); margin-bottom: 12px; }
.notecard-paper { flex-basis: 100%; }
.notecard-dialog .button-row--compact button { min-width: 0; font-size: max(11px, var(--fs-sm)); }
.notecard-dialog .button-row--compact .ico { width: var(--ico-size-md); height: var(--ico-size-md); }
.notecard-tools .notecard-shapes, .notecard-navigation { flex-wrap: nowrap; }
.notecard-tools label { display: inline-flex; align-items: center; gap: 6px; }
.notecard-dialog select.control--compact { padding-right: calc(25px * var(--ui-elements-scale)); }
.notecard-ink { width: 32px; min-height: 32px; background: var(--ink); border: 2px solid var(--border); }
.notecard-ink[aria-pressed="true"] { outline: 2px solid var(--text); outline-offset: 2px; }
.notecard-stage { position: relative; width: min(100%, calc(min(600px, 100dvh - 230px) * var(--notecard-aspect, 1.5))); margin-inline: auto; touch-action: none; border-radius: 4px; overflow: hidden; }
#notecardCanvas { display: block; width: 100%; height: auto; aspect-ratio: var(--notecard-aspect, 1.5); touch-action: none; cursor: crosshair; }
.notecard-text-panel { margin-bottom: var(--gap); }
.notecard-text-panel .button-row { flex-wrap: wrap; align-items: center; margin-block: var(--gap); }
.notecard-text-panel label { display: inline-flex; align-items: center; gap: var(--gap); }
.notecard-text-picker { flex: 1; min-width: 140px; }
.notecard-text-picker select { width: 100%; min-width: 0; }
#notecardTextContent { width: 100%; box-sizing: border-box; resize: vertical; }
#notecardTextHelp, #notecardTextError { font-size: max(12px, 0.85em); }
.notecard-text-overlay { position: absolute; inset: 0; pointer-events: none; }
.notecard-text-overlay .notecard-text-box { position: absolute; pointer-events: auto; padding: 0; min-width: 0; min-height: 0; background: transparent; backdrop-filter: none; border: 1px solid transparent; border-radius: 0; box-shadow: none; touch-action: none; }
.notecard-text-overlay .notecard-text-box[aria-pressed="true"] { background: transparent; border: 1px dashed #725217; }
.notecard-text-overlay .notecard-text-box:focus-visible { outline: 2px solid #202830; outline-offset: -3px; }
.notecard-text-overlay .notecard-text-resize { position: absolute; pointer-events: auto; min-width: 30px; min-height: 30px; padding: 4px; transform: translate(-100%, 0); background: var(--panel-solid); touch-action: none; }
@media (pointer: coarse) { .notecard-text-overlay .notecard-text-resize { min-width: 44px; min-height: 44px; } }
.notecard-dialog .modal__footer { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 6px; margin-top: var(--gap); }
@media (pointer: coarse) {
  .notecard-dialog button, .notecard-dialog select { min-height: 44px; }
  .notecard-ink { width: 36px; }
}

.notecard-dialog .modal__header { margin-bottom: 12px; }
.notecard-dialog .modal__header > button { flex-shrink: 0; }

#notecardColors { display: flex; flex-wrap: wrap; gap: 4px; max-width: 100%; }
.notecard-ink { min-width: 0; padding: 0; }

#notecardColors .notecard-ink { width: 28px; min-width: 28px; }
#notecardCustomInk { padding: 2px; background: var(--panel-solid); }

.notecard-navigation { justify-content: center; margin-bottom: var(--gap); align-items: center; }
#notecardZoomLevel { flex: none; font-size: max(11px, var(--fs-sm)); }
.notecard-pass { flex-wrap: wrap; margin-top: var(--gap); align-items: center; }
.notecard-pass select { min-width: 0; max-width: 100%; flex: 1; }
.notecard-dialog [hidden] { display: none; }
#notecardCanvas:focus-visible { outline: 2px solid var(--text); outline-offset: 2px; }
#notecardDrawingHelp { font-size: max(12px, 0.85em); }
.handcard.notecard { width: 120px; background-size: contain; background-repeat: no-repeat; background-position: center; background-color: #fffdf5; }

/* Account-backed notecard templates: compact actions and wrapping native fields. */
.notecard-template-library { margin-bottom: var(--gap); }
#libraryModal #notecardTemplateList { grid-template-columns: repeat(2, minmax(0, 1fr)); align-items: start; }
@media (max-width: 600px) { #libraryModal #notecardTemplateList { grid-template-columns: minmax(0, 1fr); } }
.notecard-template-card { min-width: 0; }
.notecard-template-card .libMeta { flex-direction: column; align-items: flex-start; min-width: 0; }
.notecard-template-card .libName { max-width: 100%; }
.notecard-template-card .hint { margin: 0; overflow-wrap: anywhere; }
.notecard-template-card > .notecard-template-form { grid-column: 1 / -1; }
/* Account templates have their own filter and actions, independent of asset sources/collections. */

.notecard-template-card input[type="number"] { width: 56px; }
.notecard-template-form { border-block: 1px solid var(--border); margin-block: var(--gap); padding-block: var(--gap); }
.notecard-template-form .field-group { margin-block: var(--gap); }
.notecard-template-form input[type="text"], .notecard-template-form input:not([type]), .notecard-template-form select { width: 100%; min-width: 0; }
.notecard-template-form label { overflow-wrap: anywhere; }

.notecard-dialog .modal__title { min-width: 0; overflow-wrap: anywhere; }

@media (max-width: 780px) {
  .notecard-layout { grid-template-columns: minmax(0, 1fr); grid-template-rows: auto; grid-template-areas: "controls" "preview" "actions"; }
  .notecard-dialog .notecard-shapes, .notecard-dialog .notecard-navigation { gap: 4px; }
  .notecard-dialog .notecard-shapes button, .notecard-dialog .notecard-navigation button { padding-inline: 4px; gap: 4px; }
  .notecard-dialog[data-orientation="portrait"] .notecard-stage { width: min(100%, max(190px, calc((100dvh - 380px) * var(--notecard-aspect)))); }
}
@media (pointer: coarse) {
  #notecardColors .notecard-ink { min-width: 40px; min-height: 44px; }
  .notecard-dialog .notecard-shapes button, .notecard-dialog .notecard-navigation button { min-width: 40px; }
}

/* Library categories, local filters and a separate package dialog. */
#libraryModal .libControls { flex-wrap: wrap; }
#libraryModal .libSearchWrap { flex: 1 1 140px; max-width: none; }
#libraryImport { margin-left: auto; }
#libraryModal .libGroup[hidden], #libraryModal .libraryCategory[hidden],
#libraryFilters[hidden] { display: none; }
#libraryModal .libraryCategory { border-bottom: 1px solid var(--border); padding: var(--gap) var(--inset); }
#libraryModal .libraryCategory > summary { cursor: pointer; padding-block: var(--gap); font-weight: 600; min-height: var(--control-min-height); }
#libraryModal .libraryCategory:not([open]) > :not(summary) { display: none; }
#libraryModal .libraryCategory > .libList { margin-top: var(--gap); }
.libraryFilters { display: flex; align-items: flex-start; flex-wrap: wrap; gap: var(--gap); max-height: 30vh; overflow-y: auto; }
.libraryFilters #lib2Source { flex: 0 0 auto; }
.libraryCollectionPicker { border: 1px solid var(--border); border-radius: var(--r); min-width: 0; max-width: 100%; background: var(--panel-solid); }
.libraryCollectionPicker > summary { cursor: pointer; padding: var(--gap) var(--inset); min-height: var(--control-min-height); }
.libraryCollectionPicker > div, .libraryCollectionPicker > p { margin: var(--gap) var(--inset); }
.libraryCollectionPicker .collectionFilters { grid-template-columns: minmax(0, 1fr); }
.collectionEntries { display: grid; gap: var(--gap); margin-block: var(--gap); }
.collectionName { flex: 1; overflow-wrap: anywhere; min-width: 0; }
#assetImportModal { z-index: 121; }
.modalCard.libraryCard.assetImportCard { width: min(560px, calc(100vw - 32px)); max-height: min(84vh, calc(100dvh - 16px)); padding: 0; gap: 0; overflow: hidden; }
#assetImportModal .libraryPackages { padding: 0; }
@media (max-width: 600px) {
  #libraryModal .libSearchWrap { flex-basis: 100%; order: -1; }
  #libraryModal .libTabs { flex-wrap: wrap; overflow: visible; }
}
@media (pointer: coarse) {
  #libraryModal .libraryCategory > summary, .libraryCollectionPicker > summary,
  #libraryFilters button, #libraryModal .libControls button { min-height: 44px; }
  .libraryCollectionPicker .collectionChoice { min-height: 44px; }
}

/* Export confirmations reuse the shared toast, placed above the Library/import overlays. */
#toast.toast-bottom {
  top: auto;
  bottom: calc(env(safe-area-inset-bottom, 0px) + var(--inset));
  z-index: 122;
  max-width: calc(100vw - 2 * var(--inset));
  box-sizing: border-box;
  overflow-wrap: anywhere;
  animation-name: toastBottomIn;
}
@keyframes toastBottomIn {
  from { opacity: 0; transform: translate(-50%, 6px); }
}
@media (prefers-reduced-motion: reduce) {
  #toast.toast-bottom { animation: none; }
}
/* Fine-pointer filters share one control treatment; retain the tested touch layout. */
@media (pointer: fine) {
  #libraryFilters #lib2Source {
    gap: var(--gap);
    border: 0;
    border-radius: 0;
    overflow: visible;
    flex-wrap: wrap;
  }
  #libraryFilters .libraryCollectionPicker { border: 0; background: none; }
  #libraryFilters #lib2Source .chip,
  #libraryFilters .libraryCollectionPicker > summary {
    box-sizing: border-box;
    min-height: var(--control-min-height);
    padding: var(--control-pad-block) var(--control-pad-inline);
    border: 1px solid var(--border);
    border-radius: var(--r);
    background: var(--panel);
    color: var(--text);
    font: 500 var(--fs-sm)/1.5 var(--font);
    opacity: 1;
  }
  #libraryFilters #lib2Source .chip.on {
    color: var(--accent);
    background: var(--accent-soft);
    border-color: var(--accent);
    box-shadow: none;
  }
  #libraryFilters .libraryCollectionPicker > summary:hover,
  #libraryFilters #lib2Source .chip:hover { border-color: var(--accent); }
}

/* Account security keeps essential action names visible in both interface modes. */
#securityView {
  max-width: 480px;
  margin-inline: auto;
}
#securityView .lbl,
#recoverBtn .lbl {
  display: inline;
}
#securityView button,
#recoverBtn {
  white-space: normal;
  min-height: 44px;
}
#securityView [role='status'] {
  min-height: 1.5em;
}
#securityView input[aria-invalid='true'] {
  outline: 2px solid var(--accent);
}
#recoveryCodeList {
  overflow-wrap: anywhere;
  white-space: pre-wrap;
  user-select: text;
  line-height: 1.8;
}
#securityView .button-row {
  flex-wrap: wrap;
}
@media (max-width: 560px), (pointer: coarse) {
  #securityView .button-row {
    flex-direction: column;
    align-items: stretch;
  }
  #securityView button {
    width: 100%;
  }
}
