/* Happy My Life — shared stylesheet (game page, theme page, chart pages).
   Every color lives in the :root token block below; rules reference tokens
   only. Token registry, roles, and theme design: docs/webapp-theme-spec.md
   (values here are the dark theme — the app's original palette, unchanged).
   Extracted verbatim from index.html's style block 2026-07-16; tokenized
   the same day (rendering unchanged, verified by image diff). */
:root {
  color-scheme:dark; /* native widgets (buttons, checks, radios, select
                        arrows) follow the theme, not the OS setting */
  /* surfaces */
  --bg:#000000;              /* page */
  --bg-box:#0a0a0a;          /* boxes, menu dropdown */
  --bg-chip:#1a1a1a;         /* card chips at rest */
  --bg-raised:#262626;       /* inputs, dark controls, tooltip, code, hovers */
  --bg-raised-hover:#333333; /* dark controls on hover */
  --bg-chart:#0f0f0f;        /* chart SVG canvas */
  /* lines */
  --line:#262626;            /* hairlines: box/table/svg borders, separators, gridlines */
  --line-ctl:#333333;        /* control borders (chips, inputs, menu, queue) */
  --line-hover:#4b5563;      /* emphasized borders: control hover, tooltip, resizer hover */
  /* text */
  --fg:#ffffff;
  --fg-muted:#6b7280;        /* timestamps, axis labels, quiet controls */
  --fg-muted-hover:#9ca3af;
  --fg-faint:#4b5563;        /* decorative watermarks */
  /* card types */
  --c-action:#4ade80;
  --c-consumable:#fb923c;
  --c-friend:#f472b6;
  --c-reward:#fbbf24;
  --c-memory:#a78bfa;
  /* staple identity colors (chips only; Sad Memory keeps --c-memory) */
  --c-banana:#fde047;        /* yellow */
  --c-water:#38bdf8;         /* watery blue */
  --c-rain:#9ca3af;          /* gray tone */
  /* resource tokens + links (aliases: same value as their partner role
     today, separable by a theme) */
  --tok-e:var(--c-reward);
  --tok-f:var(--c-action);
  --tok-d:var(--c-memory);
  --tok-h:#60a5fa;           /* happiness — cheerful blue */
  --link:var(--tok-h);
  /* feedback */
  --err:#ef4444;
  --neg:#f87171;
  --pos:var(--c-action);
  --banner-bg:#166534;
  --banner-fg:#ffffff;
  --prompt-bg:#3b2f14;
  --prompt-line:#a16207;
  /* details */
  --shadow-log:rgba(0,0,0,.6); /* mobile log-sheet shadow color */
}
/* Light theme — values and measured WCAG ratios: docs/webapp-theme-spec.md
   §4/§5. Card types move to the same hues' -700 row (AA on the chip
   surface); the two text roles dark ships below AA (muted 3.7, axis 4.0)
   clear 4.5 here. Chip surfaces stay achromatic (the .dimtint contract).
   Aliases (--tok-e/f/d, --link, --pos) follow their partner roles
   automatically and are not restated. */
:root[data-theme="light"] {
  color-scheme:light;
  /* surfaces */
  --bg:#fafafa;
  --bg-box:#ffffff;
  --bg-chip:#f5f5f5;
  --bg-raised:#e5e5e5;
  --bg-raised-hover:#d4d4d4;
  --bg-chart:#ffffff;
  /* lines */
  --line:#e5e5e5;
  --line-ctl:#d4d4d4;
  --line-hover:#a3a3a3;
  /* text */
  --fg:#171717;
  --fg-muted:#4b5563;
  --fg-muted-hover:#374151;
  --fg-faint:#9ca3af;
  /* card types (Tailwind -700 row of the dark theme's hues) */
  --c-action:#15803d;
  --c-consumable:#c2410c;
  --c-friend:#be185d;
  --c-reward:#b45309;
  --c-memory:#6d28d9;
  /* staples: the yellowest/bluest values that clear AA on --bg-chip */
  --c-banana:#a16207;
  --c-water:#0369a1;
  --c-rain:#4b5563;
  --tok-h:#2563eb;
  /* feedback */
  --err:#b91c1c;
  --neg:#b91c1c;
  --banner-bg:#dcfce7;
  --banner-fg:#14532d;
  --prompt-bg:#fef3c7;
  --prompt-line:#b45309;
  /* details */
  --shadow-log:rgba(0,0,0,.25);
}
html { height:100%; }
/* No visible scrollbars anywhere; wheel/touch/keyboard scrolling still
   works (Firefox: scrollbar-width; WebKit/Blink: ::-webkit-scrollbar). */
* { scrollbar-width:none; }
*::-webkit-scrollbar { display:none; }
/* Gutter: 12px normally, but viewport-relative under zoom-in — vw units
   resolve against the window (which zoom doesn't change), so the gutter
   stays physically constant instead of growing with the zoom level. */
/* Text sizes are rem so the browser's font-size setting (Settings >
   Appearance) scales the whole UI: 1rem = the user's chosen default.
   Customize View can add a percentage to html, relative to that default.
   Its 100% setting removes the inline declaration. At the stock 16px
   setting every rem value computes to the pre-conversion px
   (.6875rem = 11px etc.) — rendering is unchanged unless the user raised
   the setting. Exceptions kept in px on purpose: none in this file; SVG
   chart axis text (attr font-size in results.js/profiles.js) stays part
   of the chart graphic, like the text baked into the full-card PNGs. */
/* The system sans is the app default; everything inherits it. Monospace
   is the exception, set per-surface (the log, the Players box status
   lines, the results tables) as monospace,monospace — the doubled
   generic dodges the browsers' smaller "fixed-width default size" (13px)
   a bare monospace keyword triggers, keeping those surfaces at the
   user's standard default size. */
/* The game page is a normal scrolling document: the top bar (and anything
   else above the board) scrolls away with the content, deliberately not
   pinned — reverting the 2026-07-04 viewport lock, which pinned the bar as
   a side effect. Only the side/log column docks, via its own sticky rule
   at #side below; mobile floats it as a sheet instead (body.mobile). */
body { background:var(--bg); color:var(--fg); font-family:system-ui,-apple-system,sans-serif; margin:0; padding:min(12px, 1vw); box-sizing:border-box; min-height:100%; }
/* position:relative — #layout is the tooltip's containing block, so the
   absolutely-positioned #tip lives INSIDE the scrolled content and page
   scrolling moves it with the board via the DOM alone. */
#layout { display:flex; gap:6px; position:relative; }
#main { flex:2; min-width:0; }
#side { flex:0 0 360px; min-width:50px; display:flex; flex-direction:column; }
/* Desktop: the log column stays docked while the page scrolls — sticky
   against the body gutter, its boxes flexing inside it (#log scrolls
   internally, as ever). align-self keeps it from stretching to #main's
   height, which would leave sticky nothing to travel in.
   Scoped off mobile: the mobile sheet is position:fixed with top+bottom
   edges, so a height here would over-constrain it.
   The height subtracts --sidetop, the flow offset of #layout from the top
   of the document (the top bar, plus the banner and the replay controls
   when they are up), which app.js measures live. A plain viewport-tall
   column is only correct once the page has scrolled far enough to dock
   it: unscrolled, it starts --sidetop px down and hangs that far past the
   bottom of the window. The fallback covers the one-line bar before the
   first measurement. */
body:not(.mobile) #side { align-self:flex-start; position:sticky;
  top:min(12px, 1vw);
  height:calc(100vh - var(--sidetop, 54px) - min(12px, 1vw)); }
/* Drag handle to resize the log side column. */
#sideresizer { flex:0 0 6px; align-self:stretch;
  cursor:col-resize; background:var(--line); border-radius:3px; }
#sideresizer:hover, #sideresizer.dragging { background:var(--line-hover); }
#side .box { flex:1; display:flex; flex-direction:column; margin-bottom:0; min-height:0; }
/* Section labels are signage, not content: a drag across the board should
   select what a player wants to quote (card names, log lines), not pick up
   the headings it passed over. */
h4 { color:var(--fg); font-size:.625rem; text-transform:uppercase; letter-spacing:.1em; margin:0 0 4px;
  -webkit-user-select:none; user-select:none; }
/* Status prefix slots (A|P|C / d / G): keep the space slots (pre) and the
   lowercase 'd' (h4 would uppercase it). */
.pstat { white-space:pre; text-transform:none; }
/* Snapshot capture (?snapshot=1, set by theme-boot.js; scripts/ui-shots.js):
   freeze animation at its visible frame so captures are pixel-stable —
   the served-page analogue of theme.html's html.snap rule. */
html.snap * { animation:none !important; transition:none !important; }
/* user-select/touch-callout off: a long press is a valid way to act on a
   card on touch screens; without this it starts the text-selection loupe. */
.chip { display:inline-block; padding:2px 6px; margin:2px; background:var(--bg-chip); border:1px solid var(--bg-chip); border-radius:3px; font-size:.75rem; cursor:default; position:relative; -webkit-user-select:none; user-select:none; -webkit-touch-callout:none; }
#tip { display:none; position:absolute; width:max-content; max-width:min(260px, calc(100vw - 16px)); box-sizing:border-box; background:var(--bg-raised); color:var(--fg); border:1px solid var(--line-hover); border-radius:3px; padding:4px 8px; font-size:.6875rem; z-index:10; pointer-events:none; }
.tokE { color:var(--tok-e); font-weight:bold; }
.tokF { color:var(--tok-f); font-weight:bold; }
.tokD { color:var(--tok-d); font-weight:bold; }
.tokH { color:var(--tok-h); font-weight:bold; }
/* Actionable (castable/draftable/buyable): the border takes the card's
   type color (currentColor = the chip's font color). A resting chip's
   border matches its own background, so it reads as no border at all and
   the type color is the only edge on the board that means "you can act". */
.chip.act { cursor:pointer; border-color:color-mix(in srgb, currentColor 50%, transparent); }
.chip.off { opacity:.35; }
.chip.illegal { opacity:.2; text-decoration:line-through; }
/* Held back by a switch rather than ruled out: the dashed outline is the
   same "not committed yet" language the input queue uses (.qable). Legible
   enough to read the name, and it still takes .off, so a gated chip shows
   whether the pick under it is in or out. A dashed edge is only a signal
   if it can be seen, so gated (and ghost, below) re-assert the resting
   border color that the base .chip rule gave up. */
.chip.gated { opacity:.5; border-style:dashed; border-color:var(--line-ctl); }
.chip.gated.off { opacity:.28; }
/* Chips you can't act on — hand cards you can't cast (during the play
   step) and rewards you can't afford (every phase) — gray their FONT
   color 50% instead of darkening the background: the chip's background
   and border are pure grays, so grayscale() leaves them untouched and
   only desaturates the type-colored text. (Themes keep chip surfaces
   achromatic for this reason — docs/webapp-theme-spec.md §8.) */
.chip.dimtint { filter:grayscale(50%); }
/* An exhausted Reward/Staple pile keeps its slot as a ghost: faded,
   desaturated, dashed border. The row's shape stays intact so a missing
   pile reads as a gap in a known place rather than a silently shorter row,
   and the card stays identifiable. Never combined with .act — a ghost has
   no click handler. Draft-row slots never ghost (they refill). */
.chip.ghost { opacity:.32; border-style:dashed; border-color:var(--line-ctl); filter:grayscale(100%); }
/* Simul-play: a dashed actionable chip QUEUES — the server parks the same
   input and plays it when this seat's window arrives. Queued-state
   buttons (pass, abilities) show the same dash as an outline (buttons are
   platform-native, so their border can't carry it). The Queue section
   sits under the stack contents, only while something is parked. */
.chip.act.qable { border-style:dashed; }
/* In-shop prompt options (a draft-menu prompt answered on the shop rows):
   an offered chip wears the DEFAULT actionable highlight — the same .act
   type-color border as a Draft-step chip. The offer reads by contrast:
   the un-offered rest of the two rows takes .dimtint. .prompt-lit paints
   nothing itself; it marks the prompt wiring (focusable, role=button) for
   the harness, and keyboard focus shows the UA's own focus ring. */
/* image display modes (menu "Cards:"): same chip element, image body */
.chip.imgchip { padding:2px; display:inline-flex; flex-direction:column; align-items:center; gap:1px; vertical-align:top; }
.chip.imgchip img { display:block; border-radius:2px; pointer-events:none; }
/* .cm-art/.cm-full sit on the chip element itself: the mode is resolved
   per surface (Customize View's per-context matrix), so one board can mix
   text, art, and full chips. theme.html sets them the same way per sample. */
.chip.cm-art.imgchip img { height:90px; }
.chip.cm-full.imgchip img { height:220px; }
.chip.imgchip .cap { font-size:.625rem; line-height:1.2; max-width:118px; text-align:center; }
/* EXPERIMENT (localhost): art-mode labels inside the image rect, on solid
   plates in the card type's color (currentColor); --bg inverts against it
   in both themes. Top bar: happiness / ×count / effort cost. Dice: tight
   square dead center. Name: an end-to-end two-line plate at the bottom.
   The art is deliberately obscured behind the plates. */
.chip.cm-art.imgchip .ovl { position:absolute; color:var(--ink); font-size:.625rem; line-height:1.2; z-index:1; }
/* Plate ink (--ink) = the theme's card type colors on the standard chip
   surface — the same pairing the text chips use, contrast-tuned in both
   themes. Painted by the name plate, the dice square, and the top-bar
   slots — the top bar itself is transparent. */
.chip.cm-art.imgchip { --ink:var(--fg-muted); }
.chip.cm-art.ACTION { --ink:var(--c-action); }
.chip.cm-art.CONSUMABLE { --ink:var(--c-consumable); }
.chip.cm-art.FRIEND { --ink:var(--c-friend); }
.chip.cm-art.REWARD { --ink:var(--c-reward); }
.chip.cm-art.MEMORY { --ink:var(--c-memory); }
.chip.cm-art.staple-banana { --ink:var(--c-banana); }
.chip.cm-art.staple-water { --ink:var(--c-water); }
.chip.cm-art.staple-rain { --ink:var(--c-rain); }
/* Plates sit on the chip surface via --plate so the original act-chip
   hover/pressed lift (surface -> raised) shows through them. */
.chip.cm-art.imgchip .oname, .chip.cm-art.imgchip .odice { background:var(--plate, var(--bg-chip)); }
/* Plates use the fewest text lines their content needs: the info bar is
   one line; the name plate sizes to its text (one line for most names,
   two at most via the clamp), covering no more art than necessary.
   Heights derive from the font's own line box (lh), so the family or
   size can change without retuning; px values are the no-lh fallback. */
/* The top row is a transparent anchor; each slot draws its own solid rect
   tight on its text (like the dice square), so art shows between them.
   Empty slots vanish. Happiness holds the left corner; the right cluster
   reads cost then count (1F ×5), the count taking the corner. */
/* flush to the chip's inner top edge — no art/padding hairline above */
.chip.cm-art.imgchip .obar { top:0; left:0; right:0; height:14px; height:calc(1lh + 2px); display:flex; justify-content:space-between; align-items:center; gap:2px; box-sizing:border-box; }
.chip.cm-art.imgchip .obar span { background:var(--plate, var(--bg-chip)); padding:0 2px; }
.chip.cm-art.imgchip .obar span:empty { display:none; }
.chip.cm-art.imgchip .obar .of { margin-left:auto; }
/* z-index:2 — the name plate comes later in the DOM, and at large browser
   font sizes it grows tall enough to reach the center; the dice matters
   more for play, so it draws over the name (its solid plate keeps it
   legible on top). */
.chip.cm-art.imgchip .odice { top:50%; left:50%; transform:translate(-50%,-50%); padding:0 2px; font-weight:bold; z-index:2; }
.chip.cm-art.imgchip .oname { bottom:0; left:0; right:0; display:flex; align-items:center; justify-content:center; text-align:center; padding:1px; box-sizing:border-box; }
.chip.cm-art.imgchip .oname .nt { display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden; word-break:break-word; }
button.qable { outline:1px dashed var(--fg-muted); outline-offset:1px; }
#queue { margin-top:6px; padding-top:2px; border-top:1px dashed var(--line-ctl); }
#queue .qhead { color:var(--fg-muted); }
.qcancel { background:var(--bg-raised); color:var(--fg); border:1px solid var(--line-ctl);
  border-radius:3px; font-size:.6875rem; padding:0 4px; margin:2px 6px 2px 0;
  cursor:pointer; }
.qcancel:hover { border-color:var(--err); color:var(--err); }
/* Reorder arrows on my own queued inputs (ADR 0017). Same shell as .qcancel
   so the queue row keeps one rhythm, but the hover is the palette's ordinary
   control emphasis rather than --err: moving an input is an edit, not a
   destruction, and only the × should read as an alarm. Tighter right margin
   than .qcancel, since these sit in a run with it. */
.qmove { background:var(--bg-raised); color:var(--fg); border:1px solid var(--line-ctl);
  border-radius:3px; font-size:.6875rem; padding:0 4px; margin:2px 2px 2px 0;
  cursor:pointer; }
.qmove:hover { border-color:var(--line-hover); background:var(--bg-raised-hover); }
/* Debug box (games created in debug mode). Editing is a mode, so it needs
   to look like one: while it is on, every clickable card carries the same
   dotted "this click means something else now" outline, and the selected
   card is the only solid one. --err is the palette's one alarm color and
   this is the one screen that should feel like an alarm. */
.chip.dbgpick { outline:1px dotted var(--err); outline-offset:1px; cursor:pointer; }
.chip.dbgsel { outline:2px solid var(--err); outline-offset:1px; }
/* Styled in full rather than by override: page buttons are otherwise
   native (color-scheme:dark), and a native button ignores enough of a
   partial rule to end up red-on-red. */
#dbgedit { background:var(--bg-raised); color:var(--fg); border:1px solid var(--line-ctl);
  border-radius:3px; padding:1px 6px; font:inherit; cursor:pointer; }
#dbgedit.on { border-color:var(--err); color:var(--err); }
/* Last on the board: a tool, not a game area, so it sits below the play
   surface and out of the user-reorderable set (BOX_ORDER_DEFAULT). */
#dbgbox { order:99; }
#dbgbox .dbgrow { display:flex; align-items:center; gap:6px; flex-wrap:wrap; margin-top:6px; }
#dbgbox .dbgrow select { width:auto; max-width:20em; }
/* Triple the .box input default (6em): card names are long enough that the
   default truncates most of them mid-word while you type. */
#dbgbox .dbgrow input { width:18em; }
#dbgbox .dbgzone { margin-top:6px; }
#dbgbox .dbgraw { max-height:16em; overflow:auto; background:var(--bg-raised);
  border:1px solid var(--line); border-radius:3px; padding:4px; margin-top:6px;
  font-size:.6875rem; white-space:pre-wrap; word-break:break-all; }
.box input, .box select { background:var(--bg-raised); color:var(--fg); border:1px solid var(--line-ctl);
  border-radius:3px; padding:2px 4px; font:inherit; width:6em;
  box-sizing:border-box; }
.box input[type="checkbox"] { width:auto; } /* e.g. the locked setup toggles */
.box input:disabled, .box select:disabled { color:var(--fg-muted); opacity:.55; }
/* Join Game box (shown when spectating a game with an open seat) */
#joinname { width:12em; }
#joinseats { margin-top:6px; }
/* New Game — Setup: board-style stacked boxes (header + Start Game,
   Game dials, Players, Card Pool), label + control rows inside. */
.boxhead { display:flex; align-items:center; justify-content:space-between; }
.gsrows { display:flex; flex-direction:column; gap:8px; }
.qcrow { display:flex; align-items:center; gap:10px; flex-wrap:wrap; }
.qcrow label { cursor:pointer; white-space:nowrap; }
/* Tight radio-to-label gap (markup has no space after the input). */
.qcrow input { vertical-align:middle; margin:0 2px 0 0; cursor:pointer; }
/* Bot-count radios: tight 20px control (the .box input 6em default is
   sized for text fields) with 20px extra space on the left. */
.qcrow input[type="radio"] { width:20px; padding-left:20px; }
/* Named create-request library. The list remains useful at narrow widths:
   it takes a full row, while the controls wrap without changing order. */
.gssavedlayout { display:grid; grid-template-columns:minmax(180px, 1fr) 2fr;
  gap:10px 18px; align-items:start; }
.gssavedpicker, .gssavededitor { display:flex; flex-direction:column; gap:5px; }
#gssaved, #gssavedname { width:100%; max-width:none; }
#gssaved { min-height:7em; }
.gssavedactions { display:flex; flex-wrap:wrap; gap:6px; }
#gssavedstatus { min-height:1.2em; color:var(--fg-muted); }
#gssavedstatus.err { color:var(--err); }
@media (max-width: 520px) {
  .gssavedlayout { grid-template-columns:1fr; }
}
/* Seat rows are rebuilt when the player count changes; the container keeps
   the box's row rhythm. */
#gsseats { display:flex; flex-direction:column; gap:8px; }
#gsetup .param { display:flex; align-items:center; gap:10px; flex-wrap:wrap; }
#gsetup .param > label { display:inline-block; min-width:160px; }
#gsetup .param > input, #gsetup .param > select { width:17em; max-width:100%; }
#gsetup .param > input[type="checkbox"] { width:auto; }
#gspoolctl { margin-bottom:8px; }
/* Card-pool set sections (Base / Coop-only / Versus-only / Euphoria / Misc). */
#gspool .poolsec { margin:8px 0 2px; }
#gspool .poolsec:first-child { margin-top:0; }
/* Each section's own all/none pair, sized to sit inside the header line
   rather than break its rhythm (same treatment as .qcancel). */
#gspool .secbtn { background:var(--bg-raised); color:var(--fg-muted);
  border:1px solid var(--line-ctl); border-radius:3px; font:inherit;
  font-size:.6875rem; padding:0 5px; margin:0 0 0 6px; cursor:pointer; }
#gspool .secbtn:hover { color:var(--fg-muted-hover); background:var(--bg-raised-hover); }
/* Why a section is switched off, said at its header. */
#gspool .secnote { font-style:italic; }
/* Welcome / session-not-found card. */
#welcometitle { font-size:1.625rem; font-weight:400; margin-bottom:12px; }
#welcomemsg { margin-bottom:14px; }
/* Space off the New Game button above. Top margin rather than bottom on the
   button, because the button is inline-block and shares its styling with
   every other button on the page. */
#welcomenotes { margin-top:14px; }
#welcomemsg code { background:var(--bg-raised); border:1px solid var(--line-ctl); border-radius:3px;
  padding:1px 5px; }
/* Hover highlight only on devices that actually hover: on touch screens the
   browser's :hover state sticks to a tapped chip after the finger lifts, so
   there the highlight is driven by .pressed (touch press, cleared on lift). */
@media (hover: hover) { .chip.act:hover { background:var(--bg-raised); border-color:var(--fg); --plate:var(--bg-raised); } }
.chip.act.pressed { background:var(--bg-raised); border-color:var(--fg); --plate:var(--bg-raised); }
.ACTION { color:var(--c-action); } .CONSUMABLE { color:var(--c-consumable); } .FRIEND { color:var(--c-friend); }
.REWARD { color:var(--c-reward); } .MEMORY { color:var(--c-memory); }
/* Staple identity colors — the ever-present shop staples read as
   themselves instead of generic Consumable orange (Sad Memory keeps the
   Memory color). app.js chip() adds these by card name; same specificity
   as the type classes, so they must stay AFTER them in source order. */
.staple-banana { color:var(--c-banana); }
.staple-water { color:var(--c-water); }
.staple-rain { color:var(--c-rain); }
.box { background:var(--bg-box); border:1px solid var(--line); border-radius:4px; padding:8px; margin-bottom:8px;
  position:relative; isolation:isolate; }
/* Board and log boxes: zoom-stable 6px rhythm (same recipe as the body
   gutter). The log box keeps its margin-bottom:0 (it flexes to fill). */
#gameboxes .box, #side .box { padding:min(6px, 1vw); }
#gameboxes .box { margin-bottom:min(6px, 1vw); }
/* flex column so Customize View can rearrange the boxes via order:;
   same-order items keep DOM order (the prompt box shares the Phase Bar's
   slot and sits right under it). */
#gameboxes { display:flex; flex-direction:column; }
/* Board-box titles float as right-side watermarks (like the shop row
   labels): behind the content layer, free to overlap with text. */
h4.wmark { position:absolute; right:6px; margin:0; z-index:-1;
  color:var(--fg-faint); pointer-events:none; }
h4.wmark.top { top:6px; }
h4.wmark.bot { bottom:6px; }
/* Buttons use the platform-native (system default) appearance; only
   spacing is added. Native styling handles hover and disabled states.
   1px a side is just enough that adjacent buttons do not touch. It applies
   to every button on every page, so a row that spaces its children with
   flex `gap` gets the 1px on top of that gap rather than instead of it. */
button { margin-left:1px; margin-right:1px; }
/* Form controls do not inherit the document font: every engine's UA
   stylesheet gives them an ABSOLUTE px size (Chrome/WebKit: 13.333px
   Arial), so a native button's label ignored the root font-size that
   Customize View > Font size sets, and label text stayed put while the
   rest of the page grew. Restating that same default in rem re-attaches
   them to the root: .83333rem computes to 13.3333px at the stock 16px
   root — measured identical to the UA value, and to the same laid-out
   button width — so rendering is unchanged at 100%, and the labels now
   scale with everything else. Size only: family is left to the UA so
   buttons keep the platform-native face this file's rule above wants.
   Type selectors on purpose — every rule that already says `font:inherit`
   (#rpctl, #menudd, .box input, #switcher, .secbtn) outranks this one and
   keeps its inherited, already-scaling size. */
button, input, select, textarea { font-size:.83333rem; }
#log { flex:1; min-height:0; overflow-y:auto; font-size:.6875rem; color:var(--fg);
  font-family:monospace,monospace; }
#log .ts { color:var(--fg-muted); }
/* Event timestamps are plain links that open the replay at that moment
   (client hint lines keep an inert stamp). */
#log a.ts { text-decoration:none; }
#log a.ts:hover { text-decoration:underline; color:var(--fg-muted-hover); }
/* Replay mode shows the whole timeline; lines after the current step are
   dimmed — their stamps still seek on click. */
#log .future { opacity:.4; }
#banner { display:none; background:var(--banner-bg); color:var(--banner-fg); padding:10px; border-radius:4px; font-weight:bold; margin-bottom:8px; }
/* Phase box — the six major turn steps as one elastic strip with the two
   interstitial mode glyphs in the Draw–Draft and Draft–Play gaps
   (Begin - Draw > [ Draft ] > Play - End - Cleanup), the current step
   bracketed wherever it falls — a major segment or a glyph.
   space-between stretches the gaps to the box's width;
   flex-nowrap plus hidden overflow means the strip can never break onto
   a second line. In a narrow box app.js shrinks the font to fit
   (fitPhaseStrip: measured against the real glyphs, so any font family
   works; a container query can't be used here — #main's width is
   content-fed, so inline-size containment on a board box feeds back and
   collapses it). */
#phasestrip { display:flex; justify-content:space-between; align-items:center;
  white-space:nowrap; overflow:hidden; color:var(--fg-muted); font-size:.75rem; }
#phasestrip .psep { color:var(--fg-faint); }
#phasestrip .pstep.on { font-weight:bold; }
/* Interstitial mode glyphs (ADR 0018 windows) in the Draw–Draft and
   Draft–Play separator slots: > auto-pass, 1 interrupt once, ∞ interrupt
   always, · for a viewer with no seat. Clickable (.act) for a seated live
   player — the click cycles the three modes via a server input. They keep
   the separators' faint gray until they are the current step (.on), which
   brackets and bolds them like .pstep.on and lets their ph-* accent
   through (the :not(.on) rule below outranks the accent selectors). */
#phasestrip .pglyph:not(.on) { color:var(--fg-faint); }
#phasestrip .pglyph.on { font-weight:bold; }
#phasestrip .pglyph.act { cursor:pointer; }
#phasestrip .pglyph.act:not(.on):hover { color:var(--fg-muted); }
/* Only the current step wears its accent; the others recede to the same
   faint gray as the separators, so one colored label marks the phase.
   The accents are borrowed from the AA-tuned card-type/staple tokens
   (measured on --bg-box: all ≥5.0 both themes — worst is action/reward
   at 5.02 on light). The current step is still told apart by brackets +
   bold, not by color alone. */
#phasestrip .pstep:not(.on) { color:var(--fg-faint); }
#phasestrip .ph-begin { color:var(--c-water); }
#phasestrip .ph-draw { color:var(--c-memory); }
/* The ADR 0018 interstitials borrow the accent of the step they precede,
   so each before-window reads as belonging to its phase (only the current
   step is colored at all; the rest recede to faint gray). */
#phasestrip .ph-before-draft { color:var(--c-action); }
#phasestrip .ph-draft { color:var(--c-action); }
#phasestrip .ph-before-play { color:var(--c-friend); }
#phasestrip .ph-play { color:var(--c-friend); }
#phasestrip .ph-end { color:var(--c-reward); }
#phasestrip .ph-cleanup { color:var(--c-rain); }
#prompt { display:none; background:var(--prompt-bg); border:1px solid var(--prompt-line); }
/* Prompt title is body text, not a header: it leads with the asking
   card's chip ([Fine Arts Teacher] CHOOSE — TRASH), so no uppercase or
   letter-spacing — the chip keeps its printed card name. */
#ptitle { margin:0 0 4px; font-size:.75rem; }
/* Atomic replacement prompts keep their choices local until confirmation.
   The selected treatment belongs to gameplay (never the red debug-edit
   state): inset + outer prompt-colored rings remain visible on both native
   buttons and card chips, while aria-pressed carries the same state to AT. */
#popts .prompt-choice { cursor:pointer; }
#popts .prompt-choice.prompt-selected { outline:2px solid var(--prompt-line);
  outline-offset:1px; box-shadow:inset 0 0 0 1px var(--prompt-line);
  background:var(--bg-raised); }
#popts .prompt-choice.prompt-limit:not(.prompt-selected) { opacity:.55; cursor:not-allowed; }
#popts .prompt-confirm { margin-top:4px; font-weight:bold; }
/* The shared announcement list (ADR 0015): announced, visible to the whole
   table, waiting to be promoted onto the stack. Solid rather than faded — it
   is game state from the first click, not a browser-local preview, and every
   seat is reading it. */
#stack .stack-announced-seat { margin-top:4px; font-size:.6875rem; opacity:.7; }
#stack .stack-announced { opacity:.9; }
#stack .stack-announced-by { margin-left:4px; font-size:.625rem; opacity:.6; }
/* When the bar runs out of width its groups (and the items inside them)
   wrap onto following lines instead of overflowing; the log sheet's
   --barh is measured live, so a taller wrapped bar is accounted for. */
.bar { display:flex; flex-wrap:wrap; justify-content:space-between; align-items:center;
  margin-bottom:min(6px, 1vw); min-height:36px; row-gap:6px; position:relative; }
/* Title image height is fixed (matches the bar's one-line height);
   source is 2x for retina. */
.barleft { display:flex; flex-wrap:wrap; align-items:center; min-height:36px; gap:8px; }
/* The title link wraps both faces of the title (image on wide layouts,
   "hml." on mobile) and lays them out the way .barleft used to. It keeps
   the bar's own colour: link colour on the sign would read as chrome. */
.barhome { display:flex; align-items:center; gap:8px; color:inherit; text-decoration:none; }
.titleimg { height:36px; width:auto; display:block; }
/* margin-left:auto keeps the right group pinned to the right edge of the bar.
   The group itself lays out RIGHT TO LEFT (row-reverse), so it fills from the
   right edge inward in priority order — Menu, Log, session picker, then the
   "name @" seat label. Whatever does not fit flows onto the next line from
   the least important end, which is what keeps Menu and Log on the first
   line at every width. Under row-reverse main-start IS the right edge, so
   justify-content:flex-start is what right-aligns each line.
   `order` does the reversing, not the DOM, so document and tab order still
   run left-to-right and match what is on screen.
   This replaced a @media (max-width:300px) rule that pinned .barctl
   absolutely. 300px was a GUESS at the width this content needs, and it
   needs ~320px: between 301px and 319px neither path applied and the buttons
   landed BELOW the picker (measured picker top 45.0, buttons top 78.0). No
   breakpoint can be right here, because the fit width moves with the display
   name and the session id. Wrapping is measured by the browser, so this
   needs no threshold and no resize handler. */
.barright { display:flex; flex-direction:row-reverse; flex-wrap:wrap;
  align-items:center; gap:10px; margin-left:auto; justify-content:flex-start; }
/* Right-to-left priority: lowest order sits furthest right and leaves last. */
#switcher { order:2; }
#seatname { order:3; }
.menu { position:relative; } /* dropdown anchor */
/* Log (when shown) + Menu travel as one group, so the Log button always
   sits just left of the menu. order:1 holds that group at the right edge, so
   it is the last thing that would ever leave the first line; flex:0 0 auto
   keeps both at full size rather than shrinking onto a line they do not fit. */
.barctl { display:flex; align-items:center; order:1; flex:0 0 auto; }
/* Every button on the page keeps the platform-native appearance, the Menu
   trigger and Log included; #menudd's items style themselves.
   The picker and Log sit IN the bar rather than on it — fill and edge made
   transparent — so "alice @ Zx4-q9tR" reads as one phrase. Log changes
   nothing else: it is a stock button whose two colours are transparent, so
   it keeps the native bevel width, padding, font and corner. The picker
   still needs the size and font declarations below, since a <select> would
   otherwise render at the browser's smaller form-control default. */
#logshow { background:transparent; border-color:transparent; }
#switcher { background:transparent; color:var(--fg); border:1px solid transparent;
  padding:3px 0; font:inherit; }
/* Both are chrome-free at rest, so hover is the whole affordance. */
#switcher:hover, #logshow:hover { background:var(--bg-raised-hover); border-color:var(--line-hover); }
/* Session ids are base64url noise: monospace on the id menu item and the
   switcher's closed face; the open option list keeps the app font. */
#menudd #sess, #switcher { font-family:monospace,monospace; }
#switcher option { font-family:system-ui,-apple-system,sans-serif; }
/* Replay controls: same dark styling as the menu/Log/session controls.
   Flex-wrap with a gap so tight widths wrap into spaced rows instead of
   colliding lines (display is inline-toggled: '' shows, 'none' hides). */
#rpctl { display:flex; flex-wrap:wrap; align-items:center; gap:6px; }
#rpctl button, #rpctl input { background:var(--bg-raised); color:var(--fg); border:1px solid var(--line-ctl);
  border-radius:3px; padding:3px 8px; font:inherit; }
#rpctl button { cursor:pointer; }
#rpctl button:hover { background:var(--bg-raised-hover); border-color:var(--line-hover); }
#menudd { display:none; position:absolute; right:0; top:100%; margin-top:4px;
          background:var(--bg-box); border:1px solid var(--line-ctl); border-radius:4px;
          width:max-content; min-width:140px; z-index:20; }
#menudd button { display:block; width:100%; box-sizing:border-box;
                 margin:0 0 2px 0; text-align:left; white-space:nowrap;
                 background:var(--bg-box); color:var(--fg); border:0; border-radius:3px;
                 padding:4px 10px; font:inherit; cursor:pointer; }
#menudd button:hover { background:var(--bg-raised); }
.muted { color:var(--fg); font-size:.6875rem; }
/* Status/info lines separate facts with two spaces — keep them from
   collapsing to one. */
#status, #me .muted, #opp .muted { white-space:pre-wrap; }
/* Status lines are monospace so the space-padded columns and the A|P|C
   prefix slots line up — across players in the roster, and between the
   roster and the You box, which prints the same shape for one player. */
#me .mestat, #opp .muted { font-family:monospace,monospace; }
/* Counters carry the token colors but not the token weight. Bold is an
   emphasis, and it only reads as one where the surrounding text is plain: a
   status line is nothing but counters, and a tooltip is a cost line plus one
   or two sentences of rules text, so bolding every token there emphasizes the
   block against itself. Color alone still separates them. Scoped rather than
   set on .tokE/.tokF/.tokD/.tokH, which are shared with the board's card text
   and mirrored in theme.html and docs/webapp-theme-spec.md. */
#me .mestat .tokH, #me .mestat .tokE, #me .mestat .tokF,
#opp .muted .tokH, #opp .muted .tokE, #opp .muted .tokF,
#tip .tokE, #tip .tokF, #tip .tokD, #tip .tokH,
.chip .tokF, #meturn .tokH { font-weight:normal; }
/* A denominator is the limit a counter races, not a quantity anyone holds, so
   it drops below the muted body text it sits in — the value stays the thing
   the eye lands on. --fg-faint is dimmer than --fg-muted in both themes. */
#meturn .denom { color:var(--fg-faint); }
/* Shop sections: one row per printed shop-grid row (draft, staples,
   rewards ×2). The label is a right-aligned watermark UNDER the chip
   layer — cards are free to overlap it. */
.shoprow { position:relative; border-top:1px solid var(--line);
  margin-top:4px; padding-top:4px; min-height:24px; }
.shoprow .rowlabel { position:absolute; right:6px; bottom:2px; z-index:0;
  color:var(--fg-faint); pointer-events:none;
  -webkit-user-select:none; user-select:none; }
.shoprow .rowchips { position:relative; z-index:1; }
/* Clickable inline text (e.g. the You box's discard counter). */
.textbtn { cursor:pointer; -webkit-user-select:none; user-select:none; -webkit-touch-callout:none; }
.textbtn:hover { text-decoration:underline; }
/* Info-box detail panel (card pool / discard): heading left, close right. */
#ipanel, #ipanel2 { border-top:1px solid var(--line); margin-top:6px; }
#ipanelhead, #ipanel2head { display:flex; align-items:baseline; justify-content:space-between; }
/* You-box header: the same Pass/End control at both corners (#pass2 left,
   #pass right — one is always near the pointer; syncPass2 keeps them
   identical). The controls remain content-sized so short labels do not leave
   unnecessary empty space. row-gap only — a column gap would change the
   packing and start wrapping at widths that fit today. */
#mehead { display:flex; flex-wrap:wrap; align-items:center;
  justify-content:space-between; gap:4px 0; }
#mespacer { height:2px; }
/* Turn label and the rewind pair travel together as the middle of the
   three space-between children, so adding them left the Pass buttons
   pinned to their edges. It grows into the space between the buttons and
   centres its own contents, which keeps the label centred on one line AND
   on the wrapped first line, where space-between would fling it right. */
#mectl { display:flex; align-items:center; gap:6px;
  flex:1 1 auto; justify-content:center; }
/* Mobile is at minimum width: one pass button is enough — keep the right
   one (thumb side) and drop the left duplicate. */
body.mobile #pass2 { display:none; }
/* Log header: title left, height presets right (width drag still works). */
#loghead { display:flex; align-items:baseline; justify-content:space-between; }
#loghead h4 { margin-bottom:0; }
#logsizes .textbtn { color:var(--fg-muted); font-size:.6875rem; margin-left:8px; }
/* Log height presets (logsz-* on body) — MOBILE-SHEET ONLY: 90 =
   everything below the top bar, 50 = half, 30 = bottom third, 10 = the
   newest ~3 log lines. Desktop always docks the full column (drag sets
   its width), so a preset picked in mobile never leaks dead space above
   the docked log; the size controls hide there too ('hide' stays). */
body:not(.mobile) #log90, body:not(.mobile) #log50,
body:not(.mobile) #log30, body:not(.mobile) #log10 { display:none; }
body.mobile #side { justify-content:flex-end; }
body.mobile.logsz-10 #side .box { flex:0 0 auto; }
body.mobile.logsz-10 #log { flex:0 0 3.9em; } /* newest ~3 log lines */
/* Mobile mode: the log floats over the bottom of the screen instead of
   docking as a side column. Toggled from the menu (body.mobile). */
/* Mobile is a normal scrolling page, not an app shell: the whole document
   flows and scrolls, so the top bar scrolls away with it (not sticky).
   The log sheet is position:fixed and unaffected. */
body.mobile { display:block; height:auto; min-height:100%; overflow:auto; }
/* Phones trade the title image for a compact wordmark. */
body.mobile .titleimg { display:none; }
#titletext { display:none; font-size:.9375rem; font-weight:bold; letter-spacing:.05em; }
body.mobile #titletext { display:inline; }
/* Mobile: the Menu button is just the hamburger. */
#menu .menuicon { display:none; }
body.mobile #menu .menuword { display:none; }
body.mobile #menu .menuicon { display:inline; }
body.mobile #layout { display:block; }
body.mobile #main { overflow:visible; }
body.mobile #sideresizer { display:none; }
body.mobile #side { position:fixed; left:0; right:0; top:50%; bottom:0;
  z-index:15; padding:min(6px, 1vw); } /* zoom-stable, like the body gutter */
body.mobile.logsz-90 #side { top:var(--barh, 56px); }
body.mobile.logsz-30 #side { top:66.667%; } /* bottom third of the screen */
body.mobile.logsz-10 #side { top:auto; }
body.mobile #side .box { box-shadow:0 -8px 24px var(--shadow-log); }
/* Invisible half-viewport spacer at the end of the main column, so the
   content can always scroll clear of the floating log sheet. */
body.mobile #main::after { content:''; display:block; height:50vh; }
.err { color:var(--err); }
a { color:var(--link); }
/* ---- shared with the chart pages (results.html / profiles.html): their
   duplicated table/chart base rules, folded here per
   docs/webapp-theme-spec.md §3.1. Page-specific sizing (profiles' larger
   table type, select widths) stays in each page's own block; the SVG
   series strokes stay in the pages' JS until the chart-class phase. ---- */
table { border-collapse:collapse; font-size:.6875rem; }
th, td { border:1px solid var(--line); padding:2px 8px; text-align:right; }
th { text-transform:uppercase; font-size:.625rem; }
td:first-child, th:first-child { text-align:left; }
svg { background:var(--bg-chart); border:1px solid var(--line); border-radius:4px; }
.legend span { display:inline-block; margin-right:14px; font-size:.6875rem; }
.swatch { display:inline-block; width:10px; height:10px; margin-right:4px; vertical-align:-1px; }
.pos { color:var(--pos); } .neg { color:var(--neg); } .zero { color:var(--fg-muted); }
.colA { color:var(--tok-h); } .colB { color:var(--c-reward); }
/* Chart classes — results.js / profiles.js (and theme.html's specimens)
   draw SVG nodes with these classes and no color of their own, so a theme
   restyles already-rendered charts without a redraw. p1–p4 = per-player
   series (results, by seat order); pa/pb = profile A/B (profiles). The
   .swatch variants color the matching legend squares. */
svg .grid { stroke:var(--line); }
/* Zero rule on charts that go negative (deck net energy) — brighter than a
   gridline, since which side of it a series sits on is the reading. */
svg .zero { stroke:var(--fg-muted); }
svg .axis { fill:var(--fg-muted); }
svg .target { stroke:var(--err); }
svg .team { stroke:var(--fg); }
svg .p1 { stroke:var(--c-reward); }
svg .p2 { stroke:var(--c-action); }
svg .p3 { stroke:var(--c-friend); }
svg .p4 { stroke:var(--tok-h); }
svg .pa { stroke:var(--tok-h); }
svg .pb { stroke:var(--c-reward); }
.swatch.team { background:var(--fg); }
.swatch.target { background:var(--err); }
.swatch.p1, .swatch.pb { background:var(--c-reward); }
.swatch.p2 { background:var(--c-action); }
.swatch.p3 { background:var(--c-friend); }
.swatch.p4, .swatch.pa { background:var(--tok-h); }

/* Customize View (menu): a settings box above the layout. The matrix and
   the area list are tables of radio rows; first column right-aligns to
   the radios like the setup param labels. */
#custbox { overflow-y:auto; }
#custbody { display:flex; flex-wrap:wrap; gap:4px 40px; }
#custbody h4 { margin:8px 0 2px; }
#custbody table { border-collapse:collapse; }
#custbody th { font-weight:normal; color:var(--fg-muted); font-size:.6875rem; padding:1px 6px; }
#custbody td { padding:1px 6px; text-align:center; }
#custbody td:first-child, #custbody th:first-child { text-align:right; white-space:nowrap; }
#custbody input[type="radio"] { width:auto; margin:0; cursor:pointer; }
.cv-font-size-controls { display:flex; align-items:center; gap:6px; margin:4px 6px; }
.cv-font-size-controls .muted { min-width:5em; text-align:right; }
.cv-font-size-controls button { min-width:2em; margin:0; }
.cv-font-size-controls output { min-width:4ch; text-align:center; }
.cv-sound-controls { display:flex; align-items:center; gap:8px; margin-top:4px; }
.cv-sound-controls label { display:flex; align-items:center; gap:5px; }
.cv-sound-controls input[type="range"] { width:9em; }
.cv-sound-controls output { min-width:3ch; }
/* Areas hidden via Customize View (tab-scoped). !important: several of
   these elements also get inline display toggles from render(). */
.userhide { display:none !important; }
