/* ============================================================
   dice.css — a roll, in the three places it is seen

   The big number in the middle of the screen and the flight that carries it to
   the corner; the stack of your last three rolls it lands in; the card that
   opens when one is hovered; the advantage wheel a held press opens; and the
   speech bubble another player's roll pops over their tab. The dice dock in the
   middle panel's top right is here too — it is the other thing you roll *from*.

   Not here: a roll drawn in the chat log, which is chat.css's, because the
   element belongs to a chat message; and the sheet's rollable rows, which are
   character.css's for the same reason.
   ============================================================ */

/* ─── THE STAGE ──────────────────────────────────────────── */
/* A fixed layer over the whole app and under the modals. It never takes a
   pointer event: a number crossing the screen must not be able to eat a click
   on its way past. */
#dice-stage {
  position: fixed;
  inset: 0;
  z-index: 1500;
  pointer-events: none;
}

/* ─── THE FLIER ──────────────────────────────────────────── */
/* Built *at rest* — centred and nothing more — so its own transform stays free
   for the flight and its bounding box is truthful at the moment the flight is
   measured. The entrance is animated on the inner element instead. */
.roll-flier {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  isolation: isolate; /* keeps the glow below the number, not behind the app */
  text-align: center;
  will-change: transform, opacity;
}

.roll-flier.flying {
  transition-property: transform, opacity;
  /* Leans back before it goes, then eases in hard — a thrown thing, not a
     slide. The duration is set from ROLL_FLIGHT_MS in dice.js. */
  transition-timing-function: cubic-bezier(0.55, -0.14, 0.2, 1);
}

/* The corner is not on screen — the home page is in front of it, or the history
   was cleared mid-flight. Fade where it stands rather than fly at nothing. */
.roll-flier.gone {
  transition: opacity 0.26s ease, transform 0.26s ease;
  transform: translate(-50%, -50%) scale(0.84);
  opacity: 0;
}

.roll-flier-inner {
  position: relative;
  padding: 26px 46px;
  animation: roll-arrive 560ms cubic-bezier(0.16, 1.3, 0.36, 1) both;
}

/* Enough ground for the number to read against, whatever it lands over — the
   torn paper, the grid, a character sheet. A wash rather than a panel: this is
   a result passing through, not a dialog.

   **It hangs off the flier, not off the inner element**, and that is the whole
   of what keeps it still while the number tumbles. The two transforms do one
   job each: the flier's carries both of them to the corner, so the wash travels
   with the number it is the ground for; the inner's does the entrance tumble
   alone, so the number turns and the light behind it does not. A spinning disc
   of light is a different effect, and a much sillier one.

   Its size is fixed rather than `inset`-relative, because the flier's box grows
   and shrinks with the digit count as the number spins — a wash measured off it
   would breathe in and out for the whole of the tumble.

   The colour is `--panel`, and that matters more than it looks. The label and
   the detail below the number are `--text` and `--text-dim`, which are the inks
   each palette pins its *panels'* lightness against — so a bloom of anything
   else is a surface those two colours were never chosen for. `--bg` was the
   obvious first guess and is exactly wrong: the ground is locked dark in both
   palettes, so on parchment it laid a dark smudge over the paper and took the
   detail line down with it. */
.roll-flier::before {
  content: '';
  position: absolute;
  left: 50%;
  top: 50%;
  /* Wider than it is tall, because what it has to cover is: a number with a
     label and a line of arithmetic under it. `closest-side` on a non-square box
     draws an ellipse, so the shape follows from the two lengths and nothing
     has to describe it twice. */
  width:  min(78vmin, 680px);
  height: min(46vmin, 400px);
  z-index: -1;
  /* Three stops rather than two: a near-solid middle for the number to sit on,
     and a long tail so the edge of the wash is nowhere in particular. Two stops
     start falling off immediately and leave the digits reading through the
     busiest part of whatever is behind them. */
  background: radial-gradient(closest-side,
    color-mix(in srgb, var(--panel) 95%, transparent) 0%,
    color-mix(in srgb, var(--panel) 90%, transparent) 45%,
    color-mix(in srgb, var(--panel) 60%, transparent) 72%,
    transparent 100%);
  animation: roll-wash-in 420ms ease both;
}

/* Two numbers need more ground than one. Only the width changes: the row is
   wider, not taller, and stretching the ellipse vertically as well would push
   its soft edge out past the sheet it is supposed to be sitting on. */
.roll-flier.paired::before { width: min(92vmin, 840px); }

/* The centring translate has to be repeated in both frames: an animated
   `transform` replaces the property outright, so leaving it out of the
   keyframes would drop the wash a quarter-screen down and right. */
@keyframes roll-wash-in {
  from { opacity: 0; transform: translate(-50%, -50%) scale(0.72); }
  to   { opacity: 1; transform: translate(-50%, -50%) scale(1); }
}

@keyframes roll-arrive {
  0%   { transform: scale(0.3) rotate(-16deg); opacity: 0; }
  55%  { transform: scale(1.14) rotate(4deg);  opacity: 1; }
  100% { transform: scale(1) rotate(0deg);     opacity: 1; }
}

/* One row holding one number, or the two an advantage roll produced. A single
   roll is the same structure with one child, so nothing in the JS has two
   shapes to keep in step. */
.roll-flier-totals {
  display: flex;
  align-items: baseline;
  justify-content: center;
  gap: clamp(26px, 5vmin, 58px);
}

.roll-flier-total {
  /* The width is set per roll in dice.js, from the widest total the pool can
     reach. `flex: none` so the row honours it exactly, and tabular figures so a
     `ch` really is one digit and the numerals do not shuffle within the box
     either. Between them the row's layout is fixed for the whole tumble, which
     also holds the label and the detail line still underneath it. */
  flex: none;
  font-variant-numeric: tabular-nums;
  font-family: var(--font-display);
  font-size: clamp(76px, 15vmin, 148px);
  font-weight: 700;
  line-height: 1;
  color: var(--accent);
  text-shadow: 0 0 34px color-mix(in srgb, var(--accent) 55%, transparent),
               0 6px 20px var(--emboss-lo);
  /* Both halves of the judgement move by transform only, so the row never
     reflows: the loser shrinks where it stands and the winner slides to the
     middle, and the flier's box — which the flight is measured against a
     moment later — stays exactly where it was. */
  transform-origin: 50% 60%;
  transition: transform 0.42s cubic-bezier(0.3, 0.9, 0.3, 1),
              color 0.34s ease, opacity 0.34s ease, text-shadow 0.34s ease;
}

/* The die that lost. Greyed and shrunk rather than removed: what advantage
   *did* is only legible if the thing it discarded is still there beside what
   it kept. It goes altogether once the winner leaves for the corner. */
.roll-flier-total.dropped {
  color: var(--text-dim);
  opacity: 0.4;
  /* Half size. It has to clear the winner sliding into the middle of the row,
     and with three digits a side even an outward-anchored 0.62 still collided;
     half also reads more plainly as the discarded one. */
  transform: scale(0.5);
  text-shadow: none;
}

/* It shrinks **away from its neighbour**, not about its own middle, so the gap
   between the two opens rather than staying put. The winner is sliding into the
   middle of the row at the same moment, which for wide numbers means straight
   through where the loser used to be — three digits a side and the two would
   otherwise collide. Anchoring the shrink to the outer edge buys the clearance
   for free, with nothing measured and no second transform to keep in step.

   With the shrink at exactly a half, the clearance works out to half the row's
   gap and **stays there whatever the numbers are**: the winner's leading edge
   lands at `rowCentre + w/2` and the loser's at `2w + gap − w/2`, so the width
   cancels. That is the reason for the half. At 0.62 the clearance was
   `gap/2 − 0.12w`, which is comfortable for two digits and gone by three.

   Invisible at the start of the transition, where the scale is still 1 and an
   origin makes no difference to an identity transform. */
.roll-flier-total.dropped:first-child { transform-origin: 0% 60%; }
.roll-flier-total.dropped:last-child  { transform-origin: 100% 60%; }
.roll-flier.flying .roll-flier-total.dropped { opacity: 0; }

/* Not uppercased, here or in the chip or the bubble. The label arrives already
   cased by dice.js — "Arcana", "Dexterity Save" — and a tray roll's label is a
   formula, which `text-transform` would render as "1D20". Letter-spacing and
   the display face do the work of making it read as a caption instead. */
.roll-flier-label {
  margin-top: 8px;
  font-family: var(--font-display);
  font-size: clamp(15px, 2.4vmin, 22px);
  font-weight: 700;
  letter-spacing: 0.15em;
  color: var(--text);
}

.roll-flier-detail {
  margin-top: 5px;
  font-size: 11px;
  letter-spacing: 0.07em;
  color: var(--text-dim);
  transition: opacity 0.25s ease;
}

/* Held back until the answer is final — not merely until the dice stop, which
   for an advantage roll is two beats earlier. The line names every die and says
   which was dropped, so it would settle the question while both numbers are
   still standing there undecided. `visibility` rather than `display`, so the
   flier's height does not jump when it arrives. */
.roll-flier:not(.settled) .roll-flier-detail { opacity: 0; visibility: hidden; }

/* The beat as the dice stop, which is `locked` rather than `settled`: on an
   advantage roll both numbers land together and only one of them then wins.
   Small — the number has been moving for up to three seconds, and what marks
   the end is it *stopping*.

   **The two selectors are the same beat under different endings**, and the
   split is load-bearing. An animation beats an inline style, so for as long as
   this one runs it owns `transform` — which is exactly what a paired roll's
   resolve then uses to shrink the loser and slide the winner into the middle.
   It finishes well before that in the ordinary way of things, but "well
   before" is a hundred-odd milliseconds of one timing constant happening to be
   larger than another, and nothing says so out loud; ending it at `settled`
   makes the two unable to overlap at all. A straight roll cannot use that
   ending, because there `settled` follows `locked` in the same breath and the
   beat would be cut off before it played — and it needs no guard anyway, since
   nothing sets a transform on it afterwards. */
.roll-flier.locked:not(.paired) .roll-flier-total,
.roll-flier.locked.paired:not(.settled) .roll-flier-total {
  animation: roll-lock 320ms cubic-bezier(0.2, 1.5, 0.4, 1);
}
@keyframes roll-lock {
  0%   { transform: scale(1.18); }
  100% { transform: scale(1); }
}

/* A natural 20 and a natural 1 are the two results worth recognising before the
   number is read. Colour *and* the word in the detail line, never colour alone.

   **Gated on `.settled`, which is the whole point of the rule.** The class is
   on the flier from the moment it is built, but a number that turns green as it
   appears has announced it is a 20 before it has finished deciding to be one —
   the suspense given away in the first frame. So the colour arrives when the
   answer does: at the end of the tumble for a straight roll, and after the
   grey-out for an advantage one. `:not(.dropped)` keeps it off the die that
   lost, however good that die was. */
.roll-flier.settled.crit .roll-flier-total:not(.dropped) {
  color: var(--crit);
  text-shadow: 0 0 40px color-mix(in srgb, var(--crit) 62%, transparent),
               0 6px 20px var(--emboss-lo);
}
.roll-flier.settled.fumble .roll-flier-total:not(.dropped) {
  color: var(--fumble);
  text-shadow: 0 0 34px color-mix(in srgb, var(--fumble) 50%, transparent),
               0 6px 20px var(--emboss-lo);
}

/* ─── THE CORNER ─────────────────────────────────────────── */
/* Your last three rolls, newest at the bottom — the direction they arrive in,
   so a new one pushes the older ones up and away rather than shunting them
   down out of the reader's eye.

   It sits inside #inventory-panel because a roll is the *app's* answer rather
   than one panel's, and because the middle is where a flung number can be
   thrown to from anywhere. z-index 7 clears #gm-placeholder (5) and the
   character tabs (6), so a GM sees their rolls too. `pointer-events: none`
   throughout: it can overlap the stash, and the stash stays clickable. */
#dice-history {
  position: absolute;
  right: 18px;
  bottom: 16px;
  z-index: 7;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 5px;
  pointer-events: none;
}

.roll-chip {
  display: flex;
  align-items: baseline;
  gap: 9px;
  padding: 5px 11px;
  background: var(--panel);
  border: 1px solid var(--border2);
  /* The marked edge is on the right because the stack hangs off the right —
     a rule on the far side of a right-hung chip points back at nothing. */
  border-right: 3px solid var(--accent);
  border-radius: 4px;
  box-shadow: var(--shadow-soft);
  /* They shrink *toward* the corner as they age, rather than away from it. */
  transform-origin: 100% 100%;
  transition: opacity 0.32s ease, transform 0.32s ease;
  /* The one part of the stack that takes a pointer: hovering a chip opens the
     working behind it. The container stays `none`, so the only thing between
     the reader and the stash underneath is three small chips in the corner. */
  pointer-events: auto;
  cursor: help;
}
/* An older chip is faded to say it is older, which makes it hard to read the
   moment it is the one being asked about. Scoped through the id so it outranks
   the age rules below whatever order they end up in. */
#dice-history .roll-chip:hover { opacity: 1; }

/* Age, not position: the newest is at full strength and each one behind it
   steps back, so the stack reads as time without needing a timestamp. */
.roll-chip.age-0 { opacity: 1;    transform: scale(1); }
.roll-chip.age-1 { opacity: 0.52; transform: scale(0.9); }
.roll-chip.age-2 { opacity: 0.24; transform: scale(0.82); }

/* Drawn, measured, and held invisible until the flier arrives on top of it —
   the number is never in two places at once, and never in neither.
   `pointer-events: none` matters as much as the opacity: an invisible chip
   still has a box, and hovering it would open the working — the whole answer —
   while the number in the middle of the screen is still tumbling towards it. */
.roll-chip.landing {
  opacity: 0;
  transform: scale(0.72);
  transition: none;
  pointer-events: none;
}

.roll-chip-total {
  font-family: var(--font-display);
  font-size: 19px;
  font-weight: 700;
  line-height: 1;
  color: var(--accent);
}

.roll-chip-label {
  font-family: var(--font-display);
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.05em;
  color: var(--text-dim);
  max-width: 190px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.roll-chip.crit   { border-right-color: var(--crit); }
.roll-chip.fumble { border-right-color: var(--fumble); }
.roll-chip.crit   .roll-chip-total { color: var(--crit); }
.roll-chip.fumble .roll-chip-total { color: var(--fumble); }

/* ─── SPEECH BUBBLES OVER THE TABS ───────────────────────── */
/* A fixed layer rather than children of the tabs: #character-tabs scrolls
   sideways and clips its overflow, so a bubble hanging under a tab would be
   sliced off at the strip's own edge. Under the stage, over everything else. */
#dice-tab-bubbles {
  position: fixed;
  inset: 0;
  z-index: 1400;
  pointer-events: none;
}

.tab-bubble {
  position: absolute;
  display: flex;
  align-items: baseline;
  gap: 7px;
  padding: 5px 10px;
  background: var(--panel);
  border: 1px solid var(--accent);
  border-radius: 4px;
  box-shadow: var(--shadow-deep);
  white-space: nowrap;
  /* Pops in, holds, then leaves on its own. The delays are set from JS against
     the bubble's real age, so a roster update redrawing the strip mid-life puts
     it back where it was in its life rather than starting it over. */
  animation: bubble-in 220ms cubic-bezier(0.2, 1.35, 0.4, 1) both,
             bubble-out 400ms ease both;
}

/* The tail points back at the middle of the tab even when the bubble has been
   clamped sideways to stay on screen, so it never points at the wrong player. */
.tab-bubble::before {
  content: '';
  position: absolute;
  top: -5px;
  left: var(--tail-x, 50%);
  width: 8px;
  height: 8px;
  margin-left: -4px;
  background: var(--panel);
  border-left: 1px solid var(--accent);
  border-top: 1px solid var(--accent);
  transform: rotate(45deg);
}

@keyframes bubble-in {
  from { opacity: 0; transform: translateY(-6px) scale(0.88); }
  to   { opacity: 1; transform: none; }
}
@keyframes bubble-out {
  from { opacity: 1; transform: none; }
  to   { opacity: 0; transform: translateY(-4px) scale(0.96); }
}

.tab-bubble-total {
  font-family: var(--font-display);
  font-size: 16px;
  font-weight: 700;
  line-height: 1;
  color: var(--accent);
}

.tab-bubble-label {
  font-family: var(--font-display);
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.05em;
  color: var(--text-dim);
}

/* A chat line: wraps inside a fixed width rather than running off as one long
   line. Its height is bounded by the cut in JS (TAB_BUBBLE_MAX_CHARS), not a
   line clamp — that needs overflow: hidden, which would clip the tail. */
.tab-bubble.speech {
  display: block;
  max-width: 240px;
  font-family: var(--font-body);
  font-size: 12.5px;
  line-height: 1.35;
  color: var(--text);
  white-space: normal;
  overflow-wrap: anywhere;
}

.tab-bubble.crit   { border-color: var(--crit); }
.tab-bubble.fumble { border-color: var(--fumble); }
.tab-bubble.crit::before   { border-color: var(--crit); }
.tab-bubble.fumble::before { border-color: var(--fumble); }
.tab-bubble.crit   .tab-bubble-total { color: var(--crit); }
.tab-bubble.fumble .tab-bubble-total { color: var(--fumble); }

/* ─── THE ADV / DIS TAG ──────────────────────────────────── */
/* One pill, four surfaces — the corner chip, the tab bubble, the chat card and
   the flier — so a mode cannot end up shown four different ways. Built by
   `rollModePill()` in dice.js, which is why it lives here rather than in
   chat.css even where it sits inside a chat message. */
.roll-mode-pill {
  flex-shrink: 0;
  padding: 1px 5px;
  font-family: var(--font-body);
  font-size: 8.5px;
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  line-height: 1.5;
  border-radius: 2px;
  /* Advantage reads up and disadvantage down, which is the only pair of
     directions either word has. Colour carries it too, but never alone. */
  color: var(--on-accent);
  background: var(--accent);
}
.roll-mode-pill.adv::before { content: '▲ '; }
.roll-mode-pill.dis::before { content: '▼ '; }
.roll-mode-pill.dis { background: var(--danger); color: var(--paper); }

/* On the flier it carries the whole word and sits beside the label, so it is
   sized to the label rather than to a chip. */
.roll-flier-label .roll-mode-pill {
  margin-left: 9px;
  font-size: 10px;
  vertical-align: 0.16em;
}

/* ─── THE ADVANTAGE WHEEL ────────────────────────────────── */
/* Two options either side of the cursor, opened by holding a press on any roll
   target. Above the stage, because a wheel opened while an earlier roll is
   still crossing the screen has to be the thing in front. It never takes a
   pointer event: the gesture is driven by the captured pointer on the button
   that started it, and an option that could be clicked would be a second,
   contradictory way to choose one. */
#roll-wheel {
  position: fixed;
  inset: 0;
  z-index: 1700;
  pointer-events: none;
  /* The wheel's geometry, declared once and read by both halves: the rules
     below place the options from it, and `openRollWheel()` reads it back to
     work out how far the wheel reaches and therefore how far it may be nudged
     to stay on screen. Written in the JS as well, the two would drift the first
     time an option's width changed. */
  --wheel-w: 66px;   /* one option */
  --wheel-gap: 16px; /* origin to the near edge of each option */
  --wheel-h: 40px;
}

/* Zero-sized, so its children can be placed in plain coordinates around the
   origin rather than each carrying its own half-width correction. */
.roll-wheel-anchor {
  position: absolute;
  left: var(--wx, 50%);
  top: var(--wy, 50%);
  width: 0;
  height: 0;
}

/* The dot under the cursor: the middle, and what letting go here means — a
   straight roll. It closes up when a side is chosen, which is the one piece of
   feedback saying the middle is no longer where you are. */
.roll-wheel-hub {
  position: absolute;
  left: -5px;
  top: -5px;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--panel);
  border: 1.5px solid var(--accent);
  box-shadow: var(--shadow-soft);
  transition: transform 0.14s ease, opacity 0.14s ease;
}
#roll-wheel.picked .roll-wheel-hub { transform: scale(0.5); opacity: 0.45; }

.roll-wheel-opt {
  position: absolute;
  top: calc(var(--wheel-h) / -2);
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 5px;
  width: var(--wheel-w);
  height: var(--wheel-h);
  font-family: var(--font-display);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  white-space: nowrap;
  color: var(--text-dim);
  background: var(--panel);
  border: 1px solid var(--border2);
  border-radius: 4px;
  box-shadow: var(--shadow-deep);
  transition: color 0.12s, background 0.12s, border-color 0.12s, transform 0.14s ease;
  animation: wheel-in 150ms cubic-bezier(0.2, 1.3, 0.4, 1) both;
}

/* The gap either side is a shade under the deadzone in dice.js (26px), so the
   middle you can let go in is very nearly the middle you can see. */
.roll-wheel-opt.dis {
  left: calc(-1 * (var(--wheel-gap) + var(--wheel-w)));
  animation-name: wheel-in-left;
}
.roll-wheel-opt.adv { left: var(--wheel-gap); animation-name: wheel-in-right; }

/* What the two abbreviations mean, in full, under the hub — and "Straight roll"
   when neither is picked, because letting go in the middle is a choice rather
   than the absence of one. Centred on the origin, so it costs the wheel no
   width where width is the thing in short supply. */
.roll-wheel-caption {
  position: absolute;
  left: 50%;
  top: calc(var(--wheel-h) / 2 + 8px);
  transform: translateX(-50%);
  padding: 2px 8px;
  font-size: 9.5px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  white-space: nowrap;
  color: var(--text-dim);
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 3px;
  box-shadow: var(--shadow-soft);
  animation: wheel-caption-in 150ms ease both;
}
#roll-wheel.picked .roll-wheel-caption { color: var(--text); }

@keyframes wheel-caption-in {
  from { opacity: 0; transform: translateX(-50%) translateY(-5px); }
  to   { opacity: 1; transform: translateX(-50%) translateY(0); }
}

.roll-wheel-opt.active {
  color: var(--on-accent);
  background: var(--accent);
  border-color: var(--accent);
  transform: scale(1.06);
}
.roll-wheel-opt.dis.active {
  color: var(--paper);
  background: var(--danger);
  border-color: var(--danger);
}

.roll-wheel-glyph { font-size: 9px; opacity: 0.75; }
.roll-wheel-opt.active .roll-wheel-glyph { opacity: 1; }

/* They arrive from the middle, so the wheel reads as opening out of the point
   that was pressed rather than as two labels appearing beside it. */
@keyframes wheel-in-left {
  from { opacity: 0; transform: translateX(26px) scale(0.9); }
  to   { opacity: 1; transform: none; }
}
@keyframes wheel-in-right {
  from { opacity: 0; transform: translateX(-26px) scale(0.9); }
  to   { opacity: 1; transform: none; }
}

/* ─── THE WORKING, ON HOVER ──────────────────────────────── */
/* Every die, what advantage discarded, and each score and proficiency that made
   up the modifier. A card rather than a `title`, because the content is a
   table — and because a native tooltip cannot strike through the die that was
   thrown away, which is the one thing an advantage roll needs shown. */
#roll-detail {
  position: fixed;
  z-index: 1650;
  min-width: 186px;
  max-width: 260px;
  padding: 9px 11px;
  background: var(--panel);
  border: 1px solid var(--border2);
  border-radius: 5px;
  box-shadow: var(--shadow-deep), inset 0 0 0 1px var(--emboss-hi);
  /* A readout, never a target: the chip beneath it is what is being hovered,
     and a card that could take the pointer would flicker as the cursor crossed
     onto it. */
  pointer-events: none;
}

.roll-detail-head {
  display: flex;
  align-items: baseline;
  gap: 7px;
  padding-bottom: 6px;
  margin-bottom: 6px;
  border-bottom: 1px solid var(--border);
}
.roll-detail-name {
  flex: 1;
  min-width: 0;
  font-family: var(--font-display);
  font-size: 12px;
  font-weight: 700;
  color: var(--accent);
  overflow-wrap: anywhere;
}

/* Dice in one group, the modifier's parts in another, the total in the last —
   ruled off from each other, because they are three different kinds of number
   and adding them up in your head is the point of the card. */
.roll-detail-group + .roll-detail-group {
  padding-top: 5px;
  margin-top: 5px;
  border-top: 1px solid var(--border);
}

.roll-detail-row {
  display: flex;
  align-items: baseline;
  gap: 8px;
  font-size: 11px;
  line-height: 1.7;
  color: var(--text-dim);
}
.roll-detail-label { flex-shrink: 0; }
/* "score 14", "lower" — the aside that makes a row's number mean something.
   It takes the slack so the values stay in a column down the right. */
.roll-detail-note {
  flex: 1;
  min-width: 0;
  font-size: 9.5px;
  font-style: italic;
  text-align: right;
  opacity: 0.75;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.roll-detail-value {
  flex-shrink: 0;
  min-width: 26px;
  font-weight: 700;
  text-align: right;
  color: var(--text);
  font-variant-numeric: tabular-nums;
}

/* The pool advantage threw away. Struck through and dimmed: it was rolled, it
   is real, and it did not count — all three have to read at once. */
.roll-detail-row.dropped { opacity: 0.5; }
.roll-detail-row.dropped .roll-detail-value { text-decoration: line-through; }

.roll-detail-row.crit   .roll-detail-value { color: var(--crit); }
.roll-detail-row.fumble .roll-detail-value { color: var(--fumble); }

.roll-detail-group.total .roll-detail-row { font-size: 12px; }
.roll-detail-group.total .roll-detail-label {
  font-family: var(--font-display);
  font-weight: 700;
  color: var(--text);
}
.roll-detail-group.total .roll-detail-value {
  font-family: var(--font-display);
  font-size: 15px;
  color: var(--accent);
}

/* ─── THE DICE DOCK ──────────────────────────────────────── */
/* A zero-height row in #inventory-panel's column, placed after every tab strip
   and banner, so its button always hangs just under whatever is showing above
   the content — no measuring. z-index 7 clears #gm-placeholder (5), as the
   corner stack does, so a GM with nobody picked can still roll. It stands down
   on the battle map, whose top-right corner is the speed badge's. */
#dice-dock {
  position: relative;
  height: 0;
  flex-shrink: 0;
  z-index: 7;
}
#inventory-panel.map-view #dice-dock { display: none; }

/* The map button's twin, in the opposite corner. */
#dice-dock-btn {
  position: absolute;
  top: 10px;
  right: 12px;
  width: 42px;
  height: 42px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 20px;
  color: var(--accent);
  background: var(--panel);
  border: 1px solid var(--border2);
  box-shadow: var(--shadow-soft);
  transition: transform 0.15s, border-color 0.15s, color 0.15s, background 0.15s;
}
#dice-dock-btn:hover,
#dice-dock.open #dice-dock-btn {
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 12%, var(--panel));
}
#dice-dock-btn:hover { transform: translateY(-2px); }
#dice-dock-btn:focus-visible { outline: 1px solid var(--accent); outline-offset: 2px; }
#dice-dock-btn .ico { width: 1.05em; height: 1.05em; }

/* The column, dropped straight down from the button and centred under it. The
   panel clips overflow, so a short window scrolls the column rather than
   losing the Roll button off the bottom. */
#dice-dock-panel {
  position: absolute;
  top: 60px;
  right: 7px;
  width: 52px;
  max-height: calc(100vh - 170px);
  overflow-y: auto;
  scrollbar-width: none;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 6px;
  padding: 6px 4px;
  background: var(--panel);
  border: 1px solid var(--border2);
  border-radius: 26px;
  box-shadow: var(--shadow-soft);
}
#dice-dock-panel::-webkit-scrollbar { display: none; }

#dice-dock-faces {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

/* The button stays a plain rectangle — the hexagon is the face inside it, so
   the focus ring is not clipped away with the corners. */
.die-btn {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border-radius: 4px;
}
.die-btn:focus-visible { outline: 1px solid var(--accent); outline-offset: 2px; }

.die-face {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  aspect-ratio: 1;
  font-family: var(--font-display);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.01em;
  color: var(--accent);
  background: color-mix(in srgb, var(--accent) 13%, var(--surface));
  /* A flat-sided hexagon: enough of a die to read as one at this size, and one
     shape for all seven rather than seven near-identical polygons. */
  clip-path: polygon(50% 0%, 93% 25%, 93% 75%, 50% 100%, 7% 75%, 7% 25%);
  transition: background 0.13s, color 0.13s, transform 0.09s;
}
.die-btn:hover .die-face {
  background: color-mix(in srgb, var(--accent) 30%, var(--surface));
  color: var(--text);
}
.die-btn:active .die-face { transform: scale(0.94); }

/* In the handful: the face fills in. */
.die-btn.picked .die-face {
  background: var(--accent);
  color: var(--on-accent);
}

/* How many, once it is more than one — outside the hexagon's clip. */
.die-count {
  position: absolute;
  top: -2px;
  right: -2px;
  min-width: 16px;
  padding: 0 4px;
  font-size: 10px;
  font-weight: 700;
  line-height: 16px;
  text-align: center;
  color: var(--text);
  background: var(--panel);
  border: 1px solid var(--accent);
  border-radius: 8px;
  pointer-events: none;
}
.die-count:empty { display: none; }

#dice-dock-roll {
  padding: 6px 2px;
  font-size: 12px;
  font-weight: 700;
  border-radius: 14px;
}
#dice-dock-roll.hidden { display: none; }

#dice-dock-mod-input {
  width: 100%;
  padding: 4px 2px;
  font-size: 13px;
  font-weight: 700;
  text-align: center;
  border-radius: 10px;
  -moz-appearance: textfield;
  appearance: textfield;
}
#dice-dock-mod-input::-webkit-outer-spin-button,
#dice-dock-mod-input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }

#dice-dock-steps {
  display: flex;
  gap: 3px;
}
#dice-dock-steps .btn-sm {
  flex: 1;
  min-width: 0;
  padding: 2px 0;
  font-size: 13px;
  font-weight: 700;
  line-height: 1.2;
}
#dice-dock-steps .btn-sm:first-child { border-radius: 10px 3px 3px 16px; }
#dice-dock-steps .btn-sm:last-child  { border-radius: 3px 10px 16px 3px; }

/* ─── REDUCED MOTION ─────────────────────────────────────── */
/* The number still crosses the screen — that is the feature, and where it went
   is information. What goes is the tumble, the overshoot and the fading stack
   moving under it; the flight itself is shortened to a plain, quick move. */
@media (prefers-reduced-motion: reduce) {
  .roll-flier-inner { animation: none; }
  .roll-flier::before { animation-name: roll-wash-fade; }
  .roll-flier.locked:not(.paired) .roll-flier-total,
  .roll-flier.locked.paired:not(.settled) .roll-flier-total { animation: none; }
  .roll-flier.flying { transition-timing-function: ease; }
  .roll-wheel-opt, .roll-wheel-caption { animation: none; }
  @keyframes roll-wash-fade {
    from { opacity: 0; transform: translate(-50%, -50%); }
    to   { opacity: 1; transform: translate(-50%, -50%); }
  }
  .roll-chip { transition: opacity 0.15s linear; }
  .tab-bubble { animation-name: bubble-fade, bubble-out; }
  @keyframes bubble-fade { from { opacity: 0; } to { opacity: 1; } }
}
