/* Dicebox — line-work dice on a near-white field.
   The palette is almost entirely value; the one hue is reserved for the total. */

:root {
  --paper:   #FCFCFA;
  --face:    #FFFFFF;
  --line:    #1A1A18;
  --muted:   #9A968C;
  --hair:    #E4E2DC;
  --accent:  #3D5A45;
  --danger:  #8C3A2E;

  --tap: 44px;
  --gut: clamp(16px, 5vw, 28px);
}

:root[data-theme="dark"] {
  --paper:  #141413;
  --face:   #1E1E1C;
  --line:   #F2F0EA;
  --muted:  #6E6A62;
  --hair:   #2A2A27;
  --accent: #8FB79A;
  --danger: #D98878;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --paper:  #141413;
    --face:   #1E1E1C;
    --line:   #F2F0EA;
    --muted:  #6E6A62;
    --hair:   #2A2A27;
    --accent: #8FB79A;
    --danger: #D98878;
  }
}

* { box-sizing: border-box; }

/* The `hidden` attribute is only `display: none` in the UA stylesheet, so any
   class that sets display beats it. Without this, .sheet (display:flex) and
   .help render on load despite being marked hidden — the sheet covered the whole
   page in an unclickable blur. */
[hidden] { display: none !important; }

html, body {
  margin: 0;
  height: 100%;
  overscroll-behavior: none;
}

body {
  background: var(--paper);
  color: var(--line);
  font-family: "Inter Tight", system-ui, -apple-system, sans-serif;
  -webkit-font-smoothing: antialiased;
  -webkit-tap-highlight-color: transparent;
  transition: background 240ms ease, color 240ms ease;
}

.app {
  display: flex;
  flex-direction: column;
  height: 100%;
  max-width: 620px;
  margin: 0 auto;
  padding: env(safe-area-inset-top) var(--gut) calc(env(safe-area-inset-bottom) + 12px);
  gap: 10px;
}

/* ---- header ---- */

.bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-top: 14px;
  flex: 0 0 auto;
}

/* Present on arrival, then out of the way. Once you have rolled, the name has
   done its job and the tray should be the only thing asking for attention. */
.wordmark {
  margin: 0;
  font-size: 15px;
  font-weight: 500;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--muted);
  transition: opacity 1200ms ease;
}
.wordmark[data-faded="1"] { opacity: 0.28; }

@media (prefers-reduced-motion: reduce) {
  .wordmark { transition-duration: 1ms; }
}

.bar-actions {
  display: flex;
  align-items: center;
  gap: 6px;
}

.ghost {
  appearance: none;
  border: 1px solid var(--hair);
  background: none;
  color: var(--muted);
  font: inherit;
  font-size: 12px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  min-height: 32px;
  padding: 0 12px;
  border-radius: 999px;
  cursor: pointer;
}

.ghost.icon {
  width: 32px;
  padding: 0;
  font-size: 14px;
  letter-spacing: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

/* One icon at a time: the sun in light mode, the moon in dark. */
#themeToggle [data-icon] { display: none; }
#themeToggle[data-mode="light"] [data-icon="sun"] { display: block; }
#themeToggle[data-mode="dark"] [data-icon="moon"] { display: block; }

.ghost[aria-expanded="true"] {
  color: var(--accent);
  border-color: var(--accent);
}

/* ---- help ---- */

/* Fills the tray rather than floating over the page: the tray is already a
   bounded rectangle, so nothing below it shifts when the panel opens. */
.help {
  position: absolute;
  inset: 0;
  z-index: 2;
  overflow-y: auto;
  scrollbar-width: none;
  padding: 4px 2px;
  /* Opaque, not blurred: the tray is empty behind it anyway, and a frosted
     layer reads as a different material than the rest of the app. */
  background: var(--paper);
  animation: help-in 180ms ease both;
}
.help::-webkit-scrollbar { display: none; }

@keyframes help-in {
  from { opacity: 0; transform: translateY(-3px); }
  to   { opacity: 1; transform: none; }
}

.syntax {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 1px 14px;
  margin: 0;
  align-items: baseline;
}

/* Each example is tappable, so the reference doubles as a set of presets. */
.syntax dt {
  font-family: "Iosevka Etoile", ui-monospace, "SF Mono", monospace;
  font-size: 13px;
  font-variant-numeric: tabular-nums;
  color: var(--line);
  text-align: right;
  cursor: pointer;
  border-radius: 4px;
  padding: 1px 3px;
  margin: -1px -3px;
  transition: color 140ms ease;
}
.syntax dt:hover,
.syntax dt:focus-visible { color: var(--accent); }

.syntax dd {
  margin: 0;
  font-size: 12.5px;
  color: var(--muted);
}

.help .note {
  margin: 12px 0 0;
  font-size: 12px;
  line-height: 1.5;
  color: var(--muted);
  max-width: 44ch;
}

.help .install {
  margin-top: 10px;
}

.install-button {
  appearance: none;
  border: 1px solid var(--accent);
  border-radius: 8px;
  background: none;
  color: var(--accent);
  font: inherit;
  font-size: 12.5px;
  min-height: 30px;
  padding: 0 12px;
  margin-right: 8px;
  cursor: pointer;
}
.install-button:disabled { opacity: 0.5; }

.help .colophon {
  margin: 10px 0 0;
  font-size: 12px;
}

.colophon-sep {
  color: var(--muted);
  margin: 0 6px;
}

.help .colophon a {
  color: var(--accent);
  text-decoration: none;
  border-bottom: 1px solid currentColor;
  padding-bottom: 1px;
}

/* ---- tray ---- */

.tray {
  position: relative;
  flex: 1 1 auto;
  min-height: 180px;
  touch-action: none;
}

.tray canvas {
  display: block;
  width: 100%;
  height: 100%;
}

/* The one-time intro. Lines fade in one after another, then the whole thing
   leaves — it should read like a title card, not a tooltip. */
.intro {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 7px;
  pointer-events: none;
  text-align: center;
  transition: opacity 700ms ease;
}
.intro[data-gone="1"] { opacity: 0; }

.intro p {
  margin: 0;
  font-size: 13px;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--muted);
  opacity: 0;
  animation: intro-line 620ms ease forwards;
}
.intro p:nth-child(1) { animation-delay: 260ms; }
.intro p:nth-child(2) { animation-delay: 660ms; }
.intro p:nth-child(3) { animation-delay: 1060ms; }

@keyframes intro-line {
  from { opacity: 0; transform: translateY(4px); }
  to   { opacity: 0.8; transform: none; }
}

.intro-last { color: var(--accent) !important; }

@media (prefers-reduced-motion: reduce) {
  .intro p { animation-duration: 1ms; animation-delay: 0ms; opacity: 0.8; }
}

/* ---- readout ---- */

.readout {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  flex: 0 0 auto;
}

.total {
  font-family: "Iosevka Etoile", ui-monospace, "SF Mono", monospace;
  font-size: clamp(56px, 19vw, 104px);
  font-weight: 600;
  line-height: 0.92;
  font-variant-numeric: tabular-nums;
  color: var(--accent);
  transition: opacity 160ms ease;
}
.total[data-idle="1"] { color: var(--muted); }
.total[data-rolling="1"] { opacity: 0.35; }

.breakdown {
  font-size: 13px;
  color: var(--muted);
  text-align: center;
  min-height: 1.3em;
  font-variant-numeric: tabular-nums;
  word-break: break-word;
}

/* ---- dice picker ---- */

.picker {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: 0 0 auto;
}

/* How many dice each tap adds. Typing a number here and tapping d6 is how you
   get 100d6 without tapping a hundred times. */
.counter {
  display: flex;
  align-items: center;
  flex: 0 0 auto;
  border: 1px solid var(--hair);
  border-radius: 8px;
  overflow: hidden;
}

.counter .step {
  border: 0;
  border-radius: 0;
  width: 30px;
  height: 34px;
}

.count {
  width: 34px;
  height: 34px;
  border: 0;
  border-left: 1px solid var(--hair);
  border-right: 1px solid var(--hair);
  background: none;
  color: var(--line);
  font: inherit;
  font-size: 14px;
  font-variant-numeric: tabular-nums;
  text-align: center;
  padding: 0;
  -moz-appearance: textfield;
}
.count::-webkit-outer-spin-button,
.count::-webkit-inner-spin-button { appearance: none; margin: 0; }

.step {
  appearance: none;
  width: 34px;
  height: 34px;
  flex: 0 0 auto;
  border: 1px solid var(--hair);
  border-radius: 8px;
  background: none;
  color: var(--line);
  font: inherit;
  font-size: 18px;
  line-height: 1;
  cursor: pointer;
}

/* The row holds more dice than fit, so it scrolls. Arrows appear at whichever
   end has more to show — a scrollable strip with no visible edge reads as the
   whole set, and everything past the eighth die goes unnoticed. */
.dice-scroll {
  position: relative;
  display: flex;
  align-items: center;
  gap: 2px;
  flex: 1 1 auto;
  min-width: 0;
}

.dice-buttons {
  display: flex;
  gap: 4px;
  overflow-x: auto;
  scrollbar-width: none;
  flex: 1 1 auto;
  scroll-behavior: smooth;
  /* Momentum scrolling on iOS. */
  -webkit-overflow-scrolling: touch;
}
.dice-buttons::-webkit-scrollbar { display: none; }

.nudge {
  appearance: none;
  flex: 0 0 auto;
  width: 22px;
  min-height: 34px;
  border: 0;
  background: none;
  color: var(--muted);
  font-size: 19px;
  line-height: 1;
  padding: 0;
  cursor: pointer;
}
.nudge:hover { color: var(--accent); }

.dbtn {
  appearance: none;
  flex: 0 0 auto;
  min-width: 46px;
  min-height: 34px;
  border: 1px solid var(--hair);
  border-radius: 8px;
  background: none;
  color: var(--line);
  font: inherit;
  font-size: 13px;
  font-variant-numeric: tabular-nums;
  cursor: pointer;
  transition: border-color 160ms ease, color 160ms ease;
}
.dbtn:active { border-color: var(--line); }

/* A die in the tray reads as selected, with its count on the button — the pool
   is visible in the row you are already looking at, not only in the field. */
.dbtn[aria-pressed="true"] {
  color: var(--accent);
  border-color: var(--accent);
}

/* Kept inside the button's padding: the hold-fill needs overflow:hidden, so
   anything poking past the border would be clipped. */
.dbtn[data-count]::after {
  content: attr(data-count) "\00d7";
  position: absolute;
  top: 2px;
  right: 4px;
  font-size: 9.5px;
  line-height: 1;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0;
  color: var(--accent);
  pointer-events: none;
}

/* Make room for the badge so it never sits on the label. */
.dbtn[data-count] {
  padding-right: 14px;
}

/* The modifier's own glyph, bottom-left so it never collides with the count
   badge top-right. A shared underline said "modified" but not which. */
.dbtn[data-mod]::before {
  content: attr(data-mod);
  position: absolute;
  bottom: 1px;
  left: 4px;
  z-index: 1;
  font-size: 9px;
  line-height: 1;
  letter-spacing: 0;
  color: var(--accent);
  pointer-events: none;
}

.dbtn[data-mod] {
  padding-left: 13px;
}

.sheet-option[aria-pressed="true"] .sheet-option-name {
  color: var(--accent);
}

/* ---- custom die wheel ---- */

.dial .sheet-panel {
  display: flex;
  flex-direction: column;
  min-height: 0;
}

/* Scroll-snap does the physics: flick and a number settles under the marker,
   the way a phone's timer picker behaves. */
.wheel {
  flex: 1 1 auto;
  min-height: 96px;
  max-height: 150px;
  overflow-y: auto;
  scroll-snap-type: y mandatory;
  scrollbar-width: none;
  position: relative;
  /* Fades the ends so the list reads as a wheel rather than a cut-off column. */
  mask-image: linear-gradient(transparent, #000 22%, #000 78%, transparent);
  -webkit-mask-image: linear-gradient(transparent, #000 22%, #000 78%, transparent);
  /* Half the visible height, so the first and last items can reach the middle. */
  padding: 56px 0;
}
.wheel::-webkit-scrollbar { display: none; }

.wheel-item {
  scroll-snap-align: center;
  height: 30px;
  line-height: 30px;
  text-align: center;
  font-family: "Iosevka Etoile", ui-monospace, monospace;
  font-size: 15px;
  font-variant-numeric: tabular-nums;
  color: var(--muted);
  opacity: 0.45;
  cursor: pointer;
  transition: color 120ms ease, opacity 120ms ease;
}
.wheel-item[aria-selected="true"] {
  color: var(--accent);
  opacity: 1;
  font-size: 19px;
}

/* The marker: two hairlines the selected number sits between. */
.dial .sheet-panel {
  position: relative;
}
.wheel::before,
.wheel::after {
  content: "";
  position: sticky;
  left: 0;
  display: block;
  height: 1px;
  background: var(--hair);
  z-index: 1;
}
.wheel::before { top: calc(50% - 16px); }
.wheel::after  { bottom: calc(50% - 16px); }

.dial-actions {
  display: flex;
  gap: 8px;
  align-items: center;
  padding: 8px 2px 2px;
}

.dial-entry {
  display: flex;
  align-items: center;
  gap: 2px;
  border: 1px solid var(--hair);
  border-radius: 10px;
  padding: 0 10px;
  min-height: var(--tap);
  font-family: "Iosevka Etoile", ui-monospace, monospace;
  font-size: 15px;
  color: var(--muted);
}

.dial-entry input {
  width: 3.6em;
  border: 0;
  background: none;
  color: var(--line);
  font: inherit;
  font-variant-numeric: tabular-nums;
  padding: 0;
}
.dial-entry input:focus { outline: none; }

.dial-add {
  flex: 1 1 auto;
}

/* The custom-die button reads as an action rather than a die. */
.dbtn.custom {
  color: var(--muted);
  font-family: "Iosevka Etoile", ui-monospace, monospace;
}

/* A die added through the picker. Dashed to show it is not part of the standard
   set, but otherwise a full die: tappable, countable, holdable for modifiers. */
.dbtn[data-custom] {
  border-style: dashed;
}


/* Holding a die fills it, which both signals that a long press does something
   and shows how much longer to hold. The fill is its own element rather than a
   pseudo-element: ::before carries the modifier glyph and ::after the count, and
   a third use would collide with one of them. */
.dbtn {
  position: relative;
  overflow: hidden;
  isolation: isolate;
  /* Left to the browser, so a sideways drag scrolls the row. Claiming the
     gesture for scrubbing instead made the row unscrollable on a phone, which
     put every die past the eighth out of reach — a far worse trade than losing
     drag-to-scrub. Scrubbing now lives behind the long press, where it does not
     compete with panning. */
  touch-action: manipulation;
}

.dbtn-fill {
  position: absolute;
  inset: 0;
  z-index: -1;
  background: color-mix(in srgb, var(--accent) 18%, transparent);
  transform: scaleX(0);
  transform-origin: left;
  pointer-events: none;
}
.dbtn[data-holding="1"] .dbtn-fill {
  transform: scaleX(1);
  transition: transform 450ms linear;
}

@media (prefers-reduced-motion: reduce) {
  .dbtn[data-holding="1"] .dbtn-fill { transition-duration: 1ms; }
}

/* ---- modifier sheet ---- */

/* Fills the tray, like the help panel. A floating card over a dimmed page is a
   different design language than the rest of this app — the tray is the surface
   everything else happens on, so options belong there too. */
.sheet {
  position: absolute;
  inset: 0;
  z-index: 3;
  display: flex;
  flex-direction: column;
  justify-content: flex-start;
  overflow-y: auto;
  scrollbar-width: none;
  background: var(--paper);
  animation: help-in 160ms ease both;
}
.sheet::-webkit-scrollbar { display: none; }

.sheet-panel {
  width: 100%;
  padding: 2px;
  /* Sits toward the top of a tall tray rather than floating in the middle of a
     large empty field, which reads as stranded on a desktop window. */
  margin-top: clamp(0px, 4vh, 28px);
  margin-bottom: auto;
}

.sheet-title {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin: 0 0 8px;
  padding: 0 10px;
  font-family: "Iosevka Etoile", ui-monospace, monospace;
  font-size: 13px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
}

.sheet-close {
  appearance: none;
  border: 0;
  background: none;
  color: var(--muted);
  font: inherit;
  font-size: 13px;
  line-height: 1;
  padding: 6px;
  margin: -6px;
  cursor: pointer;
}

.sheet-options {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.sheet-option {
  appearance: none;
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 0 12px;
  width: 100%;
  text-align: left;
  border: 0;
  border-radius: 8px;
  background: none;
  padding: 9px 10px;
  font: inherit;
  cursor: pointer;
  transition: background 140ms ease;
}
.sheet-option:hover,
.sheet-option:focus-visible {
  background: color-mix(in srgb, var(--accent) 8%, transparent);
}

/* How many of this die are staged. Dragging the button sideways is quicker, but
   only a typed number lands on an exact count. */
.sheet-count {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 4px 10px 10px;
  margin-bottom: 4px;
  border-bottom: 1px solid var(--hair);
}

.sheet-count-label {
  font-size: 14px;
  color: var(--line);
}

.sheet-count-field {
  width: 46px;
}

/* Dragging a die button to dial its count. */
.dbtn[data-scrubbing] {
  border-color: var(--accent);
  color: var(--accent);
}

/* Another modifier already answers this question, so it cannot also apply. */
.sheet-option[data-blocked] {
  opacity: 0.34;
  cursor: default;
}
.sheet-option[data-blocked]:hover { background: none; }

.sheet-option-name {
  font-size: 14px;
  color: var(--line);
}

/* The same mark the die button shows, so the row's glyphs teach themselves. */
.sheet-option-mark {
  display: inline-block;
  width: 1.3em;
  color: var(--accent);
  font-size: 12px;
}

.sheet-option-notation {
  font-family: "Iosevka Etoile", ui-monospace, monospace;
  font-size: 12.5px;
  font-variant-numeric: tabular-nums;
  color: var(--accent);
  text-align: right;
}

.sheet-option-hint {
  grid-column: 1 / -1;
  font-size: 12px;
  color: var(--muted);
  margin-top: 1px;
}

.clear {
  appearance: none;
  flex: 0 0 auto;
  width: var(--tap);
  min-height: var(--tap);
  border: 1px solid var(--hair);
  border-radius: 10px;
  background: none;
  color: var(--muted);
  font: inherit;
  font-size: 13px;
  cursor: pointer;
}

/* ---- notation entry ---- */

.entry {
  display: flex;
  gap: 8px;
  flex: 0 0 auto;
}

.entry input {
  flex: 1 1 auto;
  min-height: var(--tap);
  padding: 0 14px;
  border: 1px solid var(--hair);
  border-radius: 10px;
  background: var(--face);
  color: var(--line);
  font: inherit;
  font-size: 16px; /* 16px keeps iOS from zooming on focus */
}
.entry input::placeholder { color: var(--muted); }
.entry input:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
  border-color: transparent;
}

.roll {
  appearance: none;
  min-height: var(--tap);
  padding: 0 22px;
  border: 0;
  border-radius: 10px;
  background: var(--line);
  color: var(--paper);
  font: inherit;
  font-size: 15px;
  font-weight: 500;
  letter-spacing: 0.04em;
  cursor: pointer;
}
.roll:active { opacity: 0.8; }

.error {
  margin: 0;
  font-size: 13px;
  color: var(--danger);
  text-align: center;
  flex: 0 0 auto;
}

/* ---- history ---- */

/* The recent strip stays short; the full session lives behind its own panel.
   Only the list scrolls, so the "full history" button is always reachable. */
.log {
  flex: 0 0 auto;
}

.log ul {
  max-height: 92px;
  overflow-y: auto;
  scrollbar-width: none;
}
.log ul::-webkit-scrollbar { display: none; }
.log::-webkit-scrollbar { display: none; }

.log ul {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.log li {
  font-size: 12px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  padding: 3px 0;
  border-top: 1px solid var(--hair);
}

/* Notation on the left, total on the right. */
.log-line {
  display: flex;
  justify-content: space-between;
  gap: 12px;
}
.log li b {
  color: var(--line);
  font-weight: 600;
}

.log-more {
  margin: 4px 0 0;
  text-align: center;
}

.log-more-button {
  appearance: none;
  border: 0;
  background: none;
  color: var(--muted);
  font: inherit;
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 3px 6px;
  cursor: pointer;
}
.log-more-button:hover { color: var(--accent); }

.log-count {
  font-variant-numeric: tabular-nums;
  text-transform: none;
  letter-spacing: 0;
  opacity: 0.8;
}

/* ---- full history ---- */

.history-full {
  list-style: none;
  margin: 0;
  padding: 0 2px;
  max-height: 46vh;
  overflow-y: auto;
  scrollbar-width: none;
}
.history-full::-webkit-scrollbar { display: none; }

.history-full li {
  font-size: 12px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  padding: 4px 8px;
  border-bottom: 1px solid var(--hair);
}
.history-full li:last-child { border-bottom: 0; }
.history-full li b { color: var(--line); font-weight: 600; }

.history-time {
  font-family: "Iosevka Etoile", ui-monospace, monospace;
  font-size: 11px;
  opacity: 0.7;
  min-width: 7em;
}

.history-notation {
  flex: 1 1 auto;
  text-align: left;
  padding-left: 10px;
}

.history-empty {
  text-align: center;
  padding: 18px 0 !important;
  opacity: 0.7;
}

.history-actions {
  display: flex;
  gap: 6px;
  padding: 10px 8px 2px;
  border-top: 1px solid var(--hair);
  margin-top: 6px;
}

.history-actions .ghost {
  flex: 1 1 auto;
  min-height: 34px;
  padding: 0 8px;
}

.history-clear {
  flex: 0 0 auto !important;
  color: var(--danger) !important;
}

/* What each die landed on, under its own roll. Quieter than the total, since
   the total is what you look for first and this is what you check afterwards. */
.log-detail {
  display: block;
  font-family: "Iosevka Etoile", ui-monospace, monospace;
  font-size: 11px;
  line-height: 1.35;
  color: var(--muted);
  opacity: 0.75;
  word-break: break-word;
}

/* ---- rooms ---- */

/* The standing answer to "are my rolls leaving this device?". It sits in the
   header rather than inside the panel, because the panel is shut almost all of
   the time and the question is always live. */
.share-flag {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: 10.5px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--accent);
  margin-right: 2px;
}

.share-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--accent);
  animation: share-pulse 2.4s ease-in-out infinite;
}

/* Slow enough to read as "on" rather than as an alert. A steady dot was too
   easy to stop seeing; anything faster looked like something was wrong. */
@keyframes share-pulse {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.35; }
}

@media (prefers-reduced-motion: reduce) {
  .share-dot { animation: none; }
}

/* On a narrow phone the word would push the buttons off the row; the dot alone
   still carries it, and the panel spells it out. */
@media (max-width: 380px) {
  .share-flag-text { display: none; }
}

#roomToggle[data-live] {
  color: var(--accent);
  border-color: var(--accent);
}

/* The before-joining half of the panel. Toggled wholesale against .room-live,
   so it needs a display of its own for [hidden] to have something to beat. */
.room-setup {
  display: block;
}

.room-note,
.room-hint {
  margin: 0;
  font-size: 12px;
  line-height: 1.5;
  color: var(--muted);
  max-width: 46ch;
}

.room-note { padding: 0 10px 12px; }

/* The link sits directly under the note it belongs to, so the note above it
   drops its own bottom padding rather than the pair reading as two blocks. */
.room-note:has(+ .room-note-more) { padding-bottom: 6px; }

.room-block {
  padding: 12px 10px;
  border-top: 1px solid var(--hair);
}

.room-create { width: 100%; }

.room-block .room-hint { margin-top: 8px; }

.room-label {
  display: block;
  margin: 0 0 6px;
  font-family: "Iosevka Etoile", ui-monospace, monospace;
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted);
}

.room-made {
  padding: 12px 10px;
  border-top: 1px solid var(--hair);
}

/* Shown plainly and large enough to read across a table, since reading it out
   loud is how it usually travels. Selectable, because copying by hand is the
   fallback when the clipboard API is unavailable. */
.room-phrase {
  margin: 0 0 10px;
  font-family: "Iosevka Etoile", ui-monospace, monospace;
  font-size: 15px;
  line-height: 1.5;
  color: var(--accent);
  word-spacing: 0.3em;
  word-break: break-word;
  user-select: all;
  -webkit-user-select: all;
}

.room-actions {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
}

.room-actions .ghost {
  flex: 1 1 auto;
  min-height: 34px;
  padding: 0 8px;
}

.room-leave {
  flex: 0 0 auto !important;
  color: var(--danger) !important;
}

.room-join input,
#roomName {
  width: 100%;
  min-height: var(--tap);
  padding: 0 12px;
  margin-bottom: 8px;
  border: 1px solid var(--hair);
  border-radius: 10px;
  background: var(--face);
  color: var(--line);
  font: inherit;
  font-size: 16px; /* 16px keeps iOS from zooming on focus */
}
.room-join input::placeholder { color: var(--muted); }

.room-join-button { width: 100%; }

.room-live {
  padding: 12px 10px;
  border-top: 1px solid var(--hair);
}

.room-roster-label { margin-top: 14px; }

.room-roster {
  list-style: none;
  margin: 0 0 12px;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
  max-height: 26vh;
  overflow-y: auto;
  scrollbar-width: none;
}
.room-roster::-webkit-scrollbar { display: none; }

.room-roster li {
  font-size: 13.5px;
  color: var(--line);
  padding: 5px 2px;
  border-bottom: 1px solid var(--hair);
}
.room-roster li:last-child { border-bottom: 0; }

.room-roster-empty {
  color: var(--muted) !important;
  font-size: 12.5px !important;
}

.room-state {
  margin: 0;
  padding: 12px 10px 2px;
  border-top: 1px solid var(--hair);
  font-size: 12px;
  line-height: 1.5;
  color: var(--muted);
  min-height: 1.5em;
}

/* Someone else's roll. Marked quietly — a name in the accent colour and a rule
   down the left — because the log is read at a glance and a loud treatment for
   half the entries would make the whole strip harder to scan, not easier. */
.log-who {
  color: var(--accent);
  margin-right: 6px;
}

.log li[data-remote] {
  border-left: 2px solid var(--accent);
  padding-left: 6px;
  margin-left: -8px;
}

:where(button, input):focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
  * { transition-duration: 1ms !important; }
}
