/* Dice Tray — docs/dice-tray-spec.md §4.
 *
 * Every class here is prefixed `dt-` and every one of them is emitted by
 * index.html or app.js: `just check-classes` asserts both directions, so a
 * rule for a class nobody writes is as much a failure as a class nobody
 * styles. */

:root {
  --bg: #12131a;
  --panel: #1c1e28;
  --panel2: #232633;
  --ink: #f4f5fa;
  --dim: #a8adc0;
  --line: #333849;
  --hot: #f5b544;          /* the held rim, and the primary button */
  --hotink: #221803;
  --good: #7fd6a2;
  --die: #f7f8fc;          /* a die is white, because a die is white */
  --dieink: #191b24;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  /* R-A8: an app scrolls as a document. `overflow-x: hidden` and NOT `auto` —
     `auto` converts a layout bug into a silent sideways scroll that looks
     correct while the page slides (new-game-preflight.md §5). */
  overflow-x: hidden;
}

#app {
  max-width: 720px;
  margin: 0 auto;
  padding: 10px 12px 0;
  /* version.js floats the chip into this corner in a short landscape window
     and keys its clearance off `#app.verFloat`, so the id has to be here. */
  position: relative;
}

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

#hdr {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 4px 0 8px;
}
.dt-hdrtext { flex: 1 1 auto; min-width: 0; }
#title { margin: 0; font-size: 21px; font-weight: 800; letter-spacing: -0.01em; }
#subtitle { margin: 2px 0 0; font-size: 12px; color: var(--dim); }
.dt-hdracts { display: flex; gap: 6px; flex: 0 0 auto; }

/* 44px is the floor, not a target (R-G26b / apps bar). */
.dt-hdrbtn { width: 44px; height: 44px; padding: 0; font-size: 18px; }

/* ---- buttons ----------------------------------------------------------- */

.dt-btn {
  min-height: 44px;
  min-width: 44px;
  padding: 0 14px;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: var(--panel2);
  color: var(--ink);
  font: inherit;
  font-weight: 700;
  font-size: 15px;
  cursor: pointer;
}
.dt-btn:disabled { opacity: 0.42; cursor: default; }
/* Focus must be VISIBLE (apps bar: any action reachable by pointer should be
   reachable by keyboard, and focus must be visible). */
.dt-btn:focus-visible, .dt-die:focus-visible, .dt-summary:focus-visible,
.dt-input:focus-visible {
  outline: 3px solid var(--hot);
  outline-offset: 2px;
}
.dt-quiet { background: transparent; }
.dt-step { font-size: 24px; line-height: 1; padding: 0; width: 52px; }

/* ---- the status line --------------------------------------------------- */

.dt-note {
  margin: 0 0 6px;
  min-height: 18px;
  font-size: 13px;
  font-weight: 700;
  color: var(--good);
  text-align: center;
}

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

.dt-notation {
  margin: 0 0 2px;
  text-align: center;
  font-size: 15px;
  font-weight: 800;
  letter-spacing: 0.04em;
  color: var(--hot);
  font-variant-numeric: tabular-nums;
}

.dt-tray {
  display: grid;
  gap: 8px;
  justify-content: center;
  margin: 4px 0 10px;
  min-height: 72px;
}

/* A die is a real <button> (R3.1): keyboard reachable, announced as a control,
   measurable by check-tap, and it gets a focus ring without inventing one. */
.dt-die {
  position: relative;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
  line-height: 0;
}
.dt-die svg { display: block; width: 100%; height: 100%; }

/* R3.3: a held die is marked in THREE channels — a gold rim, a 📌 badge, and
   aria-pressed with the word "held" in its name. Never colour alone. */
.dt-pin {
  position: absolute;
  top: -4px;
  right: -4px;
  font-size: 14px;
  line-height: 1;
  pointer-events: none;
}

/* The tumble is a face cycle, not a physics simulation (R5.1) — but it should
   read as a die in the air, so it lifts, turns past the vertical and comes
   back rather than wobbling in place. */
.dt-die { will-change: transform; }
.dt-rolling { animation: dt-shake 170ms cubic-bezier(.45,.05,.55,.95) infinite; }
@keyframes dt-shake {
  0%   { transform: translate3d(0, 0, 0)      rotate(0deg)   scale(1); }
  22%  { transform: translate3d(-3px, -9px, 0) rotate(-14deg) scale(1.05); }
  48%  { transform: translate3d(2px, -3px, 0)  rotate(10deg)  scale(0.97); }
  74%  { transform: translate3d(-2px, -7px, 0) rotate(-7deg)  scale(1.04); }
  100% { transform: translate3d(0, 0, 0)      rotate(0deg)   scale(1); }
}

/* THE LANDING. A die that simply stops has no weight; one that overshoots and
   settles does. Played once, on the die's own result, at the moment it stops
   moving — see the comment in doRoll() for the 294ms of lying this replaced. */
.dt-land { animation: dt-land 260ms cubic-bezier(.2, 1.35, .4, 1) 1; }
@keyframes dt-land {
  0%   { transform: scale(1.18) rotate(-5deg); }
  50%  { transform: scale(0.93) rotate(2deg); }
  78%  { transform: scale(1.03) rotate(-1deg); }
  100% { transform: scale(1)    rotate(0deg); }
}

/* R5.2. A tumble is delight for most people and a problem for some. */
@media (prefers-reduced-motion: reduce) {
  .dt-rolling, .dt-land { animation: none; }
}

/* ---- the result -------------------------------------------------------- */

.dt-sum {
  text-align: center;
  font-size: 14px;
  color: var(--dim);
  min-height: 19px;
  font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
}
.dt-totalrow {
  display: flex;
  align-items: baseline;
  justify-content: center;
  gap: 10px;
  flex-wrap: wrap;
  min-height: 52px;
}
.dt-total { font-size: 44px; font-weight: 800; font-variant-numeric: tabular-nums; }
.dt-swingnote { font-size: 13px; color: var(--dim); }
.dt-struck { text-decoration: line-through; }
/* A total belonging to a roll that is no longer on the tray. Dimmed, not
   blanked: the row keeps its height so nothing jumps, and the reader can see
   that the number is on its way out rather than that it is the answer. */
.dt-stale { opacity: 0.22; transition: opacity 120ms linear; }

/* ---- the controls ------------------------------------------------------ */

.dt-row {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  margin: 8px 0;
}
.dt-wrap { flex-wrap: wrap; }
.dt-count {
  min-width: 108px;
  text-align: center;
  font-weight: 800;
  font-size: 16px;
}

/* R4.5. A flex row whose contents GROW must wrap; only a row of fixed peers
   may stay on one line. */
.dt-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  justify-content: center;
  margin: 8px 0;
}
.dt-chip {
  min-height: 44px;
  min-width: 44px;
  padding: 0 10px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--panel2);
  color: var(--ink);
  font: inherit;
  font-weight: 700;
  font-size: 14px;
  cursor: pointer;
}
/* The chosen chip is marked by weight and a ring as well as colour, and it
   carries aria-pressed — the same three-channel rule as a held die. */
.dt-on {
  background: var(--hot);
  color: var(--hotink);
  border-color: var(--hot);
  box-shadow: inset 0 0 0 2px rgba(0, 0, 0, 0.18);
}
.dt-chipx {
  margin-left: 6px;
  opacity: 0.75;
  font-weight: 800;
}

/* ---- disclosure -------------------------------------------------------- */

.dt-more {
  border: 1px solid var(--line);
  border-radius: 14px;
  background: var(--panel);
  margin: 12px 0;
}
.dt-summary {
  min-height: 44px;
  display: flex;
  align-items: center;
  padding: 0 14px;
  font-weight: 800;
  cursor: pointer;
  border-radius: 14px;
}
.dt-panel { padding: 4px 12px 12px; border-top: 1px solid var(--line); }
.dt-hint { margin: 6px 0 12px; font-size: 12.5px; line-height: 1.5; color: var(--dim); }
.dt-form { display: flex; gap: 8px; margin: 10px 0 0; }
.dt-input {
  flex: 1 1 auto;
  min-width: 0;
  min-height: 44px;
  padding: 0 12px;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: var(--bg);
  color: var(--ink);
  font: inherit;
  font-size: 16px;            /* 16px or iOS zooms the page on focus */
}

/* ---- log and stats ----------------------------------------------------- */

.dt-h3 { font-size: 15px; margin: 16px 0 6px; }

/* ---- the report tabs --------------------------------------------------- */

/* Real buttons with role="tab", so they are keyboard reachable, announced as
   tabs, and measurable by check-tap. The selected one is marked by weight, a
   filled ground AND aria-selected — the same three-channel rule as a held die,
   because "which tab am I on" is exactly the kind of state a colour alone
   should never carry. */
.dt-tabs { display: flex; gap: 6px; margin: 16px 0 8px; }
.dt-tab {
  flex: 1 1 0;
  min-height: 44px;
  padding: 0 8px;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: transparent;
  color: var(--dim);
  font: inherit;
  font-weight: 800;
  font-size: 13.5px;
  cursor: pointer;
}
.dt-tab:focus-visible { outline: 3px solid var(--hot); outline-offset: 2px; }
.dt-tab.dt-on { background: var(--panel2); color: var(--ink); border-color: var(--hot); }
[role="tabpanel"][hidden] { display: none; }

/* THE LOG IS CAPPED AND SCROLLS, at every width and not only on a desktop.
   Fifty rows is 1,850px of page — the chart underneath it ends up somewhere
   nobody scrolls to, and the log's own useful part is the top six rows. The
   cap is six rows; the rest is one flick away inside its own scroller. */
.dt-log {
  list-style: none;
  margin: 0;
  padding: 0;
  max-height: 232px;
  overflow-y: auto;
  overscroll-behavior: contain;
}
.dt-logcount { font-weight: 700; color: var(--hot); font-variant-numeric: tabular-nums; }
.dt-logrow {
  display: flex;
  align-items: baseline;
  gap: 8px;
  padding: 7px 10px;
  border-bottom: 1px solid var(--line);
  font-size: 13px;
}
.dt-logset { font-weight: 800; min-width: 62px; color: var(--dim); }
.dt-logfaces { flex: 1 1 auto; overflow-wrap: anywhere; font-variant-numeric: tabular-nums; }
.dt-logtotal { font-weight: 800; font-size: 16px; font-variant-numeric: tabular-nums; }

.dt-chart { display: flex; align-items: flex-end; gap: 2px; height: 132px; margin: 8px 0 4px; overflow-x: auto; }
/* ANY `display` OF OUR OWN BEATS THE UA'S `[hidden] { display: none }`.
   Measured: `chart.hidden = true` left computed display `flex` and a 132px
   empty box on screen — a third of a phone's height of nothing, under a
   sentence explaining that the chart could not be drawn. Bucketing means the
   chart is never hidden now, and this rule stays so that setting `hidden` can
   never silently do nothing again. */
.dt-chart[hidden] { display: none; }
.dt-bar { flex: 1 0 16px; display: flex; flex-direction: column; height: 100%; }

/* R7.4: observed filled, theoretical as an outline, BOTH STANDING ON THE SAME
   BASELINE. They are absolutely positioned against one plot box rather than
   stacked as flex siblings, and the reason is a measured bug: as siblings in a
   `justify-content: flex-end` column, the "observed exceeds expected" block
   rendered BELOW the outline instead of above it, and the fill inside the
   outline hung from its TOP edge rather than standing on the floor. A bar
   grows up from the axis; nothing else is a bar. */
.dt-plot { position: relative; flex: 1 1 auto; }
.dt-fill {
  position: absolute; left: 0; right: 0; bottom: 0;
  background: var(--hot); border-radius: 3px 3px 0 0;
}
/* The outline sits ON TOP of the fill so it stays readable when the gold
   overtakes it — it is almost all transparent, so nothing is hidden either
   way, and "where the maths says" is never covered by "where you are". */
.dt-ghost {
  position: absolute; left: 0; right: 0; bottom: 0;
  border: 1px dashed var(--dim); border-bottom: 0; border-radius: 3px 3px 0 0;
}
/* R7.5: every bar carries its total. A chart distinguished only by height and
   colour fails the apps accessibility bar. */
.dt-barlab { font-size: 9px; text-align: center; color: var(--dim); padding-top: 2px; }
.dt-summarytext { font-size: 12.5px; color: var(--dim); line-height: 1.5; margin: 4px 0 10px; }

.dt-legend { list-style: none; margin: 0; padding: 0; }
.dt-legendrow { display: flex; align-items: center; gap: 10px; padding: 5px 0; font-size: 13px; }
.dt-legendrow svg { flex: 0 0 auto; width: 34px; height: 34px; }

/* ---- the roll bar ------------------------------------------------------ */

/* R4.4. FIXED, and the first attempt was `position: sticky; bottom: 0`, which
   was wrong in a way worth recording: sticky pins an element inside its own
   containing block AS YOU SCROLL PAST IT. It cannot pull the bar UP from below
   the fold. The bar is the last child of a document taller than the window, so
   on load the primary action — the whole app — was off screen entirely.
   Fixed is therefore out of flow, which makes this a `rail` and not a `pad`
   (R-C24), and the page reserves its height below. */
.dt-rollbar {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 20;
  margin: 0 auto;
  max-width: 720px;
  display: flex;
  gap: 8px;
  padding: 10px 12px calc(10px + env(safe-area-inset-bottom));
  background: linear-gradient(to top, var(--bg) 72%, rgba(18, 19, 26, 0));
}

/* The chip corner is reserved ONLY WHERE THE CHIP IS ACTUALLY IN IT, and from
   the value version.js publishes rather than a constant of our own (R-C24b) —
   the chip grows when an update is waiting, so no fixed number could be right.
   Reserving it unconditionally was the first attempt and cost the primary
   button 66 of its 351px in portrait, where the chip is not floated at all and
   sits at the end of the document like any other footer. */
@media (orientation: landscape) and (max-height: 500px) and (min-width: 600px) {
  .dt-rollbar { padding-right: calc(12px + var(--chipClear, 0px)); }
}
.dt-roll {
  flex: 1 1 auto;
  min-height: 56px;
  font-size: 19px;
  background: var(--hot);
  color: var(--hotink);
  border-color: var(--hot);
}
.dt-free { flex: 0 0 auto; }

/* The fixed bar covers the foot of the document, so the document ends above
   it. 78px is the bar at its tallest (a 56px button plus 20px of padding);
   the safe-area inset is added on top for a phone with a home indicator. */
#footer { padding: 6px 12px calc(78px + env(safe-area-inset-bottom)); }

/* ---- sheets ------------------------------------------------------------ */

.dt-sheet {
  position: fixed;
  inset: 0;
  z-index: 40;
  background: rgba(6, 7, 11, 0.72);
  padding: 16px;
  /* R-C15a. `align-items: flex-start` + `margin: auto` on the card, NOT
     `align-items: center`. Both centre a card that fits; they differ only when
     it does not, and `center` puts half the overflow above scrollTop 0 where
     no gesture reaches it. */
  display: flex;
  align-items: flex-start;
  justify-content: center;
  overflow-y: auto;
  overscroll-behavior: contain;
}
.dt-sheet[hidden] { display: none; }
.dt-card {
  margin: auto;
  width: 100%;
  max-width: 420px;
  /* R-C15c. `overflow-y: auto` and never `hidden`: a cap is a promise about
     size, not a licence to delete. */
  max-height: 100%;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 10px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 18px;
  padding: 16px;
}
.dt-card h2 { margin: 0; font-size: 21px; flex: 0 0 auto; }
.dt-card p { margin: 0 0 10px; font-size: 14px; line-height: 1.55; }
/* The body scrolls; the title and the way out stay pinned. min-height is the
   shrink permission — a flex item defaults to min-height:auto and refuses to
   shrink below its content, so without it the cap is ignored. */
.dt-scroll {
  flex: 0 1 auto;
  min-height: 40px;
  overflow-y: auto;
  overscroll-behavior: contain;
}

.dt-sr {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/* ---- wider windows ----------------------------------------------------- */

/* Apps bar: a phone layout stretched to 1440px is a failure, not a pass. */
@media (min-width: 720px) {
  #title { font-size: 26px; }
  .dt-total { font-size: 58px; }
  .dt-panel { padding: 8px 16px 16px; }
  .dt-chart { height: 168px; }
}

/* Past 1000px the dice and the readouts stop queueing and sit side by side.
   A 720px column centred in a 1440px window is not "stretched", so it would
   pass the letter of the apps rule — and it still spends half the screen on
   nothing while the log scrolls in a narrow strip. Two columns is what the
   room is for.
   The roll bar is `position: fixed` and therefore NOT a grid item; it is
   widened to match separately. */
/* Below 1000px the wrappers are invisible to layout, so the phone stack is
   exactly what it was before they existed. */
.dt-colA, .dt-colB { display: contents; }

@media (min-width: 1000px) {
  #app {
    max-width: 1080px;
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    column-gap: 28px;
    grid-template-areas:
      "hdr  hdr"
      "note note"
      "colA colB";
    align-content: start;
  }
  #hdr  { grid-area: hdr; }
  #note { grid-area: note; }
  .dt-colA { display: block; grid-area: colA; }
  .dt-colB { display: block; grid-area: colB; }
  .dt-rollbar { max-width: 1080px; }
  /* The log is the one thing that grows without limit, so it gets a bounded
     scroller rather than a column that runs past the fold. */
  .dt-log { max-height: 340px; overflow-y: auto; overscroll-behavior: contain; }
}
