/* Palette. Light is the base; dark is applied under the media query and under
   an explicit [data-theme] so the manual toggle wins in both directions. */
:root {
	color-scheme: light dark;
	--bg: #f6f8fa;
	--panel: #ffffff;
	--panel-2: #f0f3f6;
	--line: #d6dde4;
	--text: #1c2128;
	--dim: #59636e;
	--accent: #0969da;
	--accent-2: #8a5115;
	--good: #1a7f4b;
	--bad: #c0323a;
	--rowH: 6px;
	--headerH: 56px;
	/* Where sticky things stop: under the header while it is sticky, at the
	   top of the viewport once it is not (phones, below). --headerH itself is
	   written inline by the app from the header's measured height, so it
	   cannot be overridden by a media query; this can. */
	--stickyTop: var(--headerH);
	/* The board filter bar's height, written on the board wrapper by fitBoard. */
	--bfH: 48px;
	/* One spacing and radius scale, so a new panel does not have to invent
	   its own padding. */
	--r-md: 8px;
	--r-lg: 12px;
	--shadow-1: 0 1px 2px rgba(16, 24, 40, .06);
	--surface-hover: rgba(127, 127, 127, .10);
	/* The quiet fill behind a tag. Referenced as var(--chip, …) and never
	   defined, which is how --ok and --win were wrong for a year. */
	--chip: rgba(127, 127, 127, .12);
	/* Text ON the accent. A theme whose accent is light enough to need dark
	   text overrides it; white on the dark palette's #4da3ff was 2.6:1, on the
	   most-pressed button in the tool. */
	--on-accent: #ffffff;
	--mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}
@media (prefers-color-scheme: dark) {
	:root:not([data-theme="light"]) {
		--on-accent: #10161d;
		--upset-bg2: rgba(255,180,84,.24); --upset-bg3: rgba(255,180,84,.34);
		--bg: #0f1216; --panel: #161b22; --panel-2: #1c232c; --line: #2a323d;
		--text: #e6edf3; --dim: #9aa7b4; --accent: #4da3ff; --accent-2: #ffb454;
		--good: #57c78a; --bad: #f2686a;
	}
}
:root[data-theme="dark"] {
	--tag-arch-border: #3b5b7d; --tag-arch-text: #8fc0f0;
	--tag-pro-border: #6b5230; --tag-pro-text: var(--accent-2);
	--win-bg: rgba(87,199,138,.10); --upset-bg: rgba(255,180,84,.13);
	/* upset-bg2/3 were never defined anywhere, so a dark bracket painted its
	   heaviest upsets in the light theme's browns. */
	--on-accent: #10161d;
	--upset-bg2: rgba(255,180,84,.24); --upset-bg3: rgba(255,180,84,.34);
	--bg: #0f1216; --panel: #161b22; --panel-2: #1c232c; --line: #2a323d;
	--text: #e6edf3; --dim: #9aa7b4; --accent: #4da3ff; --accent-2: #ffb454;
	--good: #57c78a; --bad: #f2686a;
}

/* ---- named themes ---------------------------------------------------------
   Color-only palettes pulled from Color Type Pairings.dc.html. Each is a
   full override of the same tokens the light/dark palettes above set, so
   nothing downstream needs to know a given theme exists. */
:root[data-theme="ledger-light"] {
	--bg: #f2efe4; --panel: #fffdf8; --panel-2: #ded9e2; --line: #c0b9dd;
	--text: #211f2b; --dim: #55525f; --accent: #3a5d8f; --accent-2: #2f7877;
	--good: #1a7f4b; --bad: #c0323a;
}
:root[data-theme="twilight-court"] {
	--tag-arch-border: #3b5b7d; --tag-arch-text: #8fc0f0;
	--tag-pro-border: #6b5230; --tag-pro-text: var(--accent-2);
	--win-bg: rgba(87,199,138,.10); --upset-bg: rgba(255,180,84,.13);
	/* upset-bg2/3 were never defined anywhere, so a dark bracket painted its
	   heaviest upsets in the light theme's browns. */
	--on-accent: #10161d;
	--upset-bg2: rgba(255,180,84,.24); --upset-bg3: rgba(255,180,84,.34);
	--bg: #17151f; --panel: #1e1b28; --panel-2: #272332; --line: #383343;
	--text: #ded9e2; --dim: #a79fc0; --accent: #80a1d4; --accent-2: #75c9c8;
	--good: #75c9c8; --bad: #e89197;
}
:root[data-theme="draft-board"] {
	--bg: #efeadb; --panel: #fffefb; --panel-2: #ded9e2; --line: #c0b9dd;
	--text: #211f2b; --dim: #5a5768; --accent: #3a5a8c; --accent-2: #2f7877;
	--good: #146b3e; --bad: #c0323a;
}
:root[data-theme="fieldhouse"] {
	--bg: #efe8d5; --panel: #faf5ea; --panel-2: #d7be82; --line: #755c1b;
	--text: #400406; --dim: #5c4b2a; --accent: #66380f; --accent-2: #755c1b;
	--good: #3f4737; --bad: #a3341f;
}
:root[data-theme="scout-report"] {
	--bg: #eef3f8; --panel: #ffffff; --panel-2: #dbe4ee; --line: #81a4cd;
	/* accent-2 was #f17300: 2.3:1 as the Reroll label on panel-2 and 2.6:1
	   for a falling poll team. A burnt orange of the same hue clears 4.5:1 on
	   every surface this theme has. */
	--text: #0a2d52; --dim: #4c6784; --accent: #054a91; --accent-2: #9a4700;
	--good: #146b3e; --bad: #c0323a;
}
:root[data-theme="night-game"] {
	--tag-arch-border: #3b5b7d; --tag-arch-text: #8fc0f0;
	--tag-pro-border: #6b5230; --tag-pro-text: var(--accent-2);
	--win-bg: rgba(87,199,138,.10); --upset-bg: rgba(255,180,84,.13);
	/* upset-bg2/3 were never defined anywhere, so a dark bracket painted its
	   heaviest upsets in the light theme's browns. */
	--on-accent: #10161d;
	--upset-bg2: rgba(255,180,84,.24); --upset-bg3: rgba(255,180,84,.34);
	--bg: #171123; --panel: #221631; --panel-2: #2c1c3d; --line: #414770;
	--text: #f4efe8; --dim: #9a93b0; --accent: #7fa3c4; --accent-2: #f46036;
	--good: #57c78a; --bad: #e0574a;
}
* { box-sizing: border-box; }
body {
	margin: 0;
	font: 14px/1.45 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
	background: var(--bg);
	color: var(--text);
}
header {
	display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
	padding: 9px 16px; min-height: var(--headerH);
	background: var(--panel); border-bottom: 1px solid var(--line);
	position: sticky; top: 0; z-index: 60;
	box-shadow: var(--shadow-1);
}
header .brand { display: flex; align-items: center; gap: 8px; margin-right: 2px; }
header h1 { font-size: 15px; margin: 0; letter-spacing: .2px; white-space: nowrap; }
header h1 span { color: var(--accent); font-weight: 600; }
/* The small tools beside the two verbs that matter. Icon-only and quiet:
   eight equally weighted labelled buttons read as one undifferentiated bar,
   and Reroll and Export disappeared into it. */
.iconbtn {
	display: inline-flex; align-items: center; justify-content: center; gap: 5px;
	min-width: 30px; padding: 5px 8px; color: var(--dim);
	background: transparent; border-color: transparent;
}
.iconbtn:hover:not(:disabled) { color: var(--text); background: var(--surface-hover); }
/* The panel toggle carries a word, so it has to read as a button rather than
   as a caption sitting next to the title. */
#btnSettings { border-color: var(--line); color: var(--text); background: var(--panel-2); }
.btngroup.tools > .iconbtn, .undogrp > .iconbtn { border-color: var(--line); }
.btngroup.tools > .iconbtn:hover:not(:disabled) { border-color: var(--accent); }
/* paintUndo writes what the button will undo into the label, so its width
   changes with every action. Uncapped, one long label rewrapped the whole
   header — the toolbar jumped a row down mid-session. */
#btnUndo {
	max-width: 17ch; overflow: hidden; text-overflow: ellipsis;
	white-space: nowrap; display: inline-block;
}
/* The spacer pushes the right-hand groups over on a single-line header. When
   the header wraps — a long "Undo …" label and the seed history are enough at
   1400px — a flex item with a height of its own becomes an empty band across
   the page, which is what the 90px hole between the two header rows was. */
.spacer { flex: 1 1 0; height: 0; }
/* Six loose buttons in a row read as one undifferentiated bar. */
.btngroup { display: inline-flex; gap: 0; align-items: center; }
/* What Export JSON will write when it is not the default; see paintExportLabel. */
.exportsum { font-size: 11px; color: var(--dim); max-width: 220px; white-space: nowrap;
	overflow: hidden; text-overflow: ellipsis; }
/* The note-template presets wrap: six buttons do not fit a 305px sidebar in a row. */
.notepresets { display: flex; flex-wrap: wrap; gap: 4px; margin: 4px 0 8px; }
.btngroup.wide { display: flex; flex-wrap: wrap; gap: 6px; margin: 6px 0; }
.btngroup > button, .btngroup > select { border-radius: 0; margin-left: -1px; }
.btngroup > *:first-child { border-radius: 6px 0 0 6px; margin-left: 0; }
.btngroup > *:last-child { border-radius: 0 6px 6px 0; }
.btngroup > *:only-child { border-radius: 6px; }
.btngroup.wide > * { border-radius: 6px; margin-left: 0; flex: 1 1 auto; }
button, select, input[type=text], input[type=number], input[type=search] {
	font: inherit; color: var(--text); background: var(--panel-2);
	border: 1px solid var(--line); border-radius: 6px; padding: 6px 10px;
}
button { cursor: pointer; }
button:hover:not(:disabled) { border-color: var(--accent); position: relative; z-index: 1; }
button:disabled { opacity: .45; cursor: default; }
button.primary { background: var(--accent); color: var(--on-accent, #fff); border-color: var(--accent); font-weight: 600; }
/* On the panel color, not panel-2: accent-2 text on the darker fill was
   3.5-3.7:1 in the fieldhouse, ledger and draft-board themes. */
button.warn { border-color: var(--accent-2); color: var(--accent-2); background: var(--panel); }
button.tiny { padding: 2px 7px; font-size: 11px; }
.layout { display: flex; align-items: flex-start; gap: 0; }
aside {
	width: 306px; flex: none; padding: 14px 14px 60px; border-right: 1px solid var(--line);
	background: var(--panel); height: calc(100vh - var(--headerH));
	overflow-y: auto; position: sticky; top: var(--headerH);
	scrollbar-width: thin;
}
main { flex: 1; min-width: 0; padding: 16px 20px 80px; }

/* Sidebar groups collapse. Eight open fieldsets meant about two of them fit on
   a 900px laptop screen and everything else was a scroll away. */
details.grp {
	border: 1px solid var(--line); border-radius: 8px; margin: 0 0 10px;
	background: var(--panel);
}
details.grp > summary {
	cursor: pointer; padding: 8px 12px; color: var(--accent); font-size: 11px;
	text-transform: uppercase; letter-spacing: .09em; list-style: none;
	display: flex; align-items: center; gap: 6px; user-select: none;
}
details.grp > summary::-webkit-details-marker { display: none; }
details.grp > summary::before { content: "▸"; font-size: 10px; transition: transform .12s; }
details.grp[open] > summary::before { transform: rotate(90deg); }
details.grp > *:not(summary) { margin-left: 12px; margin-right: 12px; }
details.grp > *:last-child { margin-bottom: 12px; }
.ctl { margin: 9px 0; }
.ctl label { display: flex; justify-content: space-between; font-size: 12px; color: var(--dim); margin-bottom: 3px; gap: 8px; }
/* A checkbox caption sits beside its box; the space-between above is for a
   slider's name and read-out and pushed the caption to the far edge. */
.ctl label.check { justify-content: flex-start; }
.ctl label b { color: var(--text); font-variant-numeric: tabular-nums; font-weight: 600; }
.ctl input[type=range] { width: 100%; accent-color: var(--accent); }
.ctl input[type=text], .ctl select, .ctl input[type=number] { width: 100%; }
.hint { font-size: 11px; color: var(--dim); margin: 4px 0 0; }
/* The section nav. A row of underlined text links gave every page the same
   weight and put the group labels on the same line as the pages themselves;
   this is a segmented control on a rail, so the active page is a shape and
   not a two-pixel line. */
.tabs {
	display: flex; gap: 3px; align-items: center; flex-wrap: wrap;
	margin: 0 0 14px; padding: 4px;
	background: var(--panel); border: 1px solid var(--line);
	border-radius: var(--r-lg);
}
.tabs button {
	background: none; border: 1px solid transparent; border-radius: var(--r-md);
	padding: 6px 12px; color: var(--dim); font-size: 13px;
}
.tabs button:hover:not(.active) { color: var(--text); background: var(--surface-hover); }
.tabs button.active {
	color: var(--on-accent, #fff); background: var(--accent); border-color: var(--accent);
	font-weight: 600;
}

/* The Draft board's own two faces: the board, and the editable table behind
   the Player Edit toggle. A segmented control rather than a tab, because
   these are two views of one page and not two pages. */
.modebar { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin: 0 0 12px; }
.segmented {
	display: inline-flex; gap: 2px; padding: 3px;
	background: var(--panel-2); border: 1px solid var(--line);
	border-radius: 999px;
}
.segmented button {
	background: none; border: 1px solid transparent; border-radius: 999px;
	padding: 4px 14px; font-size: 12.5px; color: var(--dim);
}
.segmented button:hover:not(.on) { color: var(--text); }
.segmented button.on {
	background: var(--panel); border-color: var(--line); color: var(--text);
	font-weight: 600; box-shadow: var(--shadow-1);
}

table { border-collapse: separate; border-spacing: 0; width: max-content; min-width: 100%; font-size: 12.5px; }
th, td { text-align: left; padding: var(--rowH) 8px; border-bottom: 1px solid var(--line); white-space: nowrap; }
th { background: var(--panel); color: var(--dim); font-weight: 600; cursor: default; font-size: 11px; text-transform: uppercase; letter-spacing: .05em; position: sticky; top: 0; z-index: 3; }
/* The player's name stays put while the other 22 columns scroll past it. */
th.sticky, td.sticky {
	position: sticky; left: 0; background: var(--panel); z-index: 2;
	box-shadow: 1px 0 0 var(--line);
}
th.sticky { z-index: 4; }
tbody tr:hover td { background: var(--panel-2); }
tbody tr:hover td.sticky { background: var(--panel-2); }
tbody td { vertical-align: top; }
td.wrap { white-space: normal; min-width: 250px; max-width: 340px; font-size: 11.5px; line-height: 1.35; }
/* A prospect can collect a dozen honors. Left to wrap freely, one such cell
   made its whole row 150px tall and the table stopped being a table. */
.clamp {
	display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
	line-clamp: 2; overflow: hidden;
}
td.num, th.num { text-align: right; font-variant-numeric: tabular-nums; }
.scroll {
	max-width: 100%; overflow-x: auto; max-height: calc(100vh - 210px);
	overflow-y: auto; border: 1px solid var(--line); border-radius: var(--r-lg);
	background: var(--panel);
}
.density-compact { --rowH: 2px; }
.density-comfortable { --rowH: 9px; }
.tag { display: inline-block; font-size: 10.5px; padding: 1px 6px; border-radius: 999px; border: 1px solid var(--line); color: var(--dim); }
/* Promoted to tokens: these used to be hex values repeated across a
   hand-maintained selector list of every dark theme, so each new dark theme
   had to be added to four lists. A theme now sets the six tokens in its own
   block (see the theme definitions at the top) and these rules never change. */
.tag.arch { border-color: var(--tag-arch-border, #9dc0e4); color: var(--tag-arch-text, #17548d); }
.tag.posbest { border-color: var(--accent); color: var(--accent); margin-left: 2px; }
.tag.posbest.gem { border-color: var(--accent-2); color: var(--accent-2); font-weight: 600; }
.gem { color: var(--accent-2); }
.tag.pro { border-color: var(--tag-pro-border, #d8b98a); color: var(--tag-pro-text, #8a5714); }
@media (prefers-color-scheme: dark) {
	:root:not([data-theme="light"]) {
		--tag-arch-border: #3b5b7d; --tag-arch-text: #8fc0f0;
		--tag-pro-border: #6b5230; --tag-pro-text: var(--accent-2);
		--win-bg: rgba(87,199,138,.10); --upset-bg: rgba(255,180,84,.13);
	}
}
.up { color: var(--good); } .down { color: var(--bad); }
/* A schedule row for a loss: text color only, no ::before arrow. .down
   above carries that arrow for its inline-number callers (a stat delta),
   and reusing it on a <tr> inserted the arrow as an extra table cell,
   pushing every column in the row one to the right. */
.loss { color: var(--bad); }
.cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 12px; }
.card {
	border: 1px solid var(--line); border-radius: var(--r-lg); padding: 12px 14px;
	background: var(--panel); box-shadow: var(--shadow-1);
}
.card h4 { margin: 0 0 6px; font-size: 13px; }
/* The name and the per-note copy button share a line; the button is quiet
   until the card is hovered or the button itself is focused, so seventy cards
   do not read as seventy buttons. */
.notehead { justify-content: space-between; gap: 6px; flex-wrap: nowrap; }
.notehead h4 { margin: 0; }
.notehead button { opacity: 0; transition: opacity .12s ease; }
.card:hover .notehead button, .notehead button:focus-visible { opacity: 1; }
@media (hover: none) { .notehead button { opacity: 1; } }
.note { font-family: var(--mono); font-size: 11.5px; white-space: pre-wrap; color: var(--dim); }
.bracket { display: flex; gap: 18px; overflow-x: auto; padding-bottom: 10px; }
.round { min-width: 190px; }
.round h5 { margin: 0 0 8px; font-size: 11px; color: var(--dim); text-transform: uppercase; letter-spacing: .07em; }
.game { border: 1px solid var(--line); border-radius: 6px; margin-bottom: 6px; font-size: 11.5px; overflow: hidden; }
.game div { padding: 3px 7px; display: flex; gap: 6px; }
.game .w { background: var(--win-bg, rgba(26,127,75,.10)); color: var(--text); font-weight: 600; }
.game .l { color: var(--dim); }
/* The seed. It was --dim at .8 opacity on a tinted winner row: 2.8-3.5:1 in
   every theme. Full-strength text, set apart by weight and width instead. */
.game .sd { width: 20px; color: var(--text); font-weight: 600; font-variant-numeric: tabular-nums; }
/* Upset intensity scales with the seed differential: a 15-over-2 is not
   the same event as a 10-over-7. */
.game.upset1 .w { background: var(--upset-bg, rgba(162,96,26,.13)); }
.game.upset2 .w { background: var(--upset-bg2, rgba(162,96,26,.24)); }
.game.upset3 .w { background: var(--upset-bg3, rgba(190,72,20,.34)); }
/* A top seed is marked by weight, not by the accent: the accent on a tinted
   winner row was under 4.5:1 in four themes. */
.game .sdtop { color: var(--text); font-weight: 800; }
.game .sdlow { font-weight: 400; }
/* Hovering a team lights its whole path through the rounds. */
.game .pathlit { color: var(--accent); font-weight: 700; }
/* Connector elbows: each game joins toward its parent round, which is what
   makes the columns read as a bracket rather than a list of games. */
.round .game { position: relative; }
.round + .round .game::before {
	content: ""; position: absolute; left: -10px; top: 50%;
	width: 9px; height: 1px; background: var(--line);
}
/* Poll movement coloring. */
td.pollup { color: var(--good); }
td.polldown { color: var(--accent-2); }
.game .gn { flex: 1; overflow: hidden; text-overflow: ellipsis; }
.game .score { font-size: 10.5px; color: var(--dim); font-variant-numeric: tabular-nums; border-top: 1px solid var(--line); }
.drop { border: 2px dashed var(--line); border-radius: 12px; padding: 46px; text-align: center; color: var(--dim); margin: 40px auto; max-width: 620px; }
.drop.over { border-color: var(--accent); color: var(--text); }
.drop.busy { border-color: var(--accent-2); }
.pill { font-size: 11px; color: var(--dim); border: 1px solid var(--line); border-radius: 999px; padding: 2px 9px; }
.rowflex { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
h3 { font-size: 14px; margin: 18px 0 8px; }
h3:first-child { margin-top: 0; }
.legendline { font-size: 11.5px; color: var(--dim); margin: -2px 0 10px; }

/* ---- focus + affordances ------------------------------------------------- */
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px; }
th.sortable { cursor: pointer; }
th.sortable:hover { color: var(--text); }

/* ---- controls ------------------------------------------------------------ */
.seedpill { cursor: pointer; font-family: var(--mono); }
.status {
	font-size: 12px; color: var(--accent-2); border: 1px solid var(--line);
	border-left: 3px solid var(--accent-2); border-radius: 6px;
	padding: 7px 10px; margin: 0 0 10px;
}
/* The banner sits inside <main>, below the sticky header (z-index 60). It used
   to be z-index 50 against a header of 20 and rendered over the top of it. */
#errBanner, #warnBanner {
	position: sticky; top: var(--stickyTop); z-index: 10; color: #fff;
	padding: 10px 14px; font-size: 13px; white-space: pre-wrap; cursor: pointer;
	border-radius: 6px; margin-bottom: 10px;
}
#errBanner { background: #7f1d1d; }
#warnBanner { background: #7a5514; }
.filters { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; margin: 0 0 10px; }
.filters input[type=search] { min-width: 220px; flex: 1 1 220px; }
.check { display: inline-flex; align-items: center; gap: 5px; font-size: 12px; color: var(--dim); }
.checks { display: grid; gap: 4px; }
.unit { font-size: 10.5px; color: var(--dim); margin: 2px 0 0; opacity: .85; }
.unit.notesuffix { margin: 0; }
tr.locked td.sticky, tr.locked td { background: rgba(162,96,26,.08); }
tr.picked td.sticky, tr.picked td { background: rgba(9,105,218,.10); }
tbody tr { cursor: pointer; }
.progress { height: 6px; background: var(--panel-2); border-radius: 3px; overflow: hidden; margin: 6px 0; }
.progress span { display: block; height: 100%; background: var(--accent); width: 0; transition: width .1s linear; }

.editor { border: 1px solid var(--accent); border-radius: 10px; padding: 12px 14px; margin-top: 14px; background: var(--panel); }
.editor h3 { margin: 0; }
.editor .rowflex:first-child { justify-content: space-between; }
.editor h4 { margin: 14px 0 6px; font-size: 12px; color: var(--dim); text-transform: uppercase; letter-spacing: .06em; }
.editgrid { display: grid; grid-template-columns: repeat(auto-fit, minmax(168px, 1fr)); gap: 10px; margin: 10px 0; }
.editgrid > .ctl { min-width: 0; }
/* width:100% applied to the lock checkboxes too, which stretched each one to
   the full column and pushed its caption out of the row. */
.editgrid input:not([type=checkbox]), .editgrid select { width: 100%; }
.editgrid input[type=checkbox] { width: auto; }
.lockrow { display: flex; gap: 6px; align-items: center; margin-bottom: 3px; }
.lockrow input[type=checkbox] { flex: none; margin: 0; }
/* .ctl label is a space-between flex row for the slider read-outs; inside a
   lock row that stretched the caption away from its own checkbox. */
.lockrow label { display: inline; justify-content: flex-start; margin: 0; font-size: 12px; }
.editor h3 { scroll-margin-top: calc(var(--stickyTop) + 10px); }
.ratinggrid { display: grid; grid-template-columns: repeat(auto-fit, minmax(74px, 1fr)); gap: 6px; margin: 8px 0; }
.ratinggrid label { display: block; font-size: 10.5px; color: var(--dim); }
.ratinggrid input { width: 100%; padding: 2px 4px; font-size: 12px; }
.ratinggrid .changed input { border-color: var(--accent); }
.warnbox {
	border: 1px solid var(--accent-2); border-left-width: 3px; border-radius: 6px;
	padding: 7px 10px; margin: 8px 0; font-size: 12px; color: var(--accent-2);
}

/* ---- histograms ---------------------------------------------------------- */
.hist { display: grid; gap: 3px; margin: 6px 0; }
.histrow { display: grid; grid-template-columns: 48px 1fr 32px; gap: 6px; align-items: center; font-size: 11px; }
.histlabel { color: var(--dim); font-variant-numeric: tabular-nums; text-align: right; }
.histbar { background: var(--accent); height: 10px; border-radius: 3px; min-width: 1px; display: block; }
.histcount { color: var(--dim); font-variant-numeric: tabular-nums; }
.histaxis {
	display: grid; grid-template-columns: 48px 1fr 32px; gap: 6px;
	font-size: 10px; color: var(--dim); margin-top: 2px;
}
.histaxis .ticks { display: flex; justify-content: space-between; }
/* The original ratings drawn as an outline over the rebuilt bars. */
.hist.ghosted .histrow { grid-template-columns: 48px 1fr 58px; }
.histtrack { position: relative; display: block; height: 10px; }
.histtrack .histbar { position: absolute; left: 0; top: 0; }
.histbar.histghost { background: transparent; box-shadow: inset 0 0 0 1.5px var(--accent-2); }

/* ---- class curve (ovr / pot by pick) and the other distribution cards ---- */
.curvecard { grid-column: 1 / -1; }
.curvesvg { width: 100%; height: auto; max-height: 260px; display: block; }
.curvesvg .axis { stroke: var(--line); stroke-width: 1; }
.curvesvg .tick { fill: var(--dim); font-size: 9px; }
.curvesvg .ln { fill: none; stroke-width: 2; }
.curvesvg .ln.orig { stroke: var(--dim); }
.curvesvg .ln.rebuilt { stroke: var(--accent); }
.curvesvg .ln.ref { stroke: var(--accent-2); stroke-dasharray: 5 4; }
.curvekey { display: flex; flex-wrap: wrap; gap: 4px 14px; font-size: 11px; color: var(--dim); margin: 4px 0; align-items: center; }
.curvekey i { display: inline-block; width: 16px; height: 0; border-top: 2px solid var(--dim); vertical-align: middle; margin-right: 4px; }
.curvekey i.rebuilt { border-color: var(--accent); }
.curvekey i.ref { border-top-style: dashed; border-color: var(--accent-2); }
tr.myteam td { background: color-mix(in srgb, var(--accent) 14%, transparent); }
.mocktaste { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; }
.mocktaste input { width: 90px; }
.mockteams summary { cursor: pointer; font-weight: 600; font-size: 13px; }
.contactsheet td.mono { font-family: var(--mono); font-size: 11px; }
.contactsheet td.hint { max-width: 520px; }
.keptbox { display: grid; gap: 10px; min-width: min(560px, 100%); }
.keptform, .keptrow { display: grid; gap: 6px; }
.keptlist { display: grid; gap: 10px; max-height: 46vh; overflow: auto; }
.keptrow { padding: 8px; border: 1px solid var(--line); border-radius: var(--r-md); }
.keptbox textarea, .keptbox input[type="text"] { width: 100%; box-sizing: border-box; }
#btnInstall { white-space: nowrap; }

/* ---- mirrored bracket ---------------------------------------------------- */
/* A region needs four 190px columns to lay out without scrolling. Putting two
   regions side by side inside a 1100px main area gave each of them about
   430px, so every region scrolled horizontally inside a page that also
   scrolled — nested horizontal scroll, which is close to unusable. Regions get
   the full width by default and only sit side by side when there is genuinely
   room for both. */
.bracketwrap { display: grid; gap: 16px; grid-template-columns: 1fr; }
.bracketwrap .half { min-width: 0; display: grid; gap: 14px; }
/* Without this the region sizes to the whole bracket (814px at the sample
   class) and the PAGE scrolls sideways on a phone instead of the bracket
   scrolling inside its own box: every other tab sat in a 390px column with
   half the viewport blank to the right of it. */
.bracketwrap .half > *, .regionbox { min-width: 0; max-width: 100%; }
.bracketwrap .centercol { min-width: 0; order: 1; }
@media (min-width: 1850px) {
	.bracketwrap { grid-template-columns: 1fr 240px 1fr; align-items: start; }
	.bracketwrap .centercol { order: 0; align-self: center; }
}
.bracketwrap .centercol h4 { font-size: 11px; color: var(--dim); text-transform: uppercase; letter-spacing: .07em; margin: 10px 0 6px; }
.regionbox h4 { margin: 0 0 6px; font-size: 12px; }
/* Compact mode: winners only, no nested horizontal scroll. */
.compactregion { font-size: 12px; }
.compactregion ol { margin: 4px 0 0; padding-left: 18px; }
.compactregion li { margin: 2px 0; color: var(--dim); }
.compactregion li b { color: var(--text); font-weight: 600; }

/* ---- modal --------------------------------------------------------------- */
.modal {
	position: fixed; inset: 0; background: rgba(0,0,0,.45); z-index: 100;
	display: flex; align-items: center; justify-content: center; padding: 20px;
}
/* display:flex beats the `hidden` attribute's display:none, so without this the
   dialog is invisible but still covers the page and swallows every click. */
.modal[hidden] { display: none; }
.modalbox {
	background: var(--panel); border: 1px solid var(--line); border-radius: 10px;
	padding: 16px 18px; max-width: 560px; width: 100%; max-height: 80vh; overflow: auto;
}
.modalbox h3 { margin: 0 0 10px; }
/* The import-route table is five columns of two or three words each. Left to
   size itself it was 200px wider than the dialog, so the two columns that
   answer "does this keep my awards" sat off the right edge behind a scrollbar
   nobody looks for inside a dialog. Fixed layout wraps them instead. */
.modalbox table.routes { width: 100%; table-layout: fixed; font-size: 11.5px; }
.modalbox table.routes th, .modalbox table.routes td {
	white-space: normal; overflow-wrap: anywhere; padding: 4px 6px; vertical-align: top;
}
.modalbtns { justify-content: flex-end; margin-top: 12px; }

/* ---- narrow viewports ---------------------------------------------------- */
@media (max-width: 860px) {
	.layout { flex-direction: column; max-width: 100%; }
	.layout > * { width: 100%; max-width: 100%; min-width: 0; }
	aside {
		width: 100%; height: auto; position: static; border-right: none;
		border-bottom: 1px solid var(--line); padding-bottom: 20px;
	}
	main { padding: 12px; }
	main > *, #app, #view, #view > * { max-width: 100%; min-width: 0; }
	.scroll { max-height: none; }
	header { padding: 10px 12px; gap: 8px; }
	header h1 { font-size: 14px; width: 100%; }
	.bracketwrap { flex-direction: column; }
	.bracketwrap .centercol { align-self: stretch; }
}
@media (max-width: 520px) {
	.filters input[type=search] { min-width: 0; }
	.cards { grid-template-columns: 1fr; }
}
.archlist { max-height: 230px; overflow-y: auto; display: grid; gap: 2px; border: 1px solid var(--line); border-radius: 6px; padding: 6px; }
.archrow { display: grid; grid-template-columns: 1fr 62px; gap: 6px; align-items: center; font-size: 11px; color: var(--dim); }
.archrow input { padding: 2px 5px; font-size: 11px; }
.archname { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; cursor: help; }
.archname:hover { color: var(--text); }
.colpicker { display: grid; grid-template-columns: repeat(auto-fill, minmax(96px, 1fr)); gap: 3px; }

/* ---- range filters, sort stack and the new table furniture --------------- */
.ranges { gap: 6px; }
.rangefilter { display: inline-flex; align-items: center; gap: 4px; }
.rangefilter select { max-width: 130px; }
.rangebox { width: 74px; }
.sortstack { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; margin: 6px 0; }
.sortstack .chip {
	display: inline-flex; align-items: center; gap: 4px;
	background: var(--panel-2); border: 1px solid var(--line); border-radius: 999px;
	padding: 1px 4px 1px 9px; font-size: 11px; color: var(--dim);
}
.sortstack .chip button { padding: 0 5px; line-height: 1.5; }
th.sorted { color: var(--text); }

/* The horizontal-scroll shadow. With every stat column on, the sticky name
   column looks like the left edge of the table and there is nothing to say the
   rest of it is off to the right. */
.scroll {
	background:
		linear-gradient(to right, var(--panel) 30%, rgba(0,0,0,0)) left / 40px 100% no-repeat,
		linear-gradient(to left, var(--panel) 30%, rgba(0,0,0,0)) right / 40px 100% no-repeat,
		radial-gradient(farthest-side at 0 50%, rgba(0,0,0,.18), rgba(0,0,0,0)) left / 14px 100% no-repeat,
		radial-gradient(farthest-side at 100% 50%, rgba(0,0,0,.18), rgba(0,0,0,0)) right / 14px 100% no-repeat;
	background-attachment: local, local, scroll, scroll;
}

/* Row focus has to be visible on a dark theme against a hover highlight, so it
   gets an inset ring rather than the page-wide outline, which the sticky first
   column would clip anyway. */
tbody tr:focus-visible { outline: none; }
tbody tr:focus-visible td { box-shadow: inset 0 2px 0 var(--accent), inset 0 -2px 0 var(--accent); }
tbody tr:focus-visible td.sticky { box-shadow: inset 2px 0 0 var(--accent), inset 0 2px 0 var(--accent), inset 0 -2px 0 var(--accent); }
tbody tr.editing td { background: rgba(9,105,218,.14); }
tbody tr.editing td.sticky { background: rgba(9,105,218,.14); }

/* ---- banners ------------------------------------------------------------- */
/* Same trap the modal fell into: display:flex beats the `hidden` attribute's
   display:none, so without the second rule an empty banner sits on the page
   whenever there is nothing to say. */
.banner { display: flex; align-items: flex-start; gap: 10px; }
.banner[hidden] { display: none; }
.bannertext { flex: 1; white-space: pre-wrap; }
.bannerclose {
	background: rgba(255,255,255,.16); border: 1px solid rgba(255,255,255,.3);
	color: #fff; border-radius: 6px; line-height: 1; padding: 2px 8px; cursor: pointer;
	font-size: 15px;
}
.bannerclose:hover { background: rgba(255,255,255,.28); }
.bannerbtn {
	background: rgba(255,255,255,.16); border: 1px solid rgba(255,255,255,.3);
	color: #fff; border-radius: 6px; padding: 2px 10px; cursor: pointer; font-size: 12px;
	white-space: nowrap;
}
.bannerbtn:hover { background: rgba(255,255,255,.28); }

/* ---- settings panel ------------------------------------------------------ */
.rerun { margin: 2px 0 0; font-size: 10px; color: var(--dim); opacity: .8; letter-spacing: .02em; }
.archgroup {
	display: flex; align-items: center; gap: 6px;
	margin: 8px 0 2px; padding-top: 6px; border-top: 1px solid var(--line);
	font-size: 10px; text-transform: uppercase; letter-spacing: .06em; color: var(--dim);
}
.archgroup:first-child { margin-top: 0; padding-top: 0; border-top: none; }
.archgroup .archname { flex: 1; cursor: default; }
.archgroup button { padding: 0 6px; font-size: 10px; }
/* Weight, and what that weight actually produced last run. */
.archrow { grid-template-columns: 1fr 42px 62px; }
.archgot { font-variant-numeric: tabular-nums; text-align: right; font-size: 10px; color: var(--accent); }
.archgot.none { color: var(--dim); opacity: .5; }
/* Balanced has no rarity weight to type, so the third cell carries the
   sentence that says which control does move it. See paintArchWeights. */
.archrow .archnote { font-size: 10px; text-align: right; opacity: .75; }
.archlist { max-height: 300px; }
.archfilters { gap: 6px; margin-bottom: 4px; }
.archfilters input[type=search] { flex: 1 1 120px; min-width: 0; }
.archfilters select { max-width: 130px; font-size: 11px; }
.archfilters .check { font-size: 11px; }
/* The per-fieldset jump list, narrow viewports only: eight fieldsets and
   fifty controls stacked above the table is a long scroll once the panel
   is open, and a collapse toggle alone does not shorten it. */
.jumplist { display: none; }
@media (max-width: 860px) {
	.jumplist { display: flex; flex-wrap: wrap; gap: 6px; margin: 4px 0 10px; }
	.jumplist a { font-size: 11px; padding: 2px 8px; border: 1px solid var(--line); border-radius: 12px; color: var(--dim); text-decoration: none; }
	.jumplist a:hover, .jumplist a:focus { color: var(--text); border-color: var(--accent); }
}

/* ---- motion -------------------------------------------------------------- */
/* Nothing here is load-bearing animation, so honor the preference outright. */
@media (prefers-reduced-motion: reduce) {
	*, *::before, *::after {
		animation-duration: .001ms !important;
		animation-iteration-count: 1 !important;
		transition-duration: .001ms !important;
		scroll-behavior: auto !important;
	}
}

/* ---- narrow viewports ---------------------------------------------------- */
/* The aside + main grid was desktop-only and said so nowhere. It stacks now,
   the table keeps its own horizontal scroll rather than pushing the page
   sideways, and the settings panel collapses to one column. */
@media (max-width: 860px) {
	.editgrid, .ratinggrid { grid-template-columns: repeat(auto-fill, minmax(110px, 1fr)); }
	.sortstack { font-size: 11px; }
	.rangefilter select { max-width: 104px; }
	.rangebox { width: 58px; }
}
@media (max-width: 560px) {
	header .btngroup { flex: 1 1 auto; }
	header h1 span { display: none; }
	.exportsum { flex: 1 1 100%; max-width: none; white-space: normal; }
	.filters { gap: 5px; }
	.filters select, .filters button { font-size: 11px; }
	.rangefilter { flex: 1 1 100%; }
	.rangefilter select { max-width: none; flex: 1; }
	.scroll { max-height: none; }
	/* A 40-column table on a phone is a horizontal scroll no matter what, so
	   make the row header narrower rather than pretending otherwise. */
	th.sticky, td.sticky {
		max-width: 44vw; overflow: hidden; text-overflow: ellipsis;
	}
	/* The name wraps rather than collapsing to "☆ …" beside the star and the
	   "?" button; the "?" (also on the player page) goes to make room. */
	td.sticky { white-space: normal; overflow-wrap: anywhere; min-width: 92px; }
	td.sticky .whybtn { display: none; }
	.editgrid, .ratinggrid { grid-template-columns: repeat(auto-fill, minmax(92px, 1fr)); }
}

/* ---- an explanation where an empty table would be ------------------------ */
/* A filter that matched nothing used to render an empty table and nothing
   else, which reads as a broken tool rather than as an empty result. */
.empty-state { text-align: center; padding: 28px 18px; }
.empty-state h4 { margin: 0 0 6px; }
.empty-state .hint { margin: 0 0 12px; }

/* ---- risers, fallers, locks: not color alone ---------------------------- */
/* Movement and lock state were encoded in color only, which is invisible to
   the eight percent of men with a color vision deficiency and to anyone
   reading a screenshot in grayscale. The glyph carries the same information. */
.up::before { content: "▲ "; font-size: .85em; }
.down::before { content: "▼ "; font-size: .85em; }
/* The small change beside a rating ("68 ▼1"): the arrow is the sign, so the
   text is the size alone and the gap is before the arrow, not after it. */
.delta { margin-left: .35em; font-size: .9em; }
.delta.up::before { content: "▲"; }
.delta.down::before { content: "▼"; }
tr.locked td.sticky::after { content: " 🔒"; font-size: .85em; }
tr.picked td.sticky::after { content: " ✓"; font-size: .9em; }

/* ---- phones: one prospect per card --------------------------------------- */
/* Forty columns is a horizontal scroll on a phone however it is arranged, so
   the table stops being a table: each row becomes a card and each cell prints
   its own column label, which is what data-label is for.

   These rules were written for a `.cardtable` class that NOTHING EVER ADDED,
   so for as long as they have existed a phone has been getting the
   forty-column desktop table with a sticky name column and no way to see more
   than three columns at once. viewPlayers adds the class now (see cardMode in
   js/views.js), the card layout picks its own twelve columns rather than
   inheriting a selection made for a wide table, and the rules are no longer
   inside a media query — the user can ask for cards at any width, and the
   query only decides the default. */
.cardtable table, .cardtable tbody, .cardtable tr, .cardtable td {
	display: block;
	width: auto;
}
/* Visually hidden rather than moved off-canvas: `left: -9999px` on a thead
   inside a block-level table expands the table's scroll width by ten
   thousand pixels. */
.cardtable thead {
	position: absolute;
	width: 1px;
	height: 1px;
	overflow: hidden;
	clip: rect(0 0 0 0);
	white-space: nowrap;
}
.cardtable .scroll { overflow-x: visible; max-height: none; }
.cardtable tr {
	border: 1px solid var(--line);
	border-radius: 8px;
	margin: 0 0 8px;
	padding: 8px 10px;
	background: var(--panel);
}
.cardtable td {
	display: flex;
	justify-content: space-between;
	gap: 10px;
	border: 0;
	padding: 2px 0;
	text-align: right;
	max-width: none;
}
.cardtable td.sticky {
	position: static;
	font-weight: 600;
	font-size: 15px;
	text-align: left;
	border-bottom: 1px solid var(--line);
	padding-bottom: 6px;
	margin-bottom: 4px;
}
.cardtable td[data-label]::before {
	content: attr(data-label);
	color: var(--dim);
	font-weight: 500;
	text-align: left;
}
.cardtable td:empty { display: none; }

/* ---- the editor sits beside the table, not below it ---------------------- */
/* Appending the editor after a 70-row table put the panel you were about to
   use sixty-six rows below the fold. It is a drawer now: beside the table when
   there is room, above it when there is not, and sticky either way. */
/* width/min-width are load-bearing: #view is a flex child, so a grid inside it
   shrink-to-fits to zero and the table overflows sideways out of a
   zero-width column instead of scrolling inside it. minmax(0, ...) is the
   same guard on the column itself. */
.tablesplit {
	display: grid;
	grid-template-columns: minmax(0, 1fr);
	gap: 12px;
	width: 100%;
	min-width: 0;
}
.tablesplit.open { grid-template-columns: minmax(0, 1fr) minmax(300px, 380px); }
/* Sticky BELOW the sticky header (--stickyTop), not under it: at top: 8px
   the drawer's own heading and close button scrolled beneath the header. */
.tablesplit .drawer {
	align-self: start;
	position: sticky;
	top: calc(var(--stickyTop) + 8px);
	max-height: calc(100vh - var(--stickyTop) - 24px);
	overflow: auto;
	scroll-margin-top: calc(var(--stickyTop) + 8px);
}
@media (max-width: 1100px) {
	/* Not enough width for two columns: the drawer goes above the table, which
	   still keeps it on screen with the row that opened it. */
	.tablesplit.open { grid-template-columns: 1fr; }
	.tablesplit .drawer { order: -1; position: static; max-height: none; }
}

/* ---- the keyboard sheet -------------------------------------------------- */
.shortcuts { display: grid; grid-template-columns: max-content 1fr; gap: 6px 16px; margin: 0; }
.shortcuts dt { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; }
.shortcuts dd { margin: 0; color: var(--dim); }

/* ---- range-filter units and the clickable lock badge --------------------- */
.rangefilter .unit { color: var(--dim); font-size: 11px; align-self: center; }
button.lockbadge {
	font: inherit;
	background: none;
	border: 0;
	padding: 0 2px;
	cursor: pointer;
	color: inherit;
}
button.lockbadge:hover, button.lockbadge:focus-visible { text-decoration: underline; }

/* A run of text that acts as a link but is a button, because it does something
   in the page rather than going anywhere. */
button.linky {
	font: inherit;
	background: none;
	border: 0;
	padding: 0;
	color: var(--accent);
	cursor: pointer;
	text-decoration: underline;
}

/* ---- the explain panels -------------------------------------------------- */
details.explain { margin-top: 10px; border-top: 1px solid var(--line); padding-top: 8px; }
details.explain summary { cursor: pointer; font-weight: 600; }
details.explain > * { margin-top: 8px; }
details.explain .shortcuts dt { font-family: inherit; font-weight: 600; }
table.mini { border-collapse: collapse; font-size: 12px; width: 100%; }
table.mini th, table.mini td { padding: 3px 6px; border-bottom: 1px solid var(--line); }
table.mini td.num { text-align: right; font-variant-numeric: tabular-nums; }
table.mini tr.now { font-weight: 600; }

/* Two prospects side by side: the better number in each row is marked with a
   glyph as well as a color. */
table.compare td.num.up::after { content: " ▲"; font-size: .8em; }

/* The build's signature ratings, printed on the row rather than hidden in a
   title attribute that a touch device never shows. */
.offsets { color: var(--dim); font-size: 11px; white-space: nowrap; }

/* Inline season-shape sparkline in the prospect table. Twelve pixels of
   trend carries what a season average cannot. */
td.sparkcell { padding: 2px 6px; }
.spark { display: block; }
/* Theme-aware by construction. The stroke was already currentColor, which
   inherits the cell's text color and therefore does follow the theme — but
   `opacity: .75` on top of it was measured against the light palette, and the
   dark one's text (#e6edf3) against its panel (#161b22) does not carry a
   quarter of itself away as gracefully as #1c2128 on #ffffff does: a
   1.2px line at 75% is the first thing to disappear on a dark screen.

   So the line uses a token that is defined per theme rather than a constant
   fraction of the inherited color, and the token is the accent rather than
   the text, which makes the trend read as data rather than as faint writing.
   The reference line (see sparkline() in js/views.js) is drawn from the border
   token for the same reason. */
:root { --spark: #0969da; --spark-base: #d6dde4; }
@media (prefers-color-scheme: dark) {
	:root:not([data-theme="light"]) { --spark: #4da3ff; --spark-base: #2a323d; }
}
:root[data-theme="dark"] { --spark: #4da3ff; --spark-base: #2a323d; }
:root[data-theme="ledger-light"] { --spark: #3a5d8f; --spark-base: #ded9e2; }
:root[data-theme="twilight-court"] { --spark: #80a1d4; --spark-base: #383343; }
:root[data-theme="draft-board"] { --spark: #4a6da3; --spark-base: #ded9e2; }
:root[data-theme="fieldhouse"] { --spark: #7a4419; --spark-base: #d7be82; }
:root[data-theme="scout-report"] { --spark: #054a91; --spark-base: #dbe4ee; }
:root[data-theme="night-game"] { --spark: #5b85aa; --spark-base: #414770; }
.spark .sparkline {
	fill: none;
	stroke: var(--spark);
	stroke-width: 1.3;
	stroke-linejoin: round;
	stroke-linecap: round;
}
.spark .sparkbase {
	stroke: var(--spark-base);
	stroke-width: 1;
	stroke-dasharray: 2 2;
}

/* ---- busy state ---------------------------------------------------------- */
/* run() blocks the main thread for 300-600ms and said nothing while it did.
   See beginBusy in js/app.js: the class is set, a frame is allowed to paint,
   and only then does the work start. */
body.busy { cursor: progress; }
body.busy button, body.busy input[type=range] { cursor: progress; }
#status.working { color: var(--accent); }
#status.working::before {
	content: "";
	display: inline-block; width: 9px; height: 9px; margin-right: 7px;
	border: 2px solid var(--accent); border-right-color: transparent;
	border-radius: 50%; vertical-align: -1px;
	animation: spin .7s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
/* The file meter (progressShow). Sticky, so a drop onto a long page still
   shows it, and wide enough to read the byte count on a phone. */
.loadprog {
	position: sticky; top: var(--stickyTop, 0); z-index: 9;
	display: grid; grid-template-columns: 1fr auto auto; gap: 4px 10px; align-items: center;
	font-size: 12px; color: var(--text); background: var(--bg, inherit);
	border: 1px solid var(--line); border-left: 3px solid var(--accent);
	border-radius: 6px; padding: 7px 10px; margin: 0 0 10px;
}
.loadprog[hidden], .loadprog .lp-cancel[hidden] { display: none; }
.loadprog .lp-cancel { padding: 1px 8px; font-size: 12px; }
.loadprog .lp-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.loadprog .lp-detail { color: var(--dim); font-variant-numeric: tabular-nums; text-align: right; }
.loadprog progress { grid-column: 1 / -1; width: 100%; height: 8px; accent-color: var(--accent); }
.loadprog .lp-cancel { grid-column: 1 / -1; justify-self: end; }
.loadprog .lp-cancel[hidden] { display: none; }
@media (prefers-reduced-motion: reduce) {
	#status.working::before { animation: none; border-right-color: var(--accent); }
}


/* ---- numeric input paired with each slider ------------------------------- */
.slider-with-number { display: flex; align-items: center; gap: 8px; }
.slider-with-number input[type=range] { flex: 1; min-width: 0; }
.slider-with-number input[type=number] {
	width: 72px; flex: none; padding: 2px 5px; font-size: 12px;
	font-variant-numeric: tabular-nums; text-align: right;
}

/* ---- settings search ----------------------------------------------------- */
/* The box itself is #settingSearch, styled with the rest of the panel's
   controls; the rule that used to sit here named #settingsSearch, an element
   that has not existed since the search moved into the panel proper. */
.ctl.settings-hidden, details.grp.settings-hidden { display: none; }

/* ---- per-setting modified marker and revert ------------------------------ */
.ctl .modified-dot {
	display: inline-block; width: 7px; height: 7px; border-radius: 50%;
	background: var(--accent); margin-left: 6px; vertical-align: middle;
	flex-shrink: 0;
}
.ctl .revert-btn {
	background: none; border: none; padding: 0 2px; cursor: pointer;
	color: var(--dim); font-size: 13px; line-height: 1; margin-left: 2px;
	flex-shrink: 0; opacity: .7;
}
.ctl .revert-btn:hover { color: var(--accent); opacity: 1; }

/* Group-level reset. Pushed to the right-hand end of the summary row and
   only present when the group holds something modified, so a group at its
   defaults still reads as a plain heading. */
details.grp > summary .grp-reset {
	margin-left: auto; background: none; border: 1px solid var(--line);
	border-radius: 5px; padding: 1px 7px; cursor: pointer; color: var(--dim);
	font: inherit; font-size: 10px; letter-spacing: .06em; text-transform: none;
	flex-shrink: 0;
}
details.grp > summary .grp-reset:hover { color: var(--accent); border-color: var(--accent); }

/* Per-setting randomizer lock. Dim until locked, so eighty padlocks do not
   shout over the sliders they guard. */
.ctl .lock-btn {
	background: none; border: none; padding: 0 2px; cursor: pointer;
	font-size: 11px; line-height: 1; margin-left: 4px;
	flex-shrink: 0; opacity: .35; filter: grayscale(1);
}
.ctl .lock-btn:hover { opacity: .9; }
.ctl .lock-btn.locked { opacity: 1; filter: none; }

/* ---- the settings panel on a narrow screen ------------------------------- */
/* Below 860px the aside stacks above the table, which put eight fieldsets
   between the user and the thing they opened the tool to look at. It is a
   toggle now: hidden by default at that width, shown by the Settings button in
   the header, and unchanged on a desktop where it has its own column. */
/* The toggle shows at every width: on a desktop, closing the panel hands its
   column back to the table. It is an ordinary header button now, so the
   .narrowonly class that used to un-hide it is gone with the rule. */
/* #settings, not `aside`: the player editor drawer is an aside too, and
   these rules hid it along with the panel — on every phone, and on a
   desktop with the panel closed. */
body.settings-closed #settings { display: none; }
@media (max-width: 860px) {
	#settings { display: none; }
	body.settings-open #settings { display: block; }
	/* Somewhere to stop scrolling when it is open. */
	body.settings-open #settings {
		max-height: 70vh;
		overflow-y: auto;
	}
}

/* ---- the archetype frequency panel --------------------------------------- */
/* 117 builds in a 230px scroller with no search: see #archSearch in
   index.html. Filtering and folding are separate classes because a search hit
   has to show through a fold the user set. */
#archSearch { width: 100%; margin: 0 0 6px; padding: 4px 8px; font-size: 12px; }
.archrow.arch-folded, .archrow.arch-filtered, .archgroup.arch-filtered {
	display: none;
}
.archfold {
	padding: 0 4px; min-width: 18px; line-height: 1.2;
	background: none; border: none; color: var(--dim); cursor: pointer;
}
.archfold:hover { color: var(--text); }

/* ---- the prospect row context menu --------------------------------------- */
/* Right-clicking a row is the natural place to say "hold this one beside that
   one", and there was no path to the comparison from the table at all — you
   read a row, decided, and then hunted the name in a dropdown on another tab.
   See rowMenu in js/views.js. */
.rowmenu {
	position: fixed; z-index: 200; min-width: 200px;
	background: var(--panel); border: 1px solid var(--line); border-radius: 8px;
	box-shadow: 0 6px 24px rgba(0,0,0,.22);
	padding: 4px; display: flex; flex-direction: column; gap: 1px;
}
.rowmenu button {
	background: none; border: none; text-align: left;
	padding: 6px 10px; border-radius: 5px; cursor: pointer;
	color: var(--text); font: inherit; font-size: 13px;
}
.rowmenu button:hover, .rowmenu button:focus-visible {
	background: var(--panel-2); color: var(--accent);
}

/* ---- drag-to-reorder columns --------------------------------------------- */
/* The order was the order of the COLUMNS literal, so wanting PPG next to TS%
   in a forty-column table meant editing the source. See buildTable in
   js/views.js; the fixed columns (selection, lock, name) are not draggable
   because the name is what the horizontal scroll is anchored on. */
th.sortable[draggable="true"] { cursor: grab; }
th.sortable.dragging { opacity: .45; cursor: grabbing; }
th.dropbefore { box-shadow: inset 2px 0 0 0 var(--accent); }
th.dropafter { box-shadow: inset -2px 0 0 0 var(--accent); }
/* A card is a stack of labeled lines, so a horizontal drop indicator on it
   means nothing and dragging a heading that is visually hidden means less. */
.cardtable th.dropbefore, .cardtable th.dropafter { box-shadow: none; }

/* ---- grouped tabs, news, player page -------------------------------------- */
.tabgroup {
	align-self: center; font-size: 10px; color: var(--dim);
	text-transform: uppercase; letter-spacing: .1em; padding: 0 4px 0 10px;
	border-left: 1px solid var(--line);
}
.tabs .tabgroup:first-child { border-left: none; padding-left: 0; }
.news { max-width: 760px; }
.newsdate {
	margin: 18px 0 6px; font-size: 11px; color: var(--dim);
	text-transform: uppercase; letter-spacing: .09em;
	border-bottom: 1px solid var(--line); padding-bottom: 3px;
}
.newsitem { padding: 8px 0 4px; }
.newsitem h5 { margin: 0 0 3px; font-size: 14px; }
.newsitem p { margin: 0; font-size: 12.5px; color: var(--text); }
.newsitem .newskind { font-size: 10px; color: var(--dim); text-transform: uppercase; letter-spacing: .07em; margin-top: 3px; }
/* Paragraphs the voice system added under the lede: a stat block, a context
   note, a quote. Indented and slightly dimmed so the lede still reads as the
   story and these read as what a paper puts under it. */
.newsitem .newspara { margin-top: 4px; color: var(--dim); }
.playerhead { align-items: flex-start; gap: 14px; margin-bottom: 10px; }
/* facesjs draws on a 400x600 viewBox, so the box carries that 2:3 ratio —
   a square one crops the jersey off at the chest. */
.facebox { width: 120px; aspect-ratio: 2 / 3; flex: none; border: 1px solid var(--line); border-radius: 8px; overflow: hidden; background: var(--panel-2); }
.facebox > svg { display: block; width: 100%; height: 100%; }
.facebox.small { width: 40px; border-radius: 5px; }

/* Scouting traits on the player page. Each one is a tag with its full note in
   the title, so the page can carry five of them without becoming a paragraph. */
.traitlist { display: flex; flex-wrap: wrap; gap: 4px; }
.tag.trait {
	background: var(--chip, rgba(127, 127, 127, .12));
	border: 1px solid var(--line, rgba(127, 127, 127, .28));
	border-radius: 3px;
	padding: 1px 5px;
	font-size: 11px;
	cursor: help;
}

/* Randomizer scope chips. A row of one-click chips where an eight-item
   <select> used to be: the control is pressed repeatedly and the current
   scope should be readable without opening a menu. */
.chips { display: flex; flex-wrap: wrap; gap: 4px; }
.chips .chip {
	font-size: 11px;
	padding: 2px 7px;
	border-radius: 10px;
	border: 1px solid var(--line, rgba(127, 127, 127, .3));
	background: transparent;
	cursor: pointer;
}
.chips .chip.on {
	background: var(--accent, #3b82f6);
	border-color: var(--accent, #3b82f6);
	color: var(--on-accent, #fff);
}
.ctl .lbl { display: block; font-size: 11px; margin-bottom: 3px; }
/* The settings filter sits above the groups and is not itself a setting. */
#settingSearchBox { border-bottom: 1px solid var(--line, rgba(127,127,127,.2)); padding-bottom: 8px; }
#settingSearch { width: 100%; }

/* PHASE COST, colour-coded. The panel already said which phases a setting
   re-runs; saying it in text means reading it while dragging, which is when
   nobody reads. Green: milliseconds. Amber: the season is re-simulated.
   Red: the whole class is rebuilt. */
.ctl .phasecost { font-size: 10px; letter-spacing: .04em; }
/* These read var(--ok) and var(--warn), which no theme defines, so every
   theme got the same three light-theme hexes — 3.2:1 on white, and a green
   that a dark panel swallowed. The palette already carries a per-theme good,
   warning and bad. */
.ctl .phasecost.cheap { color: var(--good); }
.ctl .phasecost.mid { color: var(--accent-2); }
/* Not red: a slider that re-runs the whole class is not an error, and red
   hint text on every third slider read as a panel full of warnings. */
.ctl .phasecost.dear { color: var(--dim); font-weight: 600; }

/* Modal buttons are thumb targets on a phone. */
@media (max-width: 520px) {
	#modal .checks button, #modal .btngroup button { width: 100%; }
	#modal .checks { gap: 6px; }
}

/* The always-present live region. Off screen rather than display:none, because
   a screen reader ignores a node that is not rendered. */
.visually-hidden {
	position: absolute !important;
	width: 1px; height: 1px;
	margin: -1px; padding: 0; border: 0;
	overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%);
	white-space: nowrap;
}

/* A quadrant record as a shape. Segment width is games played in that
   quadrant; the filled part is the share of them won. */
.quadbar { display: flex; gap: 2px; width: 100%; max-width: 380px; height: 20px; }
.quadseg {
	position: relative; min-width: 18px; border-radius: 2px;
	background: var(--panel-2, rgba(127,127,127,.16));
	overflow: hidden;
}
.quadseg .quadwon { position: absolute; inset: 0 auto 0 0; background: var(--accent, #3b82f6); opacity: .55; }
.quadseg .quadlabel {
	position: relative; display: block; text-align: center;
	font-size: 9px; line-height: 20px; letter-spacing: .03em;
	white-space: nowrap; overflow: hidden;
}
.quadseg.q1 .quadwon { opacity: .8; }
.quadseg.q4 .quadwon { opacity: .3; }

/* Inline sparkline for a team's week-by-week AP ranking. Rank 1 is the top
   edge, 25 the bottom, and an unranked week is a tick along the floor. */
svg.spark { display: block; width: 160px; height: 26px; margin-top: 3px; color: var(--accent, #3b82f6); }

/* The compare tab's rating radar. Fifteen axes on one 0-100 scale, which is
   what makes the shape readable rather than decorative. */
.radarbox { margin: 8px 0; }
svg.radar { display: block; width: 260px; max-width: 100%; height: auto; color: var(--dim); }
.radarlegend { display: flex; flex-wrap: wrap; gap: 10px; font-size: 11px; margin-top: 4px; }
.radaritem { display: inline-flex; align-items: center; gap: 4px; }
.radarswatch { width: 10px; height: 10px; border-radius: 2px; display: inline-block; }

/* The six player-of-the-year ballots, top five each. */
ol.ballot { margin: 4px 0 0; padding-left: 20px; font-size: 12px; }
ol.ballot li { margin-bottom: 2px; }
ol.ballot .unit { display: inline; margin: 0; }

/* Destination weights, grouped by region: twenty-three number boxes in a flat
   list is a list nobody edits, and what people want from it is a region. */
.leaguegroup > summary { cursor: pointer; font-size: 11px; padding: 3px 0; }
.leaguegroup > summary button { margin-left: 6px; }
.leaguegroup .archrow { padding-left: 8px; }

/* The class picker in "Merge into a league file" — one row per loaded file. */
.checklist { display: flex; flex-direction: column; gap: 6px; margin: 10px 0; }
.checklist label { display: flex; align-items: center; gap: 6px; font-size: 12px; }

/* ---- draft board round dividers ------------------------------------------ */
/* A board is read in rounds. Sixty flat rows made the reader count to
   fourteen to find where the lottery ended. */
tr.bandrow td {
	background: var(--panel-2);
	color: var(--dim);
	font-size: 10px;
	text-transform: uppercase;
	letter-spacing: .1em;
	font-weight: 600;
	padding: 5px 8px;
	position: sticky;
	left: 0;
}
tr.bandrow { cursor: default; }
tr.bandrow:hover td { background: var(--panel-2); }

/* ---- the timeline's unplayed seasons ---------------------------------- */
.gaprow td { color: var(--dim); font-style: italic; background: var(--surface-hover); }

/* ---- "why is this player here?" -------------------------------------- */
/* A per-row disclosure of what the engine already computed and mostly did
   not show: board rank against preseason rank, the draft-day event and its
   reason, the anomaly tag, the flavor. A popover under the button rather
   than a title tooltip, so it can be read on a touch screen and copied. */
.whybtn {
	font-size: 10px; line-height: 1; padding: 1px 5px; margin-left: 4px;
	border-radius: 999px; color: var(--dim); vertical-align: middle;
}
.whybtn[aria-expanded="true"], .whybtn:hover { color: var(--text); }
.whypop {
	position: absolute; z-index: 30; min-width: 240px; max-width: 360px;
	background: var(--panel); color: var(--text); border: 1px solid var(--line);
	border-radius: 8px; padding: 8px 10px; box-shadow: 0 6px 22px rgba(0,0,0,.18);
	font-size: 12px; white-space: normal; text-align: left; font-weight: normal;
}
.whypop h5 { margin: 0 0 4px; font-size: 12px; }
.whypop ul { margin: 0; padding-left: 16px; }
.whypop li { margin: 2px 0; }
.whypop .hint { margin-top: 6px; }

/* ---- print --------------------------------------------------------------
   A scouting note, a draft board and a bracket are things people print or
   save as a PDF, and the page printed the settings panel, the header and the
   tab strip around them. Only the view prints, at full width, in black on
   white, with every scroll container unrolled so a forty-column table is
   cut by the page and not by a 400px box. */
@media print {
	:root, [data-theme] { color-scheme: light; }
	html, body { background: #fff !important; color: #000 !important; }
	header, aside, #settings, .tabs, #status, #errBanner, #warnBanner,
	#modal, .filters, .sortstack, .bulkbar, .drop, #fileSummary,
	button:not(.linky), input, select, .whybtn, .whypop { display: none !important; }
	.layout { display: block; }
	main { padding: 0 !important; margin: 0 !important; max-width: none !important; }
	#view { padding: 0; }
	.scroll, .tablewrap, .drawer {
		overflow: visible !important; max-height: none !important; max-width: none !important;
	}
	table { border-collapse: collapse; font-size: 9pt; page-break-inside: auto; }
	tr { page-break-inside: avoid; }
	th, td { border-bottom: 1px solid #bbb; padding: 2px 5px; }
	th.sticky, td.sticky { position: static !important; box-shadow: none !important; }
	.card { border: 1px solid #bbb; page-break-inside: avoid; box-shadow: none; }
	.cards { display: block; }
	.cards .card { margin: 0 0 8px; }
	.linky { color: #000 !important; text-decoration: none; }
	.note { white-space: pre-wrap; }
	.legendline, .hint { color: #444; }
	.up { color: #060; } .down { color: #900; }
	.bracket, .region { page-break-inside: avoid; }
	a[href]::after { content: ""; }
}

/* The run-history select's widest option is a fingerprint, a flavor, a seed
   and a file name; without a cap the select takes that width and pushes the
   header onto a new row and the page sideways on a phone. */
#sessionHistory { max-width: 150px; min-width: 0; flex: 0 1 auto; text-overflow: ellipsis; }
@media (max-width: 520px) { #sessionHistory { max-width: 96px; } }


/* ---- draft board: a prospect with no season ------------------------------ */
/* Dimmed rather than hidden or dropped: he is on the board because he is
   draftable, and the two blank cells beside him are a fact about his year,
   not a gap in the data. */
tr.dnp > td { opacity: .58; font-style: italic; }
tr.dnp > td.sticky, tr.dnp > td.num { font-style: normal; }

/* ---- long tables and the poll grid --------------------------------------- */
/* The AP-poll week table is 60 weeks x 25 rows and the prospect table is 40
   columns; both paint every cell on every render whether or not any of it is
   on screen. `content-visibility` lets the browser skip the ones that are
   not, and `contain-intrinsic-size` keeps the scrollbar honest while they
   are skipped, so the page does not jump as they come into view. The value
   is a rough per-row height, not a measurement: it only has to be close. */
@supports (content-visibility: auto) {
	.scroll > table > tbody > tr {
		content-visibility: auto;
		contain-intrinsic-size: auto 28px;
	}
}
/* Never while printing: a skipped row is a row that does not appear on the
   page, and the print stylesheet above exists to put all of them there. */
@media print {
	.scroll > table > tbody > tr { content-visibility: visible; }
}

/* ---- high contrast and forced colours -----------------------------------

   Two modes the stylesheet did not answer, and in both of them the colours
   that carry MEANING were the ones that went first.

   In Windows' forced-colours mode the system replaces every colour on the
   page with its own small palette. Everything that survives is what the
   browser can map: text, borders, link colour. So anything this tool says
   with a fill alone — a locked row, a changed setting, a good/bad delta, a
   seed higher than its opponent in a bracket — becomes indistinguishable
   from its neighbour, and the table that is most of the interface stops
   encoding anything. The fix is not to fight the mode: it is to make sure
   every one of those states ALSO has a border, an outline or a glyph, and to
   let the system own the colours.

   `prefers-contrast: more` is the milder case — the palette is ours, the
   user has asked for more separation between it — so the lines go darker and
   the dim text stops being dim. */
@media (prefers-contrast: more) {
	:root {
		--line: #94a3b1;
		--dim: #3a444e;
		--chip: rgba(127, 127, 127, .22);
		--surface-hover: rgba(127, 127, 127, .18);
	}
	@media (prefers-color-scheme: dark) {
		:root:not([data-theme="light"]) {
			--line: #5d6b79;
			--dim: #c3ccd5;
		}
	}
	th, td { border-bottom-color: var(--line); }
	.card, .chip, .pill, .tag { border: 1px solid var(--line); }
}

@media (forced-colors: active) {
	/* Let the system paint, and keep the structure it can still see. */
	* { forced-color-adjust: auto; }
	.card, .chip, .pill, .tag, .whypop, .rowmenu, .modalbox, .banner {
		border: 1px solid CanvasText;
	}
	/* State that was carried by a fill alone. A border and an outline survive
	   the mode; a background does not. */
	tr.locked td, td.locked, .lockbadge, .chip.on, .modified {
		outline: 1px solid Highlight;
		outline-offset: -1px;
	}
	/* Good and bad are the two colours in this tool that are genuinely
	   semantic rather than decorative, and forced colours flattens them into
	   one. A glyph says it instead, in the content layer where the mode
	   cannot reach. */
	.up::before { content: "\2191\FE0E "; }
	.down::before { content: "\2193\FE0E "; }
	/* The focus ring is the one thing that must never be optimised away. */
	:focus-visible { outline: 3px solid Highlight; outline-offset: 2px; }
	tbody tr:focus-visible td { outline: 2px solid Highlight; outline-offset: -2px; }
	/* A sparkline or a poll trace is decoration the system cannot recolour
	   usefully; give it an explicit stroke rather than an invisible one. */
	svg .spark, svg .trace { stroke: CanvasText; }
}

/* The caveat under a control that cannot act on the class in front of the
   user — see SLIDER_CAVEAT in js/app.js. Deliberately not styled as an
   error: nothing is wrong, the setting simply has nothing to act on. */
.ctl .caveat {
	margin: 4px 0 0;
	font-size: 11.5px;
	line-height: 1.45;
	color: var(--accent-2);
	border-left: 2px solid var(--accent-2);
	padding-left: 7px;
}
@media (forced-colors: active) {
	.ctl .caveat { border-left-color: CanvasText; }
}

/* ---- what the season was actually played at ------------------------------
   The flavor's bend, the storylines' bends and the class-environment jitter,
   diffed against the settings as sent. See paintEffective in js/app.js. */
#effectiveBox { margin: 8px 0 4px; }
#effectiveBox > summary { font-size: 12px; }
.efflist {
	display: flex;
	flex-direction: column;
	gap: 2px;
	margin: 6px 0;
}
.effrow {
	display: flex;
	justify-content: space-between;
	gap: 10px;
	font-size: 11.5px;
	line-height: 1.5;
	padding: 2px 0;
	border-bottom: 1px solid var(--line);
}
.effrow:last-child { border-bottom: none; }
.effkey { color: var(--dim); }
.effval { font-family: var(--mono); color: var(--text); white-space: nowrap; }
/* The per-control ghost value: what a setting was actually simulated at
   after the flavor, the storylines and the class jitter moved it. Quieter
   than the value the user set, because it is a consequence and not a
   decision — and italic rather than merely dim so it still separates from
   the label at prefers-contrast: more, where dim and text converge. */
.ctl label .effghost {
	color: var(--accent); font-style: italic; font-weight: normal;
	font-size: 11px; margin-left: .4em; white-space: nowrap;
}

/* ---- reroll until ------------------------------------------------------
   One row per condition: a three-state sense control (off / must / must not)
   and the condition it applies to. Two checkboxes per row would have doubled
   the height of a dialog that is already a list. */
.untillist { display: block; }
.untilrow {
	display: grid;
	grid-template-columns: 92px 1fr;
	gap: 10px;
	align-items: center;
	padding: 3px 0;
}
.untilsense { font-size: 12px; }
.untillabel { font-size: 13px; line-height: 1.4; }
@media (max-width: 520px) {
	.untilrow { grid-template-columns: 84px 1fr; gap: 8px; }
}

/* ---- challenges --------------------------------------------------------
   A fixed seed, a target and a budget of settings to reach it with. The bar
   sits under the preset row, where the settings the challenge is played with
   are. See CHALLENGES in js/app.js. */
.challengebar {
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
	gap: 6px 10px;
	margin: 6px 0 2px;
	padding: 7px 9px;
	border: 1px solid var(--line);
	border-left: 3px solid var(--accent);
	background: var(--panel-2);
	font-size: 12px;
}
.challengebar b { font-size: 12.5px; }
p.goal { margin: 2px 0; font-size: 12.5px; color: var(--dim); }
p.goal.met, .goal.met { color: var(--good); font-weight: 600; }
@media (forced-colors: active) {
	.challengebar { border-left-color: CanvasText; }
	p.goal.met::after { content: " \2713\FE0E"; }
}


/* ==== September 2026 UI pass ============================================== */

/* Skip to content: off screen until it has focus. */
.skiplink {
	position: absolute; left: 8px; top: -60px; z-index: 300;
	background: var(--accent); color: var(--on-accent, #fff);
	padding: 8px 14px; border-radius: 6px; font-weight: 600; text-decoration: none;
}
.skiplink:focus { top: 8px; }

/* A column with nothing to sort on looks like one. */
th.nosort { cursor: default; }

/* A table with a scroll box of its own, so its header stays put. */
.scroll.tall { max-height: min(70vh, 760px); }

/* The column picker: grouped, each column's meaning under its name. */
.colgroup { border: 1px solid var(--line); border-radius: 8px; margin: 8px 0; padding: 4px 8px 8px; }
.colgroup legend { font-size: 11px; color: var(--dim); text-transform: uppercase; letter-spacing: .07em; padding: 0 4px; }
.colgroup .colpicker { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 4px 10px; }
.colpick { align-items: flex-start; }
.colpick input { margin-top: 2px; }
.colpicktext { display: flex; flex-direction: column; min-width: 0; }
.colpicklabel { color: var(--text); font-weight: 600; }
.colpicktitle { font-size: 10.5px; color: var(--dim); line-height: 1.3; }

/* Player Edit's compact controls: the class summary is one line of pills
   that opens to the storylines; the filters fold into a disclosure on a
   phone and are always open (summary hidden) wider than that. */
details.classinfo { margin: 0 0 8px; }
details.classinfo > summary { list-style: none; cursor: pointer; display: flex; align-items: center; gap: 6px; }
details.classinfo > summary::-webkit-details-marker { display: none; }
details.classinfo > summary::after {
	content: "storylines ▸"; font-size: 11px; color: var(--accent); white-space: nowrap;
}
details.classinfo[open] > summary::after { content: "storylines ▾"; }
details.classinfo > summary > .rowflex { flex: 1; min-width: 0; }
details.classinfo > p { margin-top: 6px; }
/* U24: the Player Edit filters fold behind one disclosure at every width. */
.filterhead { display: flex; flex-wrap: wrap; align-items: flex-start; gap: 6px 10px; margin: 0 0 8px; }
.filterhead > .quickfilters { margin: 0; flex: 1 1 520px; }
.filterhead > details.filterfold { flex: 1 1 100%; }
details.filterfold { border: 1px solid var(--line); border-radius: 8px; margin: 0; background: var(--panel); }
details.filterfold > summary {
	display: block; cursor: pointer; padding: 6px 12px; font-weight: 600; color: var(--accent); font-size: 13px;
}
details.filterfold[open] > summary { border-bottom: 1px solid var(--line); margin-bottom: 8px; }
details.filterfold > *:not(summary) { margin-left: 10px; margin-right: 10px; }
@media (max-width: 700px) {
	details.classinfo > summary > .rowflex { flex-wrap: nowrap; overflow: hidden; }
	details.classinfo > summary .pill { white-space: nowrap; }
}

/* Phone chrome. A sticky header that wraps to four rows and a tab strip that
   wraps to three covered half of a 390x844 screen before any content. Below
   700px the header scrolls away with the page and the tabs are one row that
   scrolls sideways. */
@media (max-width: 700px) {
	:root { --stickyTop: 0px; }
	header { position: static; }
	.tabs {
		flex-wrap: nowrap; overflow-x: auto; overscroll-behavior-x: contain;
		scrollbar-width: none; -webkit-overflow-scrolling: touch;
	}
	.tabs::-webkit-scrollbar { display: none; }
	/* Eight icon tools and an "Undo reroll" label are 2px wider than a
	   390px phone; let the group wrap rather than push the page sideways. */
	header .btngroup { flex-wrap: wrap; max-width: 100%; }
	.tabs button, .tabs .tabgroup { flex: none; white-space: nowrap; }
	/* Phone cards: the value (and its small delta) sit together at the right
	   edge rather than being spread across the row by space-between. */
	.cardtable td { justify-content: flex-end; align-items: baseline; }
	.cardtable td[data-label]::before { margin-right: auto; order: -2; }
	/* The small change goes before the value, so every value ends on the
	   same right edge ("▼1 68" over "56", not "68 ▼1" beside "56"). */
	.cardtable td > .delta { order: -1; margin: 0 .45em 0 0; }
	.cardtable td.sticky { justify-content: flex-start; }
}

/* The Draft board's sortable headings. */
.playeracts { margin-top: 6px; }
.comparewrap { border: none; border-radius: 0; background: none; max-height: none; }

/* Program history (universe mode): the banners, the level sparkline and the
   season-by-season table on a team page and in the Universe tab. */
.programs { margin-top: 10px; }
.proghead { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 12px 20px; margin: 6px 0 10px; }
.banners { display: flex; flex-wrap: wrap; gap: 6px; }
.pennant {
	display: inline-flex; flex-direction: column; align-items: center; justify-content: center;
	min-width: 54px; padding: 5px 8px 9px; border-radius: 3px 3px 0 0;
	background: var(--panel-2); border: 1px solid var(--accent-2); border-bottom-width: 3px;
	color: var(--text); line-height: 1.15;
	clip-path: polygon(0 0, 100% 0, 100% 100%, 50% 86%, 0 100%);
}
.pennant .bannery { font-weight: 700; font-size: 13px; font-variant-numeric: tabular-nums; }
.pennant .bannerw { font-size: 9.5px; letter-spacing: .06em; text-transform: uppercase; color: var(--dim); }
.levelfig { margin: 0; max-width: 100%; }
svg.spark.levelspark { width: 280px; max-width: 100%; height: auto; aspect-ratio: 280 / 46; color: var(--spark); }
.levelspark .lvlline { fill: none; stroke: var(--spark); stroke-width: 1.6; stroke-linejoin: round; stroke-linecap: round; }
.levelspark .lvldot { fill: var(--spark); }
.levelspark .lvltitle { fill: var(--accent-2); stroke: var(--panel); stroke-width: 1; }
.levelspark .lvlbase { stroke: var(--spark-base); stroke-width: 1; }
.levelspark .lvlmove { stroke: var(--bad); stroke-width: 1; stroke-dasharray: 3 2; opacity: .8; }
.levelfig figcaption { margin-top: 2px; }
table.proghist tr.titlerow td { background: var(--upset-bg, rgba(255,180,84,.13)); font-weight: 600; }
table.proghist td.confmove { box-shadow: inset 3px 0 0 var(--bad); }
.tag.moved { border-color: var(--accent); color: var(--accent); }
.lvup { color: var(--good); font-size: 10.5px; }
.lvdown { color: var(--bad); font-size: 10.5px; }
@media (forced-colors: active) {
	.banner, .pennant { border-color: CanvasText; }
	.levelspark .lvlline, .levelspark .lvlmove { stroke: CanvasText; }
	.levelspark .lvldot, .levelspark .lvltitle { fill: CanvasText; }
}
.peoplegrid { grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); }
.peoplegrid table { width: 100%; }
.peoplegrid .pschool { display: block; font-size: 11px; }
.peoplegrid td.pdetail { color: var(--dim); font-size: 12px; white-space: normal; }
table.proghist tr.now td:first-child { box-shadow: inset 3px 0 0 var(--accent); font-weight: 600; color: var(--accent); }

/* The replay layer: bingo card, mutators, achievements ledger, toasts. */
.mutatorlist { display: grid; gap: 3px; }
.bingogrid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 4px; max-width: 420px; margin: 6px 0; }
.bingosq { border: 1px solid var(--accent-2); border-radius: 3px; padding: 8px 4px; min-height: 48px;
	font-size: 12px; text-align: center; display: flex; align-items: center; justify-content: center; }
.bingosq.on { background: var(--accent-2); color: var(--panel-2); font-weight: 600; }
ul.ledger { list-style: none; padding: 0; margin: 4px 0; }
ul.ledger li { padding: 2px 0; }
ul.ledger li.missing { opacity: 0.55; }
.replaytoasts { position: fixed; right: 16px; bottom: 16px; z-index: 1000; display: grid; gap: 6px;
	max-width: min(360px, calc(100vw - 32px)); pointer-events: none; }
.replaytoast { background: var(--panel-2); color: var(--text); border: 1px solid var(--accent-2);
	border-radius: 4px; padding: 8px 10px; box-shadow: 0 2px 8px rgba(0,0,0,0.3); }

/* The themed prompt (promptModal), the undo history, the inline seed box. */
.promptlabel { display: block; margin-bottom: 4px; }
#modalPrompt { width: 100%; box-sizing: border-box; }
.promptError { color: var(--bad); min-height: 1em; }
ol.undohistory { margin: 4px 0; padding-left: 24px; max-height: 50vh; overflow-y: auto; }
ol.undohistory li { padding: 2px 0; }
ol.undohistory button { text-align: left; }
input.layoutname { width: 12em; }
#seedPillEdit { cursor: text; }

/* The Play tab (js/play.js). */
.playtab .playcards { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 10px; }
.playtab .playgame { padding: 3px 0; }
.playtab .playgame .chip:disabled { opacity: .5; cursor: default; }
.playtab .playpicks li { margin: 2px 0; }
.playtab .playresult { margin-top: 10px; }
.playtab table.playscout tbody tr:hover { background: rgba(127,127,127,.12); }

/* Universe play: a followed program's rows, its card, the dynasty card. */
tr.followed td:first-child { box-shadow: inset 3px 0 0 var(--accent); }
tr.followed td { background: var(--surface-hover); }
tr.followed .pill { margin-left: 6px; }
.followed-card, .dynasty-card { margin: 6px 0; }
.dynasty-won { box-shadow: inset 3px 0 0 var(--accent); }
.dynasty-failed, .dynasty-over-budget { box-shadow: inset 3px 0 0 var(--accent-2); }

/* ---- draft board: notes, movers, sticky filters, honors, row edit -------- */
.classnotes { display: inline-flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.classnotes .lbl { font-size: 11px; color: var(--dim); }
.classnotes .pill { white-space: normal; }
details.aboutboard { margin: -4px 0 10px; font-size: 11.5px; }
details.aboutboard > summary { cursor: pointer; color: var(--accent); width: max-content; }
details.aboutboard .hint { margin: 6px 0 0; max-width: 72ch; }
.cardhead { display: flex; align-items: baseline; gap: 8px; margin: 0 0 6px; }
.cardhead h4 { margin: 0; }
.cardhead button.linkish {
	margin-left: auto; background: none; border: 0; padding: 2px 0; font: inherit;
	font-size: 11px; color: var(--accent); cursor: pointer;
}
.cardhead button.linkish:hover, .cardhead button.linkish:focus-visible { text-decoration: underline; }
.movergrid { display: grid; grid-template-columns: auto auto minmax(0, 1.2fr) minmax(0, 1fr); gap: 2px 10px; font-size: 12px; }
.moverrow { display: contents; }
.movergrid .num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.movergrid .dim { color: var(--dim); }
.movergrid .ell { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.boardfilters {
	position: sticky; top: var(--stickyTop); z-index: 9; background: var(--bg);
	padding: 8px 0; margin: 8px 0 0;
}
.boardfilters .boardcount { margin: 0; white-space: nowrap; }
.boardnone { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.cards.movercards { grid-template-columns: repeat(auto-fill, minmax(min(100%, 340px), 1fr)); }
td.honors { min-width: 180px; max-width: 240px; }
td.honors .honorcount {
	float: left; margin: 1px 6px 2px 0; font-size: 10px; line-height: 1.4; padding: 0 6px;
	border-radius: 999px; border: 1px solid var(--line); background: var(--chip); color: var(--dim); cursor: pointer;
}
td.honors .honorcount[aria-expanded="true"] { color: var(--text); border-color: var(--accent); }
button.rowedit { margin-left: 6px; font-size: 11px; color: var(--dim); opacity: 0; }
tr:hover button.rowedit, tr:focus-within button.rowedit, button.rowedit:focus-visible { opacity: 1; }
@media (hover: none) { button.rowedit { opacity: 1; } }
/* The board fits its container on a wide screen, so Honors wraps inside
   the table instead of running off its right edge. */
@media (min-width: 1100px) { table.boardtable { width: 100%; } table.boardtable td.honors { max-width: none; } }
/* The phone's sticky name column is too narrow to hold it; the ✎ Player
   Edit toggle above the board stands in there. */
@media (max-width: 520px) { button.rowedit { display: none; } }

/* ---- settings panel: tier label, changed badge, quiet re-run cost -------- */
#settingTierHidden { margin: 4px 0 0; }
details.grp > summary { flex-wrap: wrap; row-gap: 4px; }
details.grp > summary .grp-changed {
	margin-left: auto; font-size: 10px; letter-spacing: .04em; text-transform: none;
	color: var(--accent); border: 1px solid var(--accent); border-radius: 999px; padding: 0 7px;
	flex-shrink: 0;
}
details.grp > summary .grp-changed[hidden] { display: none; }
details.grp > summary .grp-changed:not([hidden]) + .grp-reset { margin-left: 6px; }
/* U33: "re-runs: …" appears on hover or focus, at every level. */
/* Shown as an overlay under the control, not in the flow: in the flow it
   grew the control on mousedown (focus) and moved the star out from under
   the pointer, so a click on a select's or checkbox's star landed on the
   row instead and did nothing. */
#settings .ctl { position: relative; }
#settings .ctl .rerun {
	display: none; position: absolute; left: 0; right: 0; top: 100%; z-index: 4;
	margin: 0; padding: 1px 4px; background: var(--panel, var(--bg)); pointer-events: none;
}
#settings .ctl:hover .rerun,
#settings .ctl:focus-within .rerun { display: block; }
/* U33: a long explanation shows two lines; ⓘ more opens the rest. */
#settings p.hintclamp {
	display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
#settings .hintmore {
	display: inline-block; padding: 0 4px; margin: 0; border: none; background: none;
	font-size: 10.5px; color: var(--accent); cursor: pointer; min-height: 0;
}

/* ---- busy: a thin bar over the view while a re-run is in flight ---------- */
#view { position: relative; }
#view[aria-busy="true"]::before {
	content: ""; position: absolute; top: 0; left: 0; right: 0; height: 3px; z-index: 20;
	background: linear-gradient(90deg, transparent, var(--accent), transparent) 0 0 / 40% 100% no-repeat;
	animation: viewbusy 1s linear infinite; pointer-events: none;
}
@keyframes viewbusy { from { background-position: -40% 0; } to { background-position: 140% 0; } }
@media (prefers-reduced-motion: reduce) {
	#view[aria-busy="true"]::before { animation: none; background: var(--accent); opacity: .5; }
}

/* ---- header, tabs and the empty state (audit 2026-09-24, UI 1-8, 19) ------ */
/* U10/U45: the small tools are one "Tools ▾" menu at every width (see
   bindHeaderMore), and inside it every tool says what it is beside its icon. */
.toolsmenu { position: relative; display: inline-flex; }
.headmore { display: inline-flex; border-color: var(--line); white-space: nowrap; }
header:not(.toolsopen) #headerTools { display: none; }
header #headerTools {
	position: absolute; right: 0; top: calc(100% + 4px); z-index: 80;
	display: flex; flex-direction: column; flex-wrap: nowrap; align-items: stretch; gap: 2px;
	min-width: 230px; padding: 4px; border: 1px solid var(--line); border-radius: 8px;
	background: var(--panel); box-shadow: var(--shadow-1);
}
header #headerTools > button { justify-content: flex-start; width: 100%; border-color: transparent; color: var(--text); text-align: left; }
header #headerTools > button[hidden] { display: none; }
header #headerTools > .iconbtn[data-tip]::after,
header #headerTools > .iconbtn[data-tip]:hover::after,
header #headerTools > .iconbtn[data-tip]:focus-visible::after {
	content: attr(data-tip); position: static; transform: none; padding: 0;
	background: none; color: inherit; box-shadow: none; font-size: 13px; white-space: nowrap;
}
@media (max-width: 560px) {
	/* Brand, Settings and Load share the first row instead of the title
	   taking a row of its own. */
	header h1 { width: auto; }
	header .brand { margin-right: auto; }
	header .btngroup[aria-label="Seed"] select { display: none; }
	header .btngroup { flex: 0 1 auto; }
	header { gap: 6px; padding: 8px 12px; }
	header button, header select { padding: 5px 8px; font-size: 13px; }
	header > .btngroup[aria-label="Generate"], header > .btngroup[aria-label="Export"] { order: 2; }
	header > .undogrp { order: 3; }
}
/* Icon-only tools carry their name in data-tip (from aria-label); it shows on
   keyboard focus and hover, since a title never appears on touch or focus. */
.iconbtn[data-tip] { position: relative; }
.iconbtn[data-tip]:focus-visible::after, .iconbtn[data-tip]:hover::after {
	content: attr(data-tip); position: absolute; top: calc(100% + 6px); left: 50%;
	transform: translateX(-50%); z-index: 70; white-space: nowrap; pointer-events: none;
	padding: 3px 8px; border-radius: 6px; font-size: 12px; font-weight: 400;
	background: var(--text); color: var(--bg); box-shadow: var(--shadow-1);
}
/* U31: before a class is loaded the header holds only what works then. */
body.noclass header > .btngroup[aria-label="Generate"],
body.noclass header > .btngroup[aria-label="Seed"],
body.noclass header > .undogrp,
body.noclass header > .btngroup[aria-label="Export"],
body.noclass header > #btnSettings { display: none; }
/* Undo and Redo name the action in the same form; both are capped. */
#btnRedo {
	max-width: 17ch; overflow: hidden; text-overflow: ellipsis;
	white-space: nowrap; display: inline-block;
}
/* Tab groups: a group's tabs wrap together, and the label is a caption on a
   divider rather than a pill-shaped thing that looks clickable. */
.tabset { display: inline-flex; align-items: center; gap: 3px; flex-wrap: nowrap; }
.tabset + .tabset { border-left: 1px solid var(--line); padding-left: 6px; margin-left: 3px; }
.tabs .tabgroup {
	border-left: none; padding: 0 2px 0 4px; font-size: 9px; letter-spacing: .12em;
	opacity: .8; cursor: default; user-select: none;
}
/* U42: one row at every width. Below 1600px the long tab names shorten;
   a strip that still does not fit scrolls sideways (faded at the edge with
   more) instead of wrapping to two or three rows. */
.tabs .tl-short { display: none; }
@media (max-width: 1599px) {
	.tabs .tl-long { display: none; }
	.tabs .tl-short { display: inline; }
	.tabs button { padding: 6px 10px; }
	/* The divider between groups stays; the caption word goes. */
	.tabs .tabgroup { font-size: 0 !important; padding: 0; }
}
@media (min-width: 701px) {
	.tabs {
		flex-wrap: nowrap; overflow-x: auto; overscroll-behavior-x: contain; scrollbar-width: none;
	}
	.tabs::-webkit-scrollbar { display: none; }
	.tabs button, .tabs .tabgroup { flex: none; white-space: nowrap; }
	.tabs .tabgroup { font-size: 10px; }
}
.tabset { flex: none; }
.tabs.more-r { -webkit-mask-image: linear-gradient(to right, #000 85%, transparent); mask-image: linear-gradient(to right, #000 85%, transparent); }
.tabs.more-l { -webkit-mask-image: linear-gradient(to left, #000 85%, transparent); mask-image: linear-gradient(to left, #000 85%, transparent); }
.tabs.more-l.more-r { -webkit-mask-image: linear-gradient(to right, transparent, #000 12%, #000 88%, transparent); mask-image: linear-gradient(to right, transparent, #000 12%, #000 88%, transparent); }
/* Narrow screens: one sideways row, faded at whichever edge has more. */
@media (max-width: 700px) {
	.tabs.more-r { -webkit-mask-image: linear-gradient(to right, #000 85%, transparent); mask-image: linear-gradient(to right, #000 85%, transparent); }
	.tabs.more-l { -webkit-mask-image: linear-gradient(to left, #000 85%, transparent); mask-image: linear-gradient(to left, #000 85%, transparent); }
	.tabs.more-l.more-r { -webkit-mask-image: linear-gradient(to right, transparent, #000 12%, #000 88%, transparent); mask-image: linear-gradient(to right, transparent, #000 12%, #000 88%, transparent); }
}
/* The empty state: the whole box is the file picker. */
#empty { cursor: pointer; }
#empty:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
#empty:hover { border-color: var(--accent); }
#empty h2 { margin: 0 0 8px; font-size: 18px; color: var(--text); }
#empty > p { margin: 0; }
#empty .steps {
	list-style: none; counter-reset: step; display: flex; flex-wrap: wrap; justify-content: center;
	gap: 6px 18px; margin: 4px 0 14px; padding: 0; font-size: 13px; color: var(--dim);
}
#empty .steps li { counter-increment: step; display: flex; align-items: center; gap: 6px; }
#empty .steps li::before {
	content: counter(step); display: inline-flex; align-items: center; justify-content: center;
	width: 20px; height: 20px; border-radius: 50%; font-size: 11px; font-weight: 700;
	background: var(--accent); color: var(--on-accent, #fff);
}
#empty .steps b { color: var(--text); }
#empty.slim .steps { display: none; }
#empty > p.hint { margin-top: 4px; }
#empty .emptyacts { margin-top: 14px; display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; align-items: center; }
@media (max-width: 560px) { .drop { padding: 24px 16px; margin: 16px auto; } }
/* Until a class is loaded the settings have nothing to act on: only the theme
   shows, with a line saying the rest will appear. */
/* A saved universe with no files loaded: the drop box shrinks to its heading. */
.drop.slim { padding: 12px 16px; margin: 8px auto; }
#empty.slim > p, #empty.slim .emptyacts { display: none; }
.noclassnote { display: none; }
body.noclass #settings > :not(.themerow):not(.noclassnote) { display: none; }
/* U31: no stub panel before a class: the theme picker sits in a corner. */
body.noclass #settings {
	display: block !important; position: fixed; right: 12px; bottom: 12px; top: auto; left: auto; z-index: 40;
	width: auto; height: auto; padding: 4px 8px; overflow: visible;
	border: 1px solid var(--line); border-radius: 8px; box-shadow: var(--shadow-1);
}
body.noclass #settings .themerow { display: flex; align-items: center; gap: 6px; margin: 0; }
body.noclass #settings .themerow label { margin: 0; font-size: 12px; }
/* A Settings button that stays in reach on a phone once a class is loaded.
   Bottom-left and above .replaytoasts, which stack bottom-right. */
.fab {
	position: fixed; left: 16px; bottom: 16px; z-index: 1001; min-height: 44px;
	padding: 8px 16px; border-radius: 999px; font-weight: 600;
	background: var(--accent); color: var(--on-accent, #fff); border-color: var(--accent);
	box-shadow: var(--shadow-1);
}
/* button:hover sets position: relative; the FAB must stay fixed. */
button.fab:hover:not(:disabled) { position: fixed; z-index: 1001; }
@media (min-width: 861px) { .fab { display: none !important; } }
body.settings-open .fab { display: none; }
/* WCAG 2.5.8: name links and the small "?" are at least 24px tall. */
button.linky { min-height: 24px; }
.whybtn { min-width: 24px; min-height: 24px; }
body.noclass .fab { display: none; }
/* Room below the last row for the FAB (44px + 16px) so it never covers content. */
@media (max-width: 860px) { body:not(.noclass):not(.settings-open) { padding-bottom: 72px; } }
@media print { .fab { display: none !important; } }

/* The loaded-files line: one summary, the per-class detail on click. */
#fileSummary > summary { cursor: pointer; }
#fileSummary .filelist { max-height: 200px; overflow-y: auto; margin-top: 4px; }

/* Watchlist star, board heatmap, tier breaks and the notes box. */
.watchstar { background: none; border: 0; padding: 0 4px; min-width: 24px; min-height: 24px;
	font-size: 15px; line-height: 1; cursor: pointer; color: var(--dim); }
.watchstar.on { color: #d9a400; }
.watchstar:hover, .watchstar:focus-visible { color: #d9a400; }
tr.tierbreak td { border-top: 2px solid var(--accent); }
.mynote { width: 100%; box-sizing: border-box; font: inherit; }

/* The board on paper: no shading, no stars, and a row is not split by a page. */
@media print {
	td.heat { background: none !important; }
	.watchstar, .mynote { display: none !important; }
	tr { break-inside: avoid; }
	tr.tierbreak td { border-top: 1px solid #000; }
	tr.bandrow td { font-weight: 700; border-bottom: 1px solid #000; }
}

/* ---- quick wins: copy strip, tags, search hint, print header --------------- */
/* The strip above a table that copies it as TSV or markdown. */
.tablecopy { display: flex; align-items: center; gap: 6px; justify-content: flex-end; margin: 2px 0; }
/* A tag the user put on a prospect, beside his name on the board. */
.tag.tagmark { margin-left: 4px; border-color: var(--accent); color: var(--accent); }
.tagrow .chip.on, .boardfilters .tagchip.on { border-color: var(--accent); }
/* A search the box only partly understood. */
input.badquery { outline: 2px dashed var(--bad); outline-offset: 1px; }
.skillchips { display: inline-flex; flex-wrap: wrap; gap: 3px; }
/* Said once on screen, in print only: the class, the seed, the file. */
.printonly { display: none; }
.renamepreview { max-height: 220px; overflow: auto; margin-top: 6px; }
@media print {
	.printonly { display: block !important; font-size: 10pt; margin: 0 0 8px;
		padding-bottom: 4px; border-bottom: 1px solid #000; }
	.printonly b { font-size: 12pt; }
	.tablecopy, .tagrow, .skillchips, .tagmark { display: none !important; }
}

/* ---- remembered session card and pinned settings (audit Q1, Q5) ---------- */
.sessioncard { max-width: 620px; margin: 0 auto 16px; text-align: center; }
.sessioncard p { margin: 0 0 8px; overflow-wrap: anywhere; }
.sessioncard .sessionacts { display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; }
.ctl .star-btn {
	background: none; border: none; padding: 0 2px; cursor: pointer;
	font-size: 12px; line-height: 1; margin-left: 4px; color: var(--dim);
	flex-shrink: 0; opacity: .35;
}
.ctl:hover .star-btn, .ctl:focus-within .star-btn, .ctl .star-btn:hover { opacity: .9; }
.ctl .star-btn[aria-pressed="true"] { opacity: 1; color: var(--accent); }
#grp-pinned > .hint[hidden] { display: none; }

/* ---- 2026-10-10 UI audit (§2) --------------------------------------------- */
/* U5/U6: when the board fits its column it has no scroller of its own — the
   page scrolls, and the headers stick under the sticky filter bar instead of
   sliding beneath it. `fits` is set from JS after layout. */
.scroll.boardwrap.fits { max-height: none; overflow: visible; }
.scroll.boardwrap.fits th { top: calc(var(--stickyTop) + var(--bfH, 48px)); }
/* U19: a labelled tier divider. */
tr.tierbreak td:first-child { position: relative; }
tr.tierbreak td:first-child::before {
	content: attr(data-tier); position: absolute; top: 2px; left: 3px;
	font-size: 9.5px; line-height: 1; padding: 1px 4px; border-radius: 4px;
	background: var(--panel); color: var(--accent); font-weight: 600;
}
.tag.dnptag { font-size: 10px; padding: 0 5px; }
/* U7: on a phone, the mock round and pick give way to Ovr and Pot. */
@media (max-width: 520px) {
	table.boardtable .rdpick { display: none; }
	table.boardtable td.honors { min-width: 140px; }
}
/* U27: touch targets. */
@media (pointer: coarse) {
	button.tiny, .pill, button.chip { min-height: 36px; }
}
/* U26: the player page's labels are prose, not keyboard shortcuts. */
.playerdl dt, .shortcuts.playerdl dt { font-family: inherit; font-weight: 600; font-size: 12px; }
/* U9: a toast can be dismissed, and sits clear of the phone settings button. */
.replaytoast { pointer-events: auto; display: flex; align-items: flex-start; gap: 6px; justify-content: space-between; }
.replaytoast .toastx { background: none; border: 0; color: inherit; cursor: pointer; padding: 0 2px; font-size: 16px; line-height: 1; }
@media (max-width: 860px) { .replaytoasts { bottom: 76px; } }
/* U18: routine information, not a warning. */
#warnBanner.info { background: var(--panel-2); color: var(--text); border: 1px solid var(--line); position: static; }
#warnBanner.info .bannerclose, #warnBanner.info .bannerbtn { color: var(--text); background: var(--chip); }
/* U41: the phone settings sheet's Done bar. */
.sheetdone { display: none; }
@media (max-width: 860px) {
	body.settings-open .sheetdone {
		display: flex; position: sticky; top: 0; z-index: 20; justify-content: space-between;
		align-items: center; padding: 8px 0; background: var(--bg); border-bottom: 1px solid var(--line);
	}
}
/* U12: radar axis labels a person can read. */
svg.radar text { font-size: 11px; }
/* U3/U38: an indeterminate stretch gets a compositor-driven spinner. */
.loadprog.indet .lp-label::before {
	content: ""; display: inline-block; width: 10px; height: 10px; margin-right: 6px;
	border: 2px solid var(--accent); border-right-color: transparent; border-radius: 50%;
	vertical-align: -1px; animation: spin .7s linear infinite; will-change: transform;
}
/* U8: the mock table scrolls inside its own box, not the page. */
.tablewrap { max-width: 100%; overflow-x: auto; }
/* U29: the "On this page" jump row on the long tabs. */
.jumprow { display: flex; flex-wrap: wrap; gap: 4px; align-items: center; margin: 0 0 10px; }
.jumprow .unit { margin: 0 4px 0 0; }
/* U14: tab headings render at one size everywhere. */
#view h2 { font-size: 18px; }
.boardfilters.nosticky { position: static; }
@media (min-width: 861px) {
	.scroll.boardwrap:not(.fits) { max-height: calc(100vh - var(--stickyTop) - 12px); }
}
/* Year and school may wrap so the whole board fits its column (and the
   page, not a nested box, does the scrolling). */
table.boardtable td.wrapok { white-space: normal; min-width: 72px; }
@media (min-width: 861px) {
	table.boardtable td.honors { min-width: 110px; }
	table.boardtable th, table.boardtable td { padding-left: 6px; padding-right: 6px; }
}
/* U47: the board's copy menu. */
.copymenu { position: relative; }
.copymenu > summary { list-style: none; cursor: pointer; }
.copymenu > summary::-webkit-details-marker { display: none; }
.copymenu-list {
	position: absolute; right: 0; top: calc(100% + 4px); z-index: 12; display: grid; gap: 4px;
	background: var(--panel); border: 1px solid var(--line); border-radius: var(--r-md);
	padding: 6px; box-shadow: 0 4px 14px rgba(0,0,0,.15); min-width: 200px;
}
.copymenu-list button { text-align: left; }
tr.tierbreak td:first-child::before { z-index: 1; }
@media (min-width: 861px) {
	table.boardtable td.sticky { white-space: normal; min-width: 170px; }
	table.boardtable td.honors { max-width: 220px; }
}
.copymenu > summary.chip { border: 1px solid var(--line); border-radius: 999px; padding: 3px 10px; background: var(--panel); font-size: 12px; }
/* U44 labels, shortened again where the phone header has no room. */
@media (max-width: 1599px) { .wideonly { display: none; } }

/* ---- U35/U36: board compact rows, its Columns menu, folding movers ----- */
details.moverfold { margin: 0 0 10px; }
details.moverfold > summary { cursor: pointer; color: var(--accent); font-size: 13px; font-weight: 600; margin-bottom: 6px; }
.boardcols { position: relative; }
.boardcols > summary { list-style: none; cursor: pointer; border: 1px solid var(--line); border-radius: 999px; padding: 3px 10px; background: var(--panel); font-size: 12px; }
.boardcols > summary::-webkit-details-marker { display: none; }
.colsmenu {
	position: absolute; right: 0; top: calc(100% + 4px); z-index: 12; display: grid; gap: 2px;
	background: var(--panel); border: 1px solid var(--line); border-radius: var(--r-md);
	padding: 6px; box-shadow: 0 4px 14px rgba(0,0,0,.15); min-width: 210px; max-height: 60vh; overflow-y: auto;
}
.colsmenu label.check { font-size: 13px; white-space: nowrap; }
.colsmenu .unit { margin: 6px 0 2px; }
table.boardcompact td { padding-top: 3px; padding-bottom: 3px; }
table.boardcompact td.wrapok, table.boardcompact td.sticky { white-space: nowrap; }
table.boardcompact td.honors { min-width: 0; }
table.boardcompact td.honors .honorcount { float: none; margin: 0; cursor: default; }
table.boardcompact .whybtn { min-height: 20px; min-width: 20px; }

/* ---- U34: the export menu in headed sections --------------------------- */
.exportmenu .exportgroup { margin: 0 0 12px; }
.exportmenu .exportgroup > h4 {
	margin: 0 0 6px; font-size: 11px; letter-spacing: .08em; text-transform: uppercase; color: var(--accent);
}
.exportmenu details.exportopts { margin-top: 8px; border: 1px solid var(--line); border-radius: 8px; padding: 6px 10px; }
.exportmenu details.exportopts > summary { cursor: pointer; font-size: 12px; color: var(--dim); }
.exportmenu details.exportopts[open] > summary { margin-bottom: 8px; }
#settings p[hidden] + .hintmore { display: none; }
