/* CardSider viewer — the brand palette (electric blue → cyan, a violet
   accent, deep navy) on light surfaces by default, and the same tokens
   on navy for the dark theme. theme.js stamps data-theme on <html>
   before the first paint; every rule below reads a token, and only the
   two blocks here know which theme is on. Semantics are named, not
   coloured: --accent is the brand (interactive, selected, brand marks),
   --green / --orange / --red are outcomes, --blue is informational. */

:root {
  color-scheme: light;
  /* surfaces */
  --bg: #f3f5fa;
  --bg-panel: #ffffff;
  --bg-hover: #eef2f9;
  --bg-hover-2: #e3e9f4;
  --bg-panel-0: rgba(255, 255, 255, 0);
  --border: #dde3ee;
  --border-strong: #b9c4d8;
  --row-border: rgba(17, 26, 51, 0.07);
  --zebra: rgba(17, 26, 51, 0.025);
  --zebra-solid: #f8f9fc;
  --nav-bg: rgba(255, 255, 255, 0.86);
  --overlay: rgba(17, 26, 51, 0.45);
  --edge-shade: rgba(17, 26, 51, 0.18);
  --shadow-pop: 0 12px 34px rgba(17, 26, 51, 0.14), 0 1px 3px rgba(17, 26, 51, 0.06);
  --shadow-card: 0 18px 50px rgba(17, 26, 51, 0.12);
  --shadow-btn: 0 1px 2px rgba(17, 26, 51, 0.12);
  /* text */
  --text: #111a33;
  --text-dim: #5d6a88;
  /* the PSA population bar: one colour a grade, best to worst, and the
     ink a count is printed in on it */
  --pop-10: #15804a; --pop-9: #1e5eff; --pop-8: #b8860b; --pop-7: #8a94a8;
  --pop-ink: #ffffff;
  --on-accent: #ffffff;
  /* how far back the role fireball's unlit picture sits (.role-cold) */
  --role-cold: 0.2;
  /* the role ladder's fill, foot to top: the fire's gold, orange and red
     as the page carries the picture (ROLE_LIT_SAT in app.js) */
  --role-step-0: #dfbc5e;
  --role-step-1: #c2793e;
  --role-step-2: #8f251c;
  /* brand */
  --accent: #1e5eff;
  --accent-strong: #0f4be6;
  --accent-border: rgba(30, 94, 255, 0.45);
  --accent-soft: rgba(30, 94, 255, 0.10);
  --accent-faint: rgba(30, 94, 255, 0.05);
  --accent-glow: rgba(30, 94, 255, 0.25);
  --cyan: #00b4f0;
  --violet: #7b3fe8;
  --brand-a: #18c4ff; --brand-b: #1e5eff; --brand-c: #8a3fff;
  --brand-gradient: linear-gradient(120deg, var(--brand-a) 0%, var(--brand-b) 52%, var(--brand-c) 100%);
  /* informational */
  --blue: #0b74c9;
  --blue-border: rgba(11, 116, 201, 0.45);
  --blue-soft: rgba(11, 116, 201, 0.10);
  --blue-faint: rgba(30, 94, 255, 0.05);
  --purple: #7b3fe8;
  --purple-border: rgba(123, 63, 232, 0.45);
  --pink: #c2185b;
  --pink-border: rgba(194, 24, 91, 0.45);
  /* outcomes */
  --green: #15804a;
  --green-border: rgba(21, 128, 74, 0.5);
  --green-soft: rgba(21, 128, 74, 0.10);
  --orange: #9a6400;
  --orange-border: rgba(154, 100, 0, 0.45);
  --orange-soft: rgba(154, 100, 0, 0.08);
  --red: #d3302f;
  --red-border: rgba(211, 48, 47, 0.5);
  --red-soft: rgba(211, 48, 47, 0.08);
  --red-faint: rgba(211, 48, 47, 0.05);
  --red-glow: rgba(211, 48, 47, 0.25);
  /* out for the season: a step past --red, the OFS badge's own crimson */
  --crimson: #94121e;
  --gold: #8f6408;
  --rd-off: #00a6e0; --rd-def: #a026c4;
  --gold-soft: rgba(212, 175, 55, 0.16);
  --gold-border: rgba(212, 175, 55, 0.8);
  --ring-neutral: rgba(93, 106, 136, 0.18);
  --radius: 8px;
  /* The caret every list-opening control wears: the selects and the date
     button, so they match by construction rather than by eye. A data URI
     cannot read a custom property or currentColor, so --text-dim is
     written out, once per theme; change one, change both. */
  --caret: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath fill='%235d6a88' d='M0 0h10L5 6z'/%3E%3C/svg%3E");
}

:root[data-theme="dark"] {
  color-scheme: dark;
  --bg: #0a0f1f;
  --bg-panel: #111a2f;
  --bg-hover: #182340;
  --bg-hover-2: #1f2c4d;
  --bg-panel-0: rgba(17, 26, 47, 0);
  --border: #243052;
  --border-strong: #35466e;
  --row-border: rgba(36, 48, 82, 0.55);
  --zebra: rgba(255, 255, 255, 0.03);
  --zebra-solid: #151f37;
  --nav-bg: rgba(10, 15, 31, 0.86);
  --overlay: rgba(2, 5, 14, 0.66);
  --edge-shade: rgba(0, 0, 0, 0.45);
  --shadow-pop: 0 12px 34px rgba(0, 0, 0, 0.55);
  --shadow-card: 0 18px 50px rgba(0, 0, 0, 0.45);
  --shadow-btn: 0 1px 2px rgba(0, 0, 0, 0.35);
  --text: #e4e9f5;
  --text-dim: #8b97b8;
  --pop-10: #3ddc84; --pop-9: #6ea0ff; --pop-8: #e4c25a; --pop-7: #5d6a88;
  --pop-ink: #0a0f1f;
  --role-cold: 0.28;
  /* the fire's red, lifted: the sampled one sinks into the navy panel */
  --role-step-0: #dfbc5e;
  --role-step-1: #cc7f42;
  --role-step-2: #c2402f;
  --on-accent: #ffffff;
  --accent: #4d8bff;
  --accent-strong: #79a7ff;
  --accent-border: rgba(77, 139, 255, 0.5);
  --accent-soft: rgba(77, 139, 255, 0.14);
  --accent-faint: rgba(77, 139, 255, 0.07);
  --accent-glow: rgba(77, 139, 255, 0.3);
  --cyan: #33d1ff;
  --violet: #a985ff;
  --brand-a: #33d1ff; --brand-b: #4d8bff; --brand-c: #a985ff;
  --brand-gradient: linear-gradient(120deg, var(--brand-a) 0%, var(--brand-b) 50%, var(--brand-c) 100%);
  --blue: #4cc3ff;
  --blue-border: rgba(76, 195, 255, 0.4);
  --blue-soft: rgba(76, 195, 255, 0.12);
  --blue-faint: rgba(77, 139, 255, 0.06);
  --purple: #b48cff;
  --purple-border: rgba(180, 140, 255, 0.4);
  --pink: #ff7ab6;
  --pink-border: rgba(255, 122, 182, 0.4);
  --green: #3ddc84;
  --green-border: rgba(61, 220, 132, 0.45);
  --green-soft: rgba(61, 220, 132, 0.12);
  --orange: #f0b64f;
  --orange-border: rgba(240, 182, 79, 0.45);
  --orange-soft: rgba(240, 182, 79, 0.08);
  --red: #ff6b6b;
  --red-border: rgba(255, 107, 107, 0.5);
  --red-soft: rgba(255, 107, 107, 0.10);
  --red-faint: rgba(255, 107, 107, 0.06);
  --red-glow: rgba(255, 107, 107, 0.3);
  --crimson: #f43f5e;
  --gold: #e4c25a;
  --rd-off: #22c9ff; --rd-def: #d46bff;
  --gold-soft: rgba(212, 175, 55, 0.12);
  --gold-border: rgba(212, 175, 55, 0.65);
  --ring-neutral: rgba(139, 151, 184, 0.2);
  --caret: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath fill='%238b97b8' d='M0 0h10L5 6z'/%3E%3C/svg%3E");
}

* { box-sizing: border-box; margin: 0; padding: 0; }
*::-webkit-scrollbar { width: 10px; height: 10px; }
*::-webkit-scrollbar-track { background: transparent; }
*::-webkit-scrollbar-thumb {
  background: var(--border); border-radius: 999px;
  border: 2px solid transparent; background-clip: padding-box;
}
*::-webkit-scrollbar-thumb:hover { background: var(--text-dim); background-clip: padding-box; }
* { scrollbar-width: thin; scrollbar-color: var(--border) transparent; }

body {
  font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
  background: var(--bg);
  color: var(--text);
  font-size: 14px;
  line-height: 1.45;
}

a { color: var(--blue); text-decoration: none; }
a:hover { text-decoration: underline; }

/* Everything that opens a list wears one frame: every select in the app
   and the sales log's date button. They stand side by side in the same
   strip, so the frame is one rule rather than two that drift — as they
   had, the button 1.84px taller than the select beside it. line-height
   was why: a select's is `normal` from the UA, and a button's `font:
   inherit` takes the body's 1.45. Naming it settles both. The caret is
   one background image for the same reason — Chrome draws a menulist its
   own arrow in the platform's shape, which matched neither, so
   appearance:none takes that off and --caret goes where it was.
   The contexts below vary size and ground only; each keeps room on the
   right for the caret, and must set background-COLOR, since the
   shorthand would wipe the caret out. */
select, .date-btn {
  appearance: none; -webkit-appearance: none;
  padding: 5px 26px 5px 10px; border-radius: 6px;
  border: 1px solid var(--border); background-color: var(--bg-panel);
  background-image: var(--caret); background-repeat: no-repeat;
  background-position: right 9px center;
  color: var(--text); text-align: left;
  font-family: inherit; font-size: 13px; line-height: 18px;
  cursor: pointer; outline: none;
}
select:hover:not(:disabled), .date-btn:hover:not(:disabled) { border-color: var(--text-dim); }
select:focus, .date-btn:focus-visible { border-color: var(--accent); }
select:disabled, .date-btn:disabled { color: var(--text-dim); cursor: default; opacity: 0.7; }

/* ---- top nav ------------------------------------------------------ */

/* Environment banner. Added by auth.js on anything that is not production,
   which since 2026-09-15 means staging against its own database — the same
   image and the same pages, so nothing else on screen tells them apart.
   Deliberately not themed: it should read as a warning in light and dark
   alike, and stay obvious in a screenshot pasted somewhere else. */
#env-banner {
  position: fixed; top: 0; left: 0; right: 0; z-index: 60;
  height: 26px; line-height: 26px;
  background: #b45309; color: #fff;
  font-size: 12px; font-weight: 600; letter-spacing: 1px;
  text-align: center; user-select: none;
}
body.has-env-banner { padding-top: 26px; }
body.has-env-banner #topnav { top: 26px; }

#topnav {
  position: sticky; top: 0; z-index: 50;
  display: flex; align-items: center; gap: 6px;
  padding: 10px 18px;
  background: var(--nav-bg);
  backdrop-filter: blur(8px);
  border-bottom: 1px solid var(--border);
}
/* The brand's hairline: cyan → blue → violet along the top edge. */
#topnav::before {
  content: ""; position: absolute; left: 0; right: 0; top: 0; height: 2px;
  background: var(--brand-gradient);
}

/* The brand marks come from the final asset family (assets/brand/):
   the one-line lockup in the top bar, the stacked lockup on the sign-in
   pages and the gate, the square symbol alone at phone widths. Light
   and dark surfaces get their own cut of the same artwork — identical
   geometry, only "ard" and the TM change from navy to white. */
.brand { display: flex; align-items: center; height: 32px; margin-right: 10px; }
.brand:hover { text-decoration: none; }
.brand-mark { display: none; width: 30px; height: 30px; }
.brand-line {
  display: block; height: 32px; aspect-ratio: 1506 / 405; flex: none;
  background: url(/icon/line.webp) center / contain no-repeat;
}
:root[data-theme="dark"] .brand-line { background-image: url(/icon/line-dark.webp); }
.brand-lockup {
  display: block; width: 180px; max-width: 100%; aspect-ratio: 1373 / 778;
  background: url(/icon/lockup.webp) center / contain no-repeat;
}
:root[data-theme="dark"] .brand-lockup { background-image: url(/icon/lockup-dark.webp); }
.topnav-links { display: flex; align-items: center; gap: 2px; }

.topnav-btn {
  padding: 5px 12px;
  border-radius: 999px;
  color: var(--text-dim);
  font-size: 13px; font-weight: 500;
}
.topnav-btn:hover { background: var(--accent-soft); color: var(--text); text-decoration: none; }
.topnav-btn.active { background: var(--accent-soft); color: var(--accent); }
/* The admin's item (the ticket queue) wears a warm tint so it reads as
   a different kind of place from the catalog sections. */
.topnav-btn.admin-only { background: var(--orange-soft); color: var(--orange); border: 1px solid var(--orange-border); padding: 4px 11px; }
.topnav-btn.admin-only:hover, .topnav-btn.admin-only.active { background: var(--orange-border); color: var(--text); }

/* ---- omnibox ------------------------------------------------------ */

#omnibox { position: relative; margin-left: auto; width: min(380px, 40vw); }

#omni-input {
  width: 100%; padding: 7px 12px;
  border-radius: 6px; border: 1px solid var(--border);
  background: var(--bg-panel); color: var(--text);
  font-size: 13px; outline: none; font-family: inherit;
  transition: border-color 0.1s, box-shadow 0.1s;
}
#omni-input:focus { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-glow); }

#omni-results {
  position: absolute; top: calc(100% + 6px); right: 0;
  width: min(560px, 92vw); max-height: 70vh; overflow-y: auto;
  background: var(--bg-panel); border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-pop);
}
#omni-results.hidden { display: none; }

.omni-section {
  padding: 6px 0 2px;
  border-bottom: 1px solid var(--border);
}
.omni-section:last-child { border-bottom: none; }
.omni-section-title {
  padding: 4px 14px; font-size: 11px; font-weight: 600;
  text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-dim);
}
.omni-item {
  display: flex; align-items: baseline; gap: 8px;
  padding: 6px 14px; cursor: pointer; color: var(--text);
}
.omni-item:hover, .omni-item.focused { background: var(--bg-hover); text-decoration: none; }
.omni-item .primary { font-weight: 500; }
.omni-item .context { font-size: 12px; color: var(--text-dim); overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; }
.omni-item .count { margin-left: auto; font-size: 11px; color: var(--text-dim); white-space: nowrap; }
/* Card hits: two lines, nothing wraps. */
.omni-item.two-line { flex-direction: column; align-items: stretch; gap: 2px; padding: 7px 14px; }
.omni-item.two-line .line { display: flex; align-items: center; gap: 6px; min-width: 0; }
.omni-item.two-line .primary { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.omni-item.two-line .badge { flex: none; }
.omni-empty { padding: 12px 14px; font-size: 13px; color: var(--text-dim); }

/* ---- layout ------------------------------------------------------- */

#view { max-width: 1080px; margin: 0 auto; padding: 22px 18px 80px; }

.loading { padding: 40px; text-align: center; color: var(--text-dim); }
.error-box {
  margin: 24px 0; padding: 14px 16px;
  border: 1px solid var(--red); border-radius: var(--radius);
  color: var(--red); background: var(--red-soft);
}

.breadcrumb { font-size: 13px; color: var(--text-dim); margin-bottom: 14px; }
.breadcrumb a { color: var(--text-dim); }
.breadcrumb a:hover { color: var(--blue); }
.breadcrumb .sep { margin: 0 6px; }
.breadcrumb .here { color: var(--text); }
/* Last crumb on a parallel page: the parallel's name, and the menu of
   the seed's other parallels. Borrows .menu-dropdown / .menu-item from
   the profile menu; only the trigger and the open rule are its own. */
.crumb-menu { position: relative; display: inline-block; }
.crumb-btn {
  font: inherit; color: var(--text); background: none; border: 0; padding: 0;
  cursor: pointer; display: inline-flex; align-items: center; gap: 4px;
}
.crumb-btn:hover, .crumb-menu.open .crumb-btn { color: var(--blue); }
.crumb-caret { font-size: 10px; color: var(--text-dim); }
.crumb-menu.open .crumb-caret { color: var(--blue); }
/* .breadcrumb-qualified so these beat the .menu-dropdown defaults the
   profile menu sets further down (and its narrow mobile width). */
.breadcrumb .crumb-list {
  left: 0; right: auto; top: calc(100% + 6px); width: max-content;
  min-width: 180px; max-width: min(320px, 70vw); max-height: 60vh; overflow-y: auto;
}
.crumb-menu.open .crumb-list { display: block; }
.crumb-list .menu-item.cur { color: var(--text-dim); background: var(--bg-hover); }

.page-title { font-size: 22px; font-weight: 700; margin-bottom: 4px; }
.page-sub { color: var(--text-dim); font-size: 13px; margin-bottom: 18px; }
.page-sub a { color: var(--text-dim); text-decoration: underline; }
.page-sub a:hover { color: var(--blue); }

.section-title {
  font-size: 15px; font-weight: 600; margin: 26px 0 10px;
  display: flex; align-items: baseline; gap: 8px;
}
.section-title .muted { font-size: 12px; font-weight: 400; color: var(--text-dim); }

/* ---- home --------------------------------------------------------- */

/* The page is read at a glance: a face or a card for who, one large
   figure for how much, a bar for how it compares, and the rest in the
   row's tooltip. Type is kept to names and numbers.

   Narrow (the base rules): search, heat, then the market's four tiles,
   stacked, scrolling. Wide (the media query below): one screen at full
   width, as the card page is: search and heat on the left, the market
   on the right as a grid of unequal tiles, the pair exactly as tall as
   the window allows (app.js fitHome sets the height). Nothing scrolls
   but a tile with more rows than it has room for. */
body.page-home #view {
  max-width: none; padding-bottom: 20px;
  background:
    radial-gradient(42% 46% at 4% 0%, var(--accent-faint), transparent 72%),
    radial-gradient(30% 36% at 100% 100%, var(--accent-faint), transparent 70%);
}

.homepage { display: flex; flex-direction: column; gap: 18px; min-width: 0; }
.hm-left { display: flex; flex-direction: column; gap: 18px; min-width: 0; }
.hm-hero { padding: 14px 0 0; }

.hm-hero h1 { font-size: 34px; line-height: 1.05; font-weight: 800; letter-spacing: -0.02em; margin-bottom: 8px; }
.hm-brand {
  background: var(--brand-gradient); -webkit-background-clip: text; background-clip: text;
  color: transparent;
}
.hm-lede { font-size: 15px; color: var(--text-dim); margin-bottom: 18px; }

/* The page's one action. Bigger than the bar's field, same search. */
.hm-search { position: relative; max-width: 620px; }
.hm-search-icon { position: absolute; left: 16px; top: 50%; transform: translateY(-50%); color: var(--text-dim); pointer-events: none; }
.hm-search input {
  width: 100%; padding: 15px 18px 15px 46px;
  border-radius: 14px; border: 1px solid var(--border-strong);
  background: var(--bg-panel); color: var(--text);
  font-size: 16px; outline: none; font-family: inherit;
  box-shadow: var(--shadow-card);
  transition: border-color 0.15s, box-shadow 0.15s;
}
.hm-search input:focus { border-color: var(--accent); box-shadow: 0 0 0 4px var(--accent-glow); }
.hm-results {
  position: absolute; top: calc(100% + 6px); left: 0; right: 0; z-index: 20;
  max-height: 60vh; overflow-y: auto;
  background: var(--bg-panel); border: 1px solid var(--border);
  border-radius: var(--radius); box-shadow: var(--shadow-pop);
}
.hm-results.hidden { display: none; }

.hm-ways { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-top: 12px; font-size: 12px; color: var(--text-dim); }
.hm-way {
  font: inherit; font-size: 12px; font-weight: 500; padding: 4px 11px; cursor: pointer;
  border-radius: 999px; border: 1px solid var(--border);
  background: var(--bg-panel); color: var(--text);
  max-width: 100%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.hm-way:hover { border-color: var(--accent-border); color: var(--accent); text-decoration: none; }

/* A tile: heat on the left, each of the market's four on the right. */
.hm-heat, .hm-panel {
  display: flex; flex-direction: column; min-width: 0; min-height: 0; padding: 16px;
  border: 1px solid var(--border); border-radius: 16px;
  background: var(--bg-panel); box-shadow: var(--shadow-card);
  transition: border-color 0.15s, box-shadow 0.15s;
}
.hm-heat:hover, .hm-panel:hover { border-color: var(--accent-border); }
.hm-panel-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 12px; }
.hm-panel-head h2 {
  font-size: 12px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.08em; color: var(--text-dim);
}
.hm-rows { display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; overflow: hidden auto; padding: 0 8px; margin: 0 -8px; }
.hm-row {
  display: flex; align-items: center; gap: 10px; min-width: 0; padding: 6px 8px; margin: 0 -8px;
  border-radius: 10px; color: var(--text);
}
a.hm-row:hover { background: var(--bg-hover); text-decoration: none; }
.hm-row-name { flex: 1 1 auto; min-width: 0; font-size: 13px; font-weight: 600; color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* A headline is a sentence: two lines of it, then cut. */
.hm-row-name.wrap { white-space: normal; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; font-weight: 500; line-height: 1.3; }
.hm-row-side { display: flex; flex-direction: column; align-items: flex-end; gap: 4px; flex: none; }
.hm-ago { flex: none; font-size: 11px; color: var(--text-dim); }
.hm-none { padding: 14px 0; font-size: 13px; color: var(--text-dim); }

/* A player's face, round, wherever one stands for who. */
.hm-face {
  flex: none; width: 32px; height: 32px; border-radius: 50%; object-fit: cover; object-position: 50% 12%;
  background: var(--bg-hover-2); border: 1px solid var(--border);
}
.hm-face.none { display: inline-block; }


/* The one large figure a tile leads with. */
.hm-hero-fig { font-size: clamp(26px, 2.3vw, 44px); line-height: 1; font-weight: 800; letter-spacing: -0.02em; color: var(--text); }
.hm-feature-name { font-size: 14px; font-weight: 600; color: var(--text); }
/* On anything that is not real yet, so it is never read as if it were. */
.hm-sample {
  font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em;
  padding: 1px 6px; border-radius: 4px;
  color: var(--orange); background: var(--orange-soft); border: 1px solid var(--orange-border);
}

/* Did you know: the card as the picture, the fact as the one thing to
   read, the reactions under it, and the way on to the card. */
.hm-fact-body { display: flex; flex-direction: column; gap: 14px; flex: 1 1 auto; min-height: 0; }
.hm-fact-pic {
  flex: none; display: flex; align-items: center; justify-content: center; padding: 12px; border-radius: 14px;
  background: linear-gradient(135deg, var(--accent-soft), transparent 70%);
  border: 1px solid var(--accent-border);
}
.hm-fact-pic:hover { box-shadow: 0 4px 18px var(--accent-glow); }
/* A card nobody has photographed is an empty frame of the same shape,
   so the tile keeps its layout either way. */
.hm-feature-card {
  flex: none; aspect-ratio: 5 / 7; width: clamp(72px, 15vh, 140px); background: var(--bg-hover);
  display: flex; align-items: center; justify-content: center; text-align: center;
  color: var(--text-dim); border-radius: 8px; font-size: 10px; box-shadow: var(--shadow-card);
}
.hm-feature-card.empty { border: 1px dashed var(--border-strong); }
.hm-feature-card img { width: 100%; height: 100%; object-fit: cover; display: block; }
.hm-feature-card.landscape { aspect-ratio: 7 / 5; width: clamp(100px, 21vh, 196px); }
.hm-feature-card.landscape img {
  position: absolute; left: 50%; top: 50%;
  width: 71.4286%; height: 140%;
  transform: translate(-50%, -50%) rotate(-90deg);
}
.hm-fact-main { display: flex; flex-direction: column; justify-content: center; gap: 14px; flex: 1 1 0; min-width: 0; min-height: 0; }
.hm-fact-text {
  font-size: clamp(17px, 1.45vw, 26px); line-height: 1.3; font-weight: 700; letter-spacing: -0.01em;
  color: var(--text); overflow: hidden; flex: 0 1 auto; min-height: 0;
  /* No line clamp: the page sizes the font down until the whole sentence
     fits (fitFactText in app.js). */
}
.hm-react { display: flex; flex-wrap: wrap; gap: 6px; }
.hm-rx {
  display: inline-flex; align-items: center; gap: 5px; height: 30px; padding: 0 10px; cursor: pointer;
  font: inherit; font-size: 12px; font-weight: 700; color: var(--text-dim);
  border: 1px solid var(--border); border-radius: 999px; background: var(--bg-hover);
  transition: transform 0.12s, border-color 0.12s, background 0.12s;
}
.hm-rx span { font-size: 15px; line-height: 1; }
.hm-rx b { font-variant-numeric: tabular-nums; }
.hm-rx.zero { padding: 0 8px; }
.hm-rx.zero b { display: none; }
.hm-rx:hover:not(:disabled) { transform: translateY(-2px) scale(1.06); border-color: var(--accent-border); background: var(--bg-panel); }
.hm-rx.on { color: var(--accent); border-color: var(--accent); background: var(--accent-soft); }
.hm-rx:disabled { cursor: default; }
.hm-rx:disabled.zero { opacity: 0.55; }
.hm-fact-go {
  display: flex; align-items: center; gap: 10px; min-width: 0; padding: 8px 12px 8px 8px; align-self: flex-start;
  max-width: 100%; border-radius: 999px; border: 1px solid var(--border); color: var(--text);
  transition: border-color 0.12s, background 0.12s;
}
.hm-fact-go:hover { text-decoration: none; border-color: var(--accent); background: var(--accent-soft); }
.hm-fact-go > span:not(.hm-fact-arrow) { display: flex; flex-direction: column; min-width: 0; }
.hm-fact-go b, .hm-fact-go i { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.hm-fact-go b { font-size: 13px; font-weight: 700; }
.hm-fact-go i { font-style: normal; font-size: 11px; color: var(--text-dim); }
.hm-fact-arrow { flex: none; font-size: 16px; font-weight: 700; color: var(--accent); transition: transform 0.12s; }
.hm-fact-go:hover .hm-fact-arrow { transform: translateX(3px); }

/* Drops: a clock for the next one, calendar chips for the rest, and a
   release on sale as its boxes: name, shape, price, whether it can be
   bought. */
.hm-onsale { margin-top: 2px; border-top: 1px solid var(--border); }
.hm-onsale:first-child { margin-top: 0; border-top: 0; }
.hm-onsale .hm-row-name { font-weight: 700; }
.hm-shop {
  flex: none; font-size: 11px; font-weight: 700; padding: 2px 9px; border-radius: 999px;
  color: var(--accent); background: var(--accent-soft); border: 1px solid var(--accent-border);
}
.hm-shop:hover { background: var(--accent-border); color: var(--text); text-decoration: none; }
.hm-box { padding-left: 14px; }
.hm-box .hm-row-name { flex: 0 1 auto; font-weight: 500; }
.hm-box .info-tip { flex: none; margin-right: auto; }
.hm-box.out .hm-row-name, .hm-box.out .hm-box-price { color: var(--text-dim); }
.hm-box-price { flex: none; font-size: 13px; font-weight: 700; font-variant-numeric: tabular-nums; }
.hm-stock {
  flex: none; min-width: 62px; text-align: center; font-size: 10px; font-weight: 700;
  padding: 1px 6px; border-radius: 4px; border: 1px solid var(--border); color: var(--text-dim);
}
.hm-stock.in { color: var(--green); border-color: var(--green-border); }
.hm-stock.pre { color: var(--orange); border-color: var(--orange-border); }
.hm-more { padding-left: 14px; }
.hm-drop-next { display: flex; flex-direction: column; align-items: center; gap: 8px; margin-bottom: 12px; text-align: center; }
.hm-clock { display: flex; align-items: flex-start; gap: 6px; }
.hm-clock-cell { display: flex; flex-direction: column; align-items: center; box-sizing: border-box; flex: none; width: clamp(52px, 4.4vw, 66px); padding: 8px 6px 6px; border-radius: 10px; background: var(--bg-hover); border: 1px solid var(--border); }
.hm-clock-cell b { font-size: clamp(22px, 2vw, 34px); line-height: 1; font-weight: 800; font-variant-numeric: tabular-nums; color: var(--text); }
.hm-clock-cell i { font-style: normal; font-size: 9px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.08em; color: var(--text-dim); margin-top: 4px; }
.hm-clock-sep { font-size: 22px; font-weight: 800; color: var(--text-dim); padding-top: 8px; }
.hm-date {
  flex: none; display: flex; flex-direction: column; align-items: center; width: 36px; overflow: hidden;
  border-radius: 8px; border: 1px solid var(--border); background: var(--bg-hover);
}
.hm-date b { width: 100%; text-align: center; font-size: 9px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; padding: 2px 0; color: var(--text-dim); background: var(--bg-hover-2); }
.hm-date i { font-style: normal; font-size: 14px; font-weight: 700; line-height: 1.5; color: var(--text); }
.hm-date.soon { border-color: var(--accent-border); }
.hm-date.soon b { color: var(--on-accent); background: var(--accent); }

/* The collection: its worth, which way it moved, and the week's shape. */
.hm-mine-body { display: flex; flex-direction: column; gap: 12px; flex: 1 1 auto; min-height: 0; }
.hm-mine-main { display: flex; flex-direction: column; gap: 10px; min-width: 0; min-height: 0; }
.hm-mine-value { display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px 12px; }
.hm-mine-count { font-size: 12px; color: var(--text-dim); }
.hm-delta { flex: none; font-size: 12px; font-weight: 700; padding: 2px 8px; border-radius: 999px; }
.hm-delta.up { color: var(--green); background: var(--green-soft); }
.hm-delta.down { color: var(--red); background: var(--red-soft); }
/* The week's shape: an accent line over a fade of the same hue. */
.hm-spark { position: relative; display: block; flex: 1 1 auto; min-height: 56px; max-height: 150px; }
.hm-spark svg { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; }
.hm-spark polyline { fill: none; stroke: var(--accent); stroke-width: 2.5; stroke-linejoin: round; stroke-linecap: round; vector-effect: non-scaling-stroke; }
.hm-spark-top { stop-color: var(--accent); stop-opacity: 0.28; }
.hm-spark-foot { stop-color: var(--accent); stop-opacity: 0; }
.hm-spark i {
  position: absolute; width: 10px; height: 10px; margin: -5px 0 0 -5px; border-radius: 50%;
  background: var(--accent); border: 2px solid var(--bg-panel); box-shadow: 0 0 0 3px var(--accent-glow);
}

/* Heat: one ranked list at a time, the tabs pick which. */
.hm-tabs { display: inline-flex; gap: 2px; padding: 2px; border-radius: 999px; background: var(--bg-hover); }
.hm-tab {
  font: inherit; font-size: 12px; font-weight: 600; padding: 4px 12px; cursor: pointer;
  border: 0; border-radius: 999px; background: none; color: var(--text-dim);
}
.hm-tab:hover { color: var(--text); }
.hm-tab.active { background: var(--bg-panel); color: var(--accent); box-shadow: var(--shadow-btn); }
.hm-board { display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; overflow: hidden auto; padding: 0 8px; margin: 0 -8px; }
.hm-board[hidden] { display: none; }
.hm-hot {
  display: flex; align-items: center; gap: 10px; min-width: 0; padding: 6px 8px; margin: 0 -8px;
  border-radius: 10px; color: var(--text);
}
.hm-hot:hover { background: var(--bg-hover); text-decoration: none; }
.hm-rank { flex: none; width: 16px; text-align: center; font-size: 13px; font-weight: 800; color: var(--text-dim); }
.hm-hot:first-child .hm-rank { color: var(--accent); }
.hm-hot-name { flex: 1 1 auto; min-width: 0; font-size: 13px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.hm-hot:first-child .hm-hot-name { font-size: 15px; }
.hm-hot:first-child .hm-face { width: 40px; height: 40px; border-color: var(--accent-border); }
/* The row's figure (members over the window) and its direction. */
.hm-hot-n { flex: none; min-width: 2ch; text-align: right; font-size: 18px; font-weight: 800; font-variant-numeric: tabular-nums; }
.hm-hot:first-child .hm-hot-n { font-size: 22px; color: var(--accent); }
.hm-trend { flex: none; width: 14px; text-align: center; font-size: 10px; line-height: 1; }
.hm-trend.up { color: var(--green); }
.hm-trend.down { color: var(--red); }
.hm-trend.flat { color: var(--text-dim); font-size: 13px; }

.hm-market { display: grid; grid-template-columns: 1fr; gap: 14px; min-width: 0; }
@media (min-width: 640px) {
  .hm-market { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (min-width: 1024px) {
  .homepage {
    display: grid; grid-template-columns: minmax(360px, 5fr) minmax(0, 8fr);
    gap: 24px; align-items: stretch;
  }
  .hm-left { min-height: 0; }
  .hm-hero { padding-top: 2vh; flex: none; }
  .hm-hero h1 { font-size: clamp(34px, 3.4vw, 60px); }
  /* Heat takes the height the search leaves. */
  .hm-heat { flex: 1 1 auto; }
  /* Unequal tiles: the fact and the collection are the wide ones, each
     with its picture or figure beside the rest; the two lists are the
     narrow ones. */
  .hm-market {
    grid-template-columns: repeat(12, minmax(0, 1fr));
    grid-template-rows: repeat(2, minmax(0, 1fr)); min-height: 0;
  }
  .hm-fact { grid-column: span 7; }
  .hm-drops { grid-column: span 5; }
  .hm-news { grid-column: span 5; }
  .hm-mine { grid-column: span 7; }
  .hm-fact-body, .hm-mine-body { flex-direction: row; gap: 18px; }
  /* The card fills its side of the tile: as wide as the tile's height
     allows, the tile being half the window less the bar and the gaps,
     and the card 5:7. */
  .hm-fact-pic { flex: 0 0 38%; }
  .hm-feature-card { width: clamp(52px, calc(35.7vh - 110px), 230px); }
  .hm-feature-card.landscape { width: clamp(90px, 20vw, 300px); max-width: 100%; }
  .hm-mine-body > .hm-rows { flex: 1 1 0; justify-content: space-evenly; }
  .hm-mine-main { flex: 0 0 52%; }
}

/* A laptop's height: the same screen, drawn tighter. */
@media (min-width: 1024px) and (max-height: 820px) {
  .hm-hero { padding-top: 0; }
  .hm-hero h1 { font-size: clamp(30px, 2.8vw, 44px); margin-bottom: 4px; }
  .hm-lede { margin-bottom: 12px; font-size: 14px; }
  .hm-search input { padding-top: 11px; padding-bottom: 11px; }
  .hm-ways { margin-top: 8px; }
  .hm-left { gap: 12px; }
  .hm-heat, .hm-panel { padding: 12px; }
  .hm-panel-head { margin-bottom: 8px; }
  .hm-row, .hm-hot { padding-top: 4px; padding-bottom: 4px; }
  .hm-face { width: 26px; height: 26px; }
  .hm-hot:first-child .hm-face { width: 32px; height: 32px; }
  .hm-fact-main { gap: 10px; }
  .hm-fact-pic { padding: 8px; }
  .hm-drop-next { margin-bottom: 8px; gap: 4px; }
  .hm-clock-cell { padding: 5px 4px 4px; min-width: 42px; }
  .hm-date i { line-height: 1.3; }
  .hm-spark { min-height: 40px; }
}

/* The tiles arrive, once, for a reader who has not asked for stillness. */
@media (prefers-reduced-motion: no-preference) {
  @keyframes hm-rise { from { opacity: 0; transform: translateY(8px); } }
  .hm-hero, .hm-heat, .hm-panel { animation: hm-rise 0.4s ease-out both; }
  .hm-heat { animation-delay: 0.06s; }
  .hm-panel:nth-child(1) { animation-delay: 0.1s; }
  .hm-panel:nth-child(2) { animation-delay: 0.16s; }
  .hm-panel:nth-child(3) { animation-delay: 0.22s; }
  .hm-panel:nth-child(4) { animation-delay: 0.28s; }
}

.stat-row {
  display: flex; flex-wrap: wrap; gap: 10px; justify-content: flex-start;
  margin: 10px 0 8px;
}
.stat-row.left { justify-content: flex-start; }
.stat-tile {
  background: var(--bg-panel); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 12px 20px; min-width: 118px;
  text-align: center; cursor: pointer; color: var(--text);
}
.stat-tile:hover { border-color: var(--accent-border); text-decoration: none; }
.stat-tile .num {
  font-size: 20px; font-weight: 700;
  background: var(--brand-gradient); -webkit-background-clip: text; background-clip: text;
  color: transparent;
}
.stat-tile .lbl { font-size: 12px; color: var(--text-dim); }
.stat-tile.static { cursor: default; }
.stat-tile.static:hover { border-color: var(--border); }
.stat-tile.warn .num { background: none; color: var(--orange); }
.stat-tile .tile-note { font-size: 12px; font-weight: 500; color: var(--text-dim); }

.scope-pill {
  padding: 1px 10px; border-radius: 999px; font-weight: 600;
  color: var(--accent); background: var(--accent-soft);
  border: 1px solid var(--accent-border);
}

/* ---- PSA linkage indicators --------------------------------------- */

.badge.warn { color: var(--orange); border-color: var(--orange-border); }
#sales-list .pager-top { display: none; }
/* a sale the price readings set aside: admins see the row tinted */
body.is-admin tr.far-off td { background: var(--red-faint); }
.badge.dna { color: var(--purple); border-color: var(--purple-border); }
.badge.psa-match { color: var(--green); border-color: var(--green-border); }
.warn-txt { color: var(--orange); }
th.ctr, td.ctr { text-align: center; }
/* CSS-drawn checkbox (native disabled inputs render gray on Windows). */
.cb {
  position: relative; display: inline-block;
  width: 15px; height: 15px; vertical-align: middle;
  border: 1.5px solid var(--border); border-radius: 4px;
}
.cb.on { border-color: var(--accent-border); background: var(--accent-soft); }
.cb.on::after {
  content: ''; position: absolute; left: 4px; top: 1px;
  width: 4px; height: 8px;
  border: solid var(--accent); border-width: 0 2px 2px 0;
  transform: rotate(45deg);
}

/* Coverage bar (share of seeds with a pop attached). */
.cov { display: flex; align-items: center; gap: 7px; min-width: 108px; }
.cov-bar {
  flex: 0 0 68px; height: 6px; border-radius: 3px; overflow: hidden;
  background: var(--bg); border: 1px solid var(--border);
}
.cov-fill { height: 100%; background: var(--green); }
.cov-fill.mid { background: var(--orange); }
.cov-fill.low { background: var(--red); }
.cov-txt { font-size: 11px; color: var(--text-dim); font-variant-numeric: tabular-nums; }

.chip.psa-set { border-color: var(--accent-border); }

/* Deep link to psacard.com's per-spec page (/spec/psa/<id>). */
.spec-link { font-size: 11px; margin-left: 5px; white-space: nowrap; }

/* Filter chips (design page seed + spec lists): buttons restyled as chips. */
#d-seed-filter .chip, #d-spec-filter .chip { font-family: inherit; }
#d-seed-filter .chip.active, #d-spec-filter .chip.active {
  color: var(--accent); border-color: var(--accent-border);
  background: var(--accent-soft);
}

.psa-hint {
  font-size: 13px; color: var(--text); margin: 4px 0 10px;
  padding: 8px 12px; border-left: 3px solid var(--orange);
  background: var(--orange-soft);
}
.psa-hint.dim { color: var(--text-dim); border-left-color: var(--border);
  background: var(--bg-panel); }

.chip-grid { display: flex; flex-wrap: wrap; gap: 8px; }
.chip {
  display: inline-flex; align-items: baseline; gap: 6px;
  padding: 5px 12px; border-radius: 999px;
  background: var(--bg-panel); border: 1px solid var(--border);
  font-size: 13px; color: var(--text); cursor: pointer;
}
.chip:hover { border-color: var(--accent-border); text-decoration: none; }
.chip .n { font-size: 11px; color: var(--text-dim); }

.card-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); gap: 12px; }
.entity-card {
  background: var(--bg-panel); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 14px 16px; color: var(--text);
  display: block;
}
.entity-card:hover { border-color: var(--accent-border); text-decoration: none; }
.entity-card .name { font-weight: 600; margin-bottom: 2px; }
.entity-card .meta { font-size: 12px; color: var(--text-dim); }

/* ---- tables ------------------------------------------------------- */

.tbl-wrap {
  border: 1px solid var(--border); border-radius: var(--radius);
  overflow-x: auto; background: var(--bg-panel);
}
table { border-collapse: collapse; width: 100%; }
th {
  text-align: left; font-size: 11px; font-weight: 600;
  text-transform: uppercase; letter-spacing: 0.05em;
  color: var(--text-dim); padding: 8px 12px;
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
}
td { padding: 7px 12px; border-bottom: 1px solid var(--row-border); vertical-align: top; }
tr:last-child td { border-bottom: none; }
/* Zebra striping; rowlink hover below outranks it (0,2,2 vs 0,1,3). */
tbody tr:nth-child(even) td { background: var(--zebra); }
tr.rowlink { cursor: pointer; }
tr.rowlink:hover td { background: var(--bg-hover); }

/* Products whose release date has not arrived. Their checklist is published
   but the odds matrix is not, so they legitimately have no print runs. Greyed
   rather than badged: a badge per row buries the table in noise, while the
   whole row reading faint says "nothing to see here yet" at a glance. Restored
   to full contrast on hover so the row is still readable when reached for. */
tr.unreleased td { opacity: 0.45; font-style: italic; }
tr.unreleased:hover td { opacity: 1; font-style: normal; }

/* ---- standardized table layout ------------------------------------
   Column conventions (applied per table via .t-std):
   - every column hugs its content; only .flex columns (the long text —
     product name, design name, card title) take the leftover width;
   - related columns form GROUPS opened by a .grp hairline;
   - the PSA group is additionally tinted via .psa-c. The tint is a
     background-image so it stacks on the zebra background-color; these
     rules sit AFTER the zebra rule on purpose (same specificity — the
     later background-image wins), while the rowlink hover above still
     outranks both and flattens the row to the hover color. */
table.t-std th, table.t-std td { white-space: nowrap; width: 1%; }
table.t-std th.flex, table.t-std td.flex { width: auto; white-space: normal; }
th.grp, td.grp { border-left: 1px solid var(--border); }
thead tr th.psa-c, tbody tr td.psa-c {
  background-image: linear-gradient(var(--blue-faint), var(--blue-faint));
}

/* Column-header info icon: hover, or press, for its tooltip
   (tooltip.js). */
.info-tip {
  display: inline-block; width: 13px; height: 13px; line-height: 11px;
  border: 1px solid var(--text-dim); border-radius: 50%;
  color: var(--text-dim); text-align: center;
  font-size: 9px; font-weight: 700;
  text-transform: none; letter-spacing: 0;
  cursor: help; vertical-align: 1px;
}
.info-tip:hover { color: var(--text); border-color: var(--text); }
/* A finger cannot hit a 13px circle. The circle stays 13px so no row
   changes height; ::after grows what the press has to land on, taking no
   space of its own. It stops short of a whole finger's width on purpose:
   any bigger and it starts swallowing presses meant for what sits beside
   it, and it never grows past the table row it is in. */
@media (pointer: coarse) {
  .info-tip { position: relative; }
  .info-tip::after { content: ''; position: absolute; inset: -9px -7px; }
}
/* Single-number columns are centered so they read as aligned units. */
td.num, th.num { text-align: center; font-variant-numeric: tabular-nums; }
/* A sortable header always shows both arrows; the one in use is lit, and
   an arrow click sorts that way at once (sortTh in app.js). */
th.sortable { cursor: pointer; user-select: none; }
th.sortable:hover { color: var(--accent); }
.sort-head { display: inline-flex; align-items: center; gap: 4px; }
.sort-arrows { display: inline-flex; flex-direction: column; line-height: 1; }
.sort-arrow {
  background: none; border: 0; padding: 0 2px; margin: 0; cursor: pointer;
  font: inherit; font-size: 8px; line-height: 9px; color: var(--text-dim); opacity: .45;
}
.sort-arrow:hover { opacity: 1; color: var(--accent); }
.sort-arrow.on { opacity: 1; color: var(--accent); }
td select {
  background-color: var(--bg); max-width: 320px;
  padding: 4px 22px 4px 8px; font-size: 12px; line-height: 16px;
  background-position: right 7px center;
}
.mini-btn {
  background: var(--accent-soft); color: var(--accent);
  border: 1px solid var(--accent-border); border-radius: 6px;
  padding: 4px 12px; font-size: 12px; cursor: pointer;
}
.mini-btn:hover { background: var(--accent-border); color: var(--text); }
/* Icon-only row actions: a 28px square, the glyph flex-centred; the
   accent for a link out, red for a report. The title is the caption. */
.row-icon {
  display: inline-flex; align-items: center; justify-content: center;
  width: 28px; height: 28px; border-radius: 6px; border: 1px solid transparent;
  background: none; color: var(--blue); cursor: pointer; padding: 0; font-family: inherit;
  text-decoration: none; vertical-align: middle;
}
.row-icon:hover { background: var(--blue-soft); border-color: var(--blue-border); }
.row-icon.danger { color: var(--red); }
.row-icon.danger:hover { background: var(--red-soft); border-color: var(--red-border); }
th.ctr, td.ctr { text-align: center; }
.mini-btn:disabled { opacity: 0.5; cursor: default; }
.mini-btn:disabled:hover { background: var(--accent-soft); color: var(--accent); }
.dim { color: var(--text-dim); }
.nowrap { white-space: nowrap; }

/* Print runs. No Topps document states a serial run, so nearly every
   figure is an estimate from the pack odds ('.est', ≈N); '.ceil' marks an
   estimate that is only an upper bound (≤N). A plain /N is a run a human
   stated. */
.est { color: var(--text-dim); font-style: italic; }
.est.ceil { border-bottom: 1px dotted var(--text-dim); }
/* Provenance icon beside a derived run: opens the odds PDF it came from.
   Dim until hovered so a column of derived runs does not become a column
   of arrows. */
.src-link {
  display: inline-flex; align-items: center; vertical-align: -2px; margin-left: 5px;
  color: var(--blue); text-decoration: none;
}
.src-link svg { width: 13px; height: 13px; }
.src-link:hover { text-decoration: none; filter: brightness(1.15); }
.page-sub a.src-link { color: var(--blue); text-decoration: none; }

/* The box-types popup holds an eight-column table: wider than a dialog. */
.overlay-box.wide { width: min(1100px, 100%); }
/* A form of two card slots and one field: 760px leaves it stranded in
   its own dialog. */
.overlay-box.narrow { width: min(460px, 100%); }
.overlay-box.wide .overlay-body { overflow-x: auto; }

/* The Boxes popup: one section per format, its packs in a table. */
.fmt { border: 1px solid var(--border); border-radius: 8px; padding: 10px 12px; margin: 0 0 12px; }
.fmt-h { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-bottom: 6px; }
.fmt-name { font-weight: 600; }
.fmt-tools { margin-left: auto; display: inline-flex; gap: 6px; }
td .fmt-tools { margin-left: 4px; gap: 2px; }
td .fmt-tools .row-icon { width: 22px; height: 22px; }
.fmt-notes { margin: 4px 0; font-size: 13px; color: var(--text-dim); white-space: pre-line; }
.fmt-src { font-size: 12px; color: var(--text-dim); margin: 4px 0; overflow-wrap: anywhere; }
.fmt-prod { margin: 0 0 12px; font-size: 13px; }
.fmt-prod summary { cursor: pointer; color: var(--text-dim); }
.fmt-actions { display: flex; gap: 8px; margin-top: 8px; }
.fmt-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 8px 12px; }
.fmt-field { display: flex; flex-direction: column; gap: 3px; font-size: 12px; color: var(--text-dim); }
.fmt-field.wide { margin-top: 8px; }
.fmt-field .run-input { width: 100%; height: auto; min-height: 1.9em; text-align: left; font-size: 13px; color: var(--text); }
.fmt-field textarea.run-input { resize: vertical; line-height: 1.4; padding: 4px 6px; }
.fmt-checks { display: flex; flex-wrap: wrap; gap: 4px 14px; }
.fmt-check { font-size: 13px; color: var(--text); display: inline-flex; gap: 5px; align-items: center; }
.fmt-form-row td { background: var(--accent-soft); }
.fmt-err { color: var(--red); font-size: 13px; margin-top: 8px; }
.fmt-err:empty { display: none; }
/* The team page's roles editor. */
.roles-tbl select.run-input { width: auto; text-align: left; }
.roles-tbl .role-note { width: 100%; text-align: left; }
/* One line, sized to the number it replaces: a box count is at most five
   digits, and `font: inherit` would otherwise drag the row's line-height in
   and leave the box taller than the text it sits in. The configuration
   panel's inputs reuse it. */
.run-input {
  box-sizing: border-box;
  width: 6em;
  height: 1.6em;
  padding: 0 4px;
  line-height: 1.6em;
  text-align: right;
  font-family: inherit;
  font-size: inherit;
  color: var(--text);
  background: var(--bg-hover);
  border: 1px solid var(--accent-border);
  border-radius: 3px;
  outline: none;
  appearance: none;
}
.run-input:focus { border-color: var(--accent); }
.run-input.bad { border-color: var(--orange); }

/* ---- badges / pills ---------------------------------------------- */

.badge {
  display: inline-block; padding: 1px 7px; border-radius: 999px;
  font-size: 11px; font-weight: 600; line-height: 1.5;
  border: 1px solid var(--border); color: var(--text-dim);
}
.badge.rc { color: var(--accent); border-color: var(--accent-border); background: var(--accent-soft); }
.badge.kind-base { color: var(--blue); border-color: var(--blue-border); }
.badge.kind-insert { color: var(--purple); border-color: var(--purple-border); }
.badge.kind-autograph { color: var(--orange); border-color: var(--orange-border); }
.badge.kind-memorabilia { color: var(--pink); border-color: var(--pink-border); }
.badge.kind-other { color: var(--text-dim); }
.badge.inactive { color: var(--red); border-color: var(--red-border); }
.badge.active-yes { color: var(--green); border-color: var(--green-border); }
.badge.base { color: var(--blue); border-color: var(--blue-border); }
/* 'derived' membership = cross-product rows under the producer's own
   disclaimer, not source-materialized — shown dimmed. */
.badge.derived { color: var(--text-dim); border-style: dashed; opacity: 0.75; }

/* Box-type chips on product pages (plain <span>, not a link). */
.chip.box-chip { cursor: default; }
.chip.box-chip:hover { border-color: var(--border); }

/* ---- filter bar --------------------------------------------------- */

.filter-bar {
  display: flex; flex-wrap: wrap; gap: 10px; align-items: center;
  margin-bottom: 16px;
}
.filter-bar input[type="text"] {
  padding: 6px 10px; border-radius: 6px;
  border: 1px solid var(--border); background: var(--bg-panel);
  color: var(--text); font-size: 13px; font-family: inherit; outline: none;
}
.filter-bar select { padding: 6px 26px 6px 10px; }
.filter-bar input:focus { border-color: var(--accent); }
/* Search boxes carry a placeholder long enough to name what they match,
   so they get a floor width instead of shrinking to the flex minimum. */
.filter-bar input.f-search { min-width: 260px; }
.filter-bar label.check {
  display: inline-flex; gap: 6px; align-items: center;
  font-size: 13px; color: var(--text-dim); cursor: pointer;
}
.filter-bar .spacer { margin-left: auto; }
.filter-bar .result-count { font-size: 12px; color: var(--text-dim); }

/* ---- detail panels ------------------------------------------------ */

.panel {
  background: var(--bg-panel); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 16px 18px; margin-bottom: 16px;
}

.kv-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
  gap: 10px 22px;
}
.kv .k { font-size: 11px; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-dim); }
.kv .v { font-size: 14px; }
/* Wide cells: facts that need more room than one grid track. */
.kv-grid .kv.span2 { grid-column: span 2; }
.kv-grid .kv.span3 { grid-column: span 3; }

.summary-block {
  border-left: 3px solid var(--accent-border);
  padding: 8px 12px; margin: 14px 0;
  background: var(--accent-faint);
  font-size: 13px; white-space: pre-wrap;
}

code.src {
  display: block; padding: 8px 10px; margin-top: 4px;
  background: var(--bg); border: 1px solid var(--border); border-radius: 6px;
  font-size: 12px; color: var(--text-dim);
  font-family: ui-monospace, "Cascadia Code", Consolas, monospace;
  white-space: pre-wrap; word-break: break-all;
}


/* ---- player bits -------------------------------------------------- */

.headshot {
  width: 130px; height: 95px; object-fit: cover;
  border-radius: var(--radius); border: 1px solid var(--border);
  background: var(--bg);
}
.headshot.mini {
  width: 36px; height: 27px; border-radius: 4px; vertical-align: middle;
}

.season-group { margin-bottom: 10px; }
.season-group > summary {
  cursor: pointer; padding: 8px 12px;
  background: var(--bg-panel); border: 1px solid var(--border);
  border-radius: var(--radius); font-weight: 600; font-size: 14px;
  display: flex; align-items: baseline; gap: 10px;
}
.season-group > summary .muted { font-weight: 400; font-size: 12px; color: var(--text-dim); }
.season-group[open] > summary { border-bottom-left-radius: 0; border-bottom-right-radius: 0; }
.season-body {
  border: 1px solid var(--border); border-top: none;
  border-bottom-left-radius: var(--radius); border-bottom-right-radius: var(--radius);
  padding: 10px 14px 14px;
}
.product-block { margin-top: 10px; }
.product-block .pb-title { font-size: 13px; font-weight: 600; margin-bottom: 6px; }
.product-block .pb-title a { color: var(--text); }
.product-block .pb-title a:hover { color: var(--blue); }

.linked-chips { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 6px; }
.linked-chips .chip img { align-self: center; }


/* ---- overlay ------------------------------------------------------ */

.eye-btn {
  background: none; border: none; cursor: pointer;
  color: var(--text-dim); padding: 0 2px; vertical-align: middle;
}
.eye-btn:hover { color: var(--accent); }
.eye-btn svg { width: 16px; height: 16px; display: block; }

/* While an overlay is up the page behind does not scroll. */
body.overlay-open { overflow: hidden; }
.overlay {
  position: fixed; inset: 0; z-index: 100;
  background: var(--overlay);
  display: flex; align-items: flex-start; justify-content: center;
  padding: 60px 16px 40px;
}
.overlay-box {
  background: var(--bg-panel); border: 1px solid var(--border);
  border-radius: var(--radius);
  width: min(760px, 100%); max-height: calc(100vh - 100px);
  display: flex; flex-direction: column;
  box-shadow: var(--shadow-pop);
}
.overlay-head {
  display: flex; align-items: center; gap: 10px;
  padding: 12px 16px; border-bottom: 1px solid var(--border);
}
.overlay-title { font-weight: 600; font-size: 15px; flex: 1; }
.overlay-close {
  background: none; border: none; cursor: pointer;
  color: var(--text-dim); font-size: 20px; line-height: 1; padding: 2px 6px;
}
.overlay-close:hover { color: var(--text); }
.overlay-sub { padding: 8px 16px 0; font-size: 12px; color: var(--text-dim); }
.overlay-body { padding: 12px 16px 16px; overflow-y: auto; }
.overlay-body .tbl-wrap { background: var(--bg); }

/* Odds matrix: indented member-subset rows under their parallel. */
.odds-sub td { background: var(--zebra); }
.odds-sub .sub-label { padding-left: 26px; color: var(--text-dim); }

/* ---- unit card page (#/card/<id>) --------------------------------- */

.card-layout { display: flex; gap: 16px; align-items: stretch; margin-bottom: 4px; }
.card-facts { flex: 1; min-width: 0; }

.card-images { display: flex; gap: 10px; flex-shrink: 0; }
.img-ph {
  width: 132px; aspect-ratio: 63 / 88;         /* physical card ratio */
  border: 1px dashed var(--border); border-radius: var(--radius);
  background: var(--bg-panel);
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 4px; font-size: 12px; color: var(--text-dim); text-align: center;
}
@media (max-width: 720px) {
  .card-layout { flex-direction: column; }
  .card-images { justify-content: center; }
}

.mkt-empty { padding: 28px 10px; text-align: center; color: var(--text-dim); font-size: 13px; }

/* ---- the card page (one card in one parallel) ---------------------- */
/* Narrow (the base rules): the card, who it is, the hero card, then the
   two tabs, stacked, scrolling. Wide (further down): the same blocks in
   two columns sized to the window, so the page never scrolls — the
   sales table is paged to whatever rows are left (app.js fitCardPage).
   The page is full-width there: two columns of market data have more to
   say than the 1080px reading column the browse pages use. */
body.page-card #view { max-width: none; padding-bottom: 20px; }

.cardpage { display: flex; flex-direction: column; gap: 18px; min-width: 0; }
.cp-left, .cp-right { display: flex; flex-direction: column; min-width: 0; gap: 12px; }
.cp-image { display: flex; justify-content: center; min-height: 0; }
.cp-image #c3d-stage { min-width: 0; }
/* The placeholder wears the card's shape, whichever it is, so the page
   does not change size when a photograph arrives. */
.cp-image.landscape .cp-noimg { aspect-ratio: 7 / 5; }
.cp-noimg {
  width: min(240px, 60vw); aspect-ratio: 5 / 7; display: flex;
  flex-direction: column; align-items: center; justify-content: center;
  gap: 8px; text-align: center; padding: 0 14px;
  border: 1px dashed var(--border); border-radius: var(--radius);
  background: var(--bg-panel); color: var(--text-dim); font-size: 12px;
}
/* Under the card: the name centred with its tools on the right of it,
   where the subjects play now, then the badges. The tools sit outside
   the centring so the name is centred on the column, not on name + tools. */
.cp-id { display: flex; flex-direction: column; align-items: center; gap: 6px; text-align: center; }
.cid-name { position: relative; max-width: 100%; padding: 0 56px; }
.cp-id .page-title { font-size: 20px; margin: 0; }
/* Centred on the letters, not on the line box: the name's glyphs sit
   low in its line (caps' middle ~1.5px, lowercase ~3.5px under the
   box's), so centring on the box leaves the icons riding high. */
.cid-tools {
  position: absolute; right: 0; top: 50%; transform: translateY(calc(-50% + 2px));
  display: inline-flex; align-items: center; gap: 4px;
}
.cid-link { display: inline-flex; color: var(--text-dim); }
.cid-link:hover { color: var(--accent); }
/* The badges are pictures (static/art/badges/, 219 x 272), all drawn on
   one canvas so a single height sizes them alike. A card shows at most
   five (--n, set by app.js), and they stay on one line: 84px tall where
   they fit, else whatever height the column's width allows for four, or
   for as many as there are when that is more. */
.cp-id { container-type: inline-size; }
.cid-badges {
  --badge-h: min(84px, calc((100cqi - (max(4, var(--n, 4)) - 1) * 6px)
                            / max(4, var(--n, 4)) * 272 / 219));
  display: flex; flex-wrap: nowrap; justify-content: center; gap: 6px;
}
.cid-badge { position: relative; display: block; height: var(--badge-h); }
.cid-badge img { display: block; height: 100%; width: auto; }
/* The label, lettered like the picture it sits on: its metal shaded
   light to dark, with the same near-black outline, in the picture's own
   units (app.js BADGE_ART). */
.cid-label { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; }
.cid-label text {
  font-family: inherit; font-size: 36px; font-weight: 900; text-transform: uppercase;
  text-anchor: middle; dominant-baseline: central;
  stroke: #0b0b0b; stroke-width: 6px; stroke-linejoin: round; paint-order: stroke;
}
.cid-metals { position: absolute; }
.cid-foot { font-size: 13px; color: var(--text-dim); }
.cid-foot .sep { margin: 0 6px; }

/* About this card: the rating framework's readings, by axis. A
   <details> so the phone layout can fold it away. */
.cp-hero {
  border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--bg-panel); padding: 10px 14px;
}
.cp-hero > summary {
  cursor: pointer; list-style: none; font-size: 12px; font-weight: 600;
  text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-dim);
  display: flex; align-items: center; gap: 6px;
}
.cp-hero > summary::-webkit-details-marker { display: none; }
.cp-hero > summary::before {
  content: "\25B8"; font-size: 10px; transition: transform .12s ease-out;
}
.cp-hero[open] > summary::before { transform: rotate(90deg); }
.cp-hero > summary:hover { color: var(--text); }
.cp-about { margin-top: 12px; display: flex; flex-direction: column; gap: 16px; }
.ab-axis-t {
  margin: 0 0 4px; font-size: 11px; font-weight: 600; text-transform: uppercase;
  letter-spacing: 0.06em; color: var(--accent);
}
.ab-list { margin: 0; display: flex; flex-direction: column; }
/* A reading is a row: what it is on the left, what it reads on the
   right, drawn where a drawing says more than the word. Words wear
   text ink; colour only ever repeats what a label already says. */
.ab-row {
  display: grid; grid-template-columns: minmax(96px, 3fr) minmax(0, 8fr); gap: 6px 14px;
  align-items: center; padding: 10px 0; border-top: 1px solid var(--row-border);
}
.ab-row:first-child { border-top: 0; }
.ab-row dt { margin: 0; font-size: 12px; color: var(--text-dim); }
.ab-row dt .cid-link { vertical-align: -2px; margin-left: 2px; }
.ab-row dd {
  margin: 0; min-width: 0; display: flex; flex-wrap: wrap; align-items: baseline;
  justify-content: space-between; gap: 4px 10px; font-size: 14px;
}
.ab-val { font-weight: 600; font-variant-numeric: tabular-nums; }
.ab-rank { font-size: 12px; color: var(--text-dim); white-space: nowrap; }
/* A print run's rank, in brackets beside the copies rather than pushed
   to the far edge. */
.ab-rank-in { font-size: 12px; font-weight: 400; color: var(--text-dim); white-space: nowrap; }

/* Segments: every option the factor can take, the card's own lit and
   the rest dimmed, so the reading is seen against what it could have
   been. 2px of surface between fills. */
.ab-seg { display: flex; gap: 2px; width: 100%; }
.ab-opt {
  flex: 1 1 0; min-width: 0; text-align: center; padding: 5px 4px; font-size: 11px;
  font-weight: 600; line-height: 1.2; white-space: nowrap; overflow: hidden;
  text-overflow: ellipsis; color: var(--text-dim); background: var(--bg-hover-2);
  opacity: 0.5;
}
.ab-opt:first-child { border-radius: 6px 0 0 6px; }
.ab-opt:last-child { border-radius: 0 6px 6px 0; }
.ab-opt.on { opacity: 1; color: var(--on-accent); }
/* A ladder, low to high: one hue, deepening up the rungs, so where the
   lit rung stands is seen as well as read. */
.ab-scale .ab-opt, .ab-vessel .ab-opt { opacity: 0.75; }
.ab-scale .ab-opt:nth-child(1), .ab-vessel .ab-opt:nth-child(1) { background: color-mix(in srgb, var(--accent) 8%, var(--bg-hover-2)); }
.ab-scale .ab-opt:nth-child(2), .ab-vessel .ab-opt:nth-child(2) { background: color-mix(in srgb, var(--accent) 18%, var(--bg-hover-2)); }
.ab-scale .ab-opt:nth-child(3), .ab-vessel .ab-opt:nth-child(3) { background: color-mix(in srgb, var(--accent) 30%, var(--bg-hover-2)); }
.ab-scale .ab-opt:nth-child(n+4), .ab-vessel .ab-opt:nth-child(n+4) { background: color-mix(in srgb, var(--accent) 44%, var(--bg-hover-2)); }
.ab-scale .ab-opt.on, .ab-vessel .ab-opt.on { opacity: 1; }
.ab-scale .ab-opt:nth-child(1).on, .ab-vessel .ab-opt:nth-child(1).on { background: color-mix(in srgb, var(--accent) 38%, var(--text-dim)); }
.ab-scale .ab-opt:nth-child(2).on, .ab-vessel .ab-opt:nth-child(2).on { background: color-mix(in srgb, var(--accent) 62%, var(--text-dim)); }
.ab-scale .ab-opt:nth-child(3).on, .ab-vessel .ab-opt:nth-child(3).on { background: color-mix(in srgb, var(--accent) 84%, var(--text-dim)); }
.ab-scale .ab-opt:nth-child(n+4).on, .ab-vessel .ab-opt:nth-child(n+4).on { background: var(--accent); }
/* A vocabulary with no order: the lit one in the accent, no ramp. */
.ab-set .ab-opt.on { background: var(--accent); }
/* Two short readings on one line (card year, licensed): the first sits
   on the rows' own two columns, so its value lines up with the drawings
   under it, and the second takes the far part of the value column, its
   name then its value. The line keeps the rows' rule above it. */
.ab-duo {
  display: grid; grid-template-columns: minmax(96px, 3fr) minmax(0, 3fr) minmax(0, 5fr);
  gap: 6px 14px; align-items: center; padding: 10px 0;
  border-top: 1px solid var(--row-border);
}
.ab-duo:first-child { border-top: 0; }
.ab-duo > .ab-row:first-child { display: contents; }
.ab-duo > .ab-row + .ab-row {
  display: flex; align-items: center; gap: 10px; padding: 0; border-top: 0;
}
.ab-duo > .ab-row dd { justify-content: flex-start; }
/* Yes or no as the one answer: a tick in green or a cross in red, in a
   disc of its own colour the height of the row's text. */
.ab-check { display: inline-flex; align-self: center; }
.ab-check svg {
  width: 15px; height: 15px; flex: none; border-radius: 50%; padding: 1px;
  fill: none; stroke: #fff; stroke-width: 2.4; stroke-linecap: round; stroke-linejoin: round;
}
.ab-check.yes svg { background: var(--green); }
.ab-check.no svg { background: var(--red); }
/* An ordered ladder as a breadcrumb: chevrons low to high, each pointing
   on to the next. Only the reading's own rung is lit: a premium product
   did not pass through core to get there, so the rungs below it stay
   as plain as the rungs above. The rungs overlap by all but a sliver of their points,
   so 2px of surface shows along each diagonal. */
.ab-steps { display: flex; width: 100%; }
.ab-step {
  --pt: 10px;
  flex: 1 1 0; min-width: 0; margin-left: calc(2px - var(--pt));
  padding: 6px 4px 6px calc(var(--pt) + 4px); text-align: center; font-size: 11px;
  font-weight: 600; line-height: 1.2; white-space: nowrap; overflow: hidden;
  text-overflow: ellipsis; color: var(--text-dim); background: var(--bg-hover-2);
  clip-path: polygon(0 0, calc(100% - var(--pt)) 0, 100% 50%, calc(100% - var(--pt)) 100%,
                     0 100%, var(--pt) 50%);
}
.ab-step:first-child {
  margin-left: 0; padding-left: 8px; border-radius: 6px 0 0 6px;
  clip-path: polygon(0 0, calc(100% - var(--pt)) 0, 100% 50%, calc(100% - var(--pt)) 100%, 0 100%);
}
.ab-step.on { background: var(--accent); color: var(--on-accent); }

/* The sentence under a drawn reading, where the class is not the whole
   answer: the path a depth chart leaves, the minutes that moved. */
.ab-note { margin-top: 4px; font-size: 11px; line-height: 1.35; color: var(--text-dim); }

/* The honours on file: a trophy each, wrapping where the column ends,
   with the count under it where it is more than one: a pill in one flat
   colour, its tier's (the art's mid tone), never over the art. Every
   trophy keeps the pill's room, counted or not, so the lines stay one
   height. A reading from before the tiers keeps its chip. */
.ab-tally { grid-column: 1 / -1; display: flex; flex-wrap: wrap; gap: 4px 12px; margin-top: 4px; }
.ab-trophy { position: relative; display: inline-block; line-height: 0; padding-bottom: 20px; }
.ab-trophy > img { height: 84px; width: auto; }
.ab-trophy > b {
  position: absolute; left: 50%; bottom: 0; transform: translateX(-50%);
  line-height: 1; font-size: 11px; font-weight: 800; font-variant-numeric: tabular-nums;
  padding: 2px 7px; border-radius: 999px; color: #2a1606; background: #e0a93b;
}
.ab-trophy.t-legacy > b { background: #6a3fb5; color: #f8f1df; }
.ab-trophy.t-silver > b { background: #b8bfca; color: #1f2733; }
.ab-trophy.t-bronze > b { background: #c9814f; color: #2a1606; }
.ab-chip {
  display: inline-flex; align-items: center; gap: 5px; font-size: 11px; font-weight: 600;
  padding: 3px 8px; border-radius: 999px; color: var(--text);
  background: var(--accent-soft); border: 1px solid var(--accent-border);
}
.ab-chip > b { font-variant-numeric: tabular-nums; color: var(--accent); }

/* A ranked number: the bar is where it stands in its pool, the caption
   names the pool. The number is on the row above, in text ink. */
.ab-gauge { flex-basis: 100%; min-width: 0; }
.ab-meter {
  height: 4px; border-radius: 2px; background: var(--bg-hover-2); overflow: hidden;
  margin-top: 5px;
}
.ab-meter > span { display: block; height: 100%; border-radius: 2px; background: var(--accent); }
.ab-cap { font-size: 11px; color: var(--text-dim); margin-top: 2px; }

/* The PSA population: one bar cut by grade, each piece as wide as its
   share; a count sits in its piece when it fits (.tight hides it, and
   the piece's tooltip still says it). Above the bar, the total graded
   on the left and the legend on the right. */
.pop-top {
  display: flex; justify-content: space-between; align-items: center; gap: 8px;
  font-size: 11px; line-height: 15px; color: var(--text-dim);
  font-variant-numeric: tabular-nums;
}
.pop-total { white-space: nowrap; }
.pop-bar {
  display: flex; height: 18px; margin-top: 3px; border-radius: 3px; overflow: hidden;
  gap: 1px; background: var(--bg-panel);
}
.pop-seg {
  flex-basis: 0; min-width: 3px; display: flex; align-items: center; justify-content: center;
  color: var(--pop-ink); font-size: 11px; line-height: 1; cursor: default; outline-offset: -2px;
}
.pop-seg b { font-weight: 600; font-variant-numeric: tabular-nums; white-space: nowrap; }
.pop-seg.tight b { visibility: hidden; }
.g10 { background: var(--pop-10); } .g9 { background: var(--pop-9); }
.g8 { background: var(--pop-8); } .g7 { background: var(--pop-7); }
.pop-key { display: flex; align-items: center; gap: 3px; white-space: nowrap; }
.pop-sw { width: 8px; height: 8px; border-radius: 2px; display: inline-block; margin-left: 6px; }
.pop-sw:first-child { margin-left: 0; }


/* The Player Equity radar: six readings of one season as one shape.
   Every spoke is a percentile, so the rings are the same five steps on
   all six and the shape can be read across them. One line says what is
   being ranked and off how many games; the readings themselves are the
   vertices' tooltips and the svg's own label, so nothing under the
   drawing repeats them. */
/* The axis's two drawings stand side by side and split the width: one
   is what a season's rates came to, the other is what the player is
   now. Stacked, the second sat most of a screen below the first.

   The columns STRETCH to a common height and each drawing takes the
   middle of its own (`margin: auto`), so the two titles sit on one line
   and the two drawings sit on one centre whatever their shapes. On a
   phone they stack, radar first, because side by side in half of a
   narrow screen leaves both too small to read. */
.ab-pair {
  display: flex; align-items: stretch; gap: 0 20px; padding: 2px 0 0;
}
/* Not an even split. The radar is square, so every pixel of width it
   is given it also wants in height; the role fireball is a narrow
   column and any width past its own is gutter. So the fireball takes
   exactly what it needs and the radar takes the rest. */
.ab-pair > * { display: flex; flex-direction: column; }
.ab-pair > .ab-radar { flex: 1 1 0; min-width: 0; }
.ab-pair > .ab-role { flex: 0 0 auto; }
.ab-pair > * + * { border-left: 1px solid var(--border); padding-left: 20px; }
/* 4px, not the role's 6: the radar's title stands alone in its head and
   the line under it (.rd-played) is the first thing in the drawing
   below, so this gap is the one the role's head has between its title
   and its own line (.inj-row), and the two lines start level. */
.ab-radar { gap: 4px; }
.ab-radar > .rd-head { min-height: 0; }
/* A season's drawings: its regular season, its playoff run, or the two
   side by side, splitting the radar's column evenly. Each is a column
   of its own (what it stands on, the shape, the strip a vertex writes
   into), so a pair reads as two of the same thing. */
.rd-shapes { flex: 1 1 auto; display: flex; align-items: stretch; gap: 0 16px; }
.rd-one { flex: 1 1 0; min-width: 0; display: flex; flex-direction: column; gap: 6px; }
/* Which of the season's two a drawing is, said only where the season
   alone does not say it: a run, or either of a pair. Lettered as the
   title above it and centred over its shape. */
.rd-cap {
  font-size: 10.5px; font-weight: 600; text-transform: uppercase;
  letter-spacing: 0.06em; color: var(--text-dim); line-height: 1.3;
  text-align: center;
}
/* Both heads reserve the same height whether or not they carry a season
   control, so the two titles start on one line. */
.rd-head {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 4px 10px; flex-wrap: wrap; min-height: 26px;
}
/* A title, in the family of the axis titles above it and a step below
   them: the same case and tracking, the dim ink rather than the accent,
   so it reads as naming a drawing and not as naming a section.

   The radar's season control sits INSIDE its title, between the two
   halves of the sentence, so the control IS the season rather than a
   second mention of one standing beside it. Laid out as a flex row, so
   the control centres against the words instead of hanging off their
   baseline. */
.ab-drawn-t {
  margin: 0; font-size: 10.5px; font-weight: 600; text-transform: uppercase;
  letter-spacing: 0.06em; color: var(--text-dim); line-height: 1.3;
  display: flex; align-items: center; gap: 7px; flex-wrap: wrap;
}
/* A link in a title (the role drawing's depth chart) is drawn to the
   title's capitals rather than at the 14px the rows use: at 14px its
   frame stood taller than the 10.5px caps and hung below their baseline.
   At 12px the frame is the caps' height, and it is set ON the baseline
   rather than centred on the line: centring put it on the middle of the
   line box, which sits below the middle of capitals (the line keeps room
   for descenders they never use). The flex row puts the svg's BOX on the
   baseline, and the box has 2.5 of its 16 units empty under the frame,
   so it is moved down by that much and the frame's own foot stands on
   the baseline. The negative top margin keeps the link from standing
   taller than the words, so the title's line is the words' own and it
   starts level with the radar's title beside it. The radar's title
   keeps its centring: its season control is a box, and a box centres. */
.ab-drawn-t .cid-link { margin-top: -3px; }
.ab-drawn-t .cid-link svg { width: 12px; height: 12px; transform: translateY(1.875px); }
.ab-role .ab-drawn-t { align-items: baseline; }
/* Both titles stand in a line of the season control's height (19.6px),
   whether or not the radar has a control to show: its words are centred
   on that line, and the role's, which sit on a baseline instead, are
   set down by the same 3px. Without it the role's title stood 3px
   above the radar's wherever a player has more than one season, and
   the line under each title started 6px apart. */
.rd-head .ab-drawn-t { min-height: 20px; }
.ab-role .ab-drawn-t { padding-top: 3px; box-sizing: border-box; }
/* A lone season is the same value the control would hold (.rd-pick). */
.rd-season { color: var(--text); font-variant-numeric: tabular-nums; }
/* What a shape stands on, a line of its own above it: the games and
   the minutes a game. Sentence case in the dim ink, so it reads as a
   note on the title and not as a second one. */
.rd-played {
  margin: 0; font-size: 11px; line-height: 15px;
  color: var(--text-dim); font-variant-numeric: tabular-nums;
}
.rd-played:empty { display: none; }
/* The games and the minutes are the line's point: lettered as a
   reading's value (.ab-val), the words around them left dim. */
.rd-fig { color: var(--text); font-weight: 600; }
/* The season control is part of the title's SENTENCE, so it is lettered
   as the sentence — the same size, weight, case and tracking — and
   kept as a CONTROL by its border and its caret (`--caret` is already
   the dim ink in both themes). Taking the letterforms from the heading
   is what stops it reading as a form field dropped into one; the border
   is what stops it reading as a word that happens to be darker. Its ink
   is the full one, not the heading's: the season is the value the
   games and minutes under it (.rd-fig) come off, and it reads as a
   value the way they do.
   What it does not keep from the base `select` is that rule's padding,
   which ate the space either side of it in a line this tight. */
.ab-drawn-t .rd-pick {
  font-family: inherit; font-size: inherit; font-weight: inherit;
  letter-spacing: inherit; text-transform: inherit; line-height: inherit;
  color: var(--text); font-variant-numeric: tabular-nums;
  background-color: transparent;
  border: 1px solid var(--border); border-radius: 4px;
  padding: 2px 15px 2px 6px;
  background-position: right 5px center; background-size: 7px auto;
}
.ab-drawn-t .rd-pick:hover:not(:disabled) {
  border-color: var(--border-strong);
  background-color: var(--bg-hover);
}
.ab-drawn-t .rd-pick:focus-visible {
  border-color: var(--accent);
  outline: 2px solid var(--accent-glow); outline-offset: 0;
}
/* The strip under a drawing, EMPTY at rest. It is what a radar vertex
   writes itself into while it is pointed at, tapped or focused: a
   native tooltip never fires on a touch, so without somewhere on the
   page to put the sentence a finger cannot read a spoke at all. It
   holds its line of height either way, so nothing moves when a sentence
   arrives. Only the radar has one: the fireball writes nothing there,
   and an empty strip under it was only more space below than above. */
.rd-t {
  font-size: 11px; line-height: 15px; min-height: 15px; color: var(--text-dim);
  font-variant-numeric: tabular-nums; text-align: center;
}
.rd-pick { font-size: 12px; padding: 2px 22px 2px 8px; line-height: 16px; }
/* One number sizes the pair, on .ab-pair, so the two cannot drift
   apart: the fireball's box is a share of it and sets the row's height,
   and the radar's box follows its own width up to the scale that same
   number allows. Each centres its shape in its column (`meet`); the
   radar's column also holds its caption strip, so its shape sits a few
   pixels above the fireball's middle, which two shapes this different
   do not show. */
/* The radar's fill weight (`.rd-area`). */
.ab-pair { --ab-draw-h: 360px; --ab-fill: 0.3; }
.rd-svg, .role-svg {
  display: block; width: 100%; height: var(--ab-draw-h);
  margin: auto; overflow: visible;
}
/* The radar's box follows its own width, at its frame's proportions
   (292 x 212), so a narrow column no longer puts a small shape in a tall
   empty box. Its height stops where its scale reaches what a 292-square
   frame had in the full height (--ab-draw-h / 292), the size the
   fireball's factor below is matched to. */
.rd-svg {
  height: auto; aspect-ratio: 292 / 212;
  max-height: calc(var(--ab-draw-h) * 212 / 292);
}
/* The fireball's box is a share of the same height. The radar's frame
   reserves room around its rings for six labels, so its shape fills
   about seven tenths of its box; the fireball's fills nearly all of it.
   The factor is what brings the two DRAWINGS to a height, and it is the
   only place the difference is stated. */
.role-svg { height: calc(var(--ab-draw-h) * 0.8); width: auto; }
.rd-ring { fill: none; stroke: var(--border); stroke-width: 1; vector-effect: non-scaling-stroke; }
.rd-ring:last-of-type { stroke: var(--border-strong); }
.rd-spoke { stroke: var(--border); stroke-width: 1; vector-effect: non-scaling-stroke; }
.rd-lbl { font-size: 9px; fill: var(--text-dim); }
/* The shape is painted with a gradient down its own height, because
   RADAR's order puts the three offensive spokes across the top half and
   the three defensive ones across the bottom. The fill and the stroke
   take the same paint, so the edge shifts with the area rather than
   ringing it in one flat colour.

   The two hold flat over most of their own half and change across a
   band around the middle, rather than blending the whole way down. Run
   as an even blend they met in the middle of the palette and the shape
   read as one colour that happened to be lighter at the top; the split
   only says anything if it is visible AS a split. The band is about a
   fifth of the height — narrow enough to read as a change and wide
   enough not to read as a seam drawn across the shape. They are tokens of
   their own because the radar's halves are not a semantic the palette
   already carries, and the outcome hues (green, orange, red) are
   spoken for — the momentum arrow beside this uses two of them.

   It carries NO reading. Every spoke is a percentile in the same pool
   and they are all the same kind of number; the gradient only makes the
   two halves easier to see, and each spoke still names itself. */
.rd-g0 { stop-color: var(--rd-off); }
.rd-g1 { stop-color: var(--rd-def); }
.rd-area {
  fill-opacity: var(--ab-fill); stroke-width: 1.75; stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
}
.rd-dot { stroke: var(--bg); stroke-width: 1.5; }
/* A vertex is the only place its figures are written, so what the press
   has to land on is `rd-hit` and not the 3.5-unit dot: transparent, no
   space of its own, and stopping well short of the next spoke. The
   whole vertex takes focus, so a tab key reaches the same sentence. */
.rd-hit { fill: transparent; }
.rd-pt { outline: none; }
.rd-pt:hover .rd-dot, .rd-pt:focus-visible .rd-dot { r: 5.5; }
.rd-pt:focus-visible .rd-dot { stroke: var(--accent); }
.rd-lbl { cursor: help; }

/* The role fireball: three role readings as one drawing (app.js,
   aboutRole), with the ladder of levels on its left. How far the fire
   is lit, from the tip of its trail up, is the rotation status, and the
   arrow between two of the ladder's names is which way the minutes are
   moving; the pecking order has no mark of its own and reaches the
   drawing only at the top level.

   One picture drawn twice. `role-cold` is the whole of it, grey and
   faint; `role-lit` is the same picture in colour, masked to the part
   that is lit. Their colour is SVG filters set in app.js (aboutRole),
   since WebKit ignores a CSS filter on an element inside an svg; their
   opacity is here. The cold copy sits a little further back on the
   light theme (`--role-cold`). */
.ab-role { gap: 6px; }
.role-cold { opacity: var(--role-cold); }

/* What the subject is listed as hurt with: a line of small pictures
   under the drawing's title, where the radar beside it carries its
   subtitle (.rd-played), and about that line's height: 16px against
   its 15, the smallest the symbols still read at. Left-aligned as a
   subtitle is. Each picture is a tile
   whose shape and colour are the level and a white symbol for the part
   of the body (static/art/injuries); the injury is in its tooltip.

   Beside them on the same line, the season the contract runs through
   (.role-signed), lettered as the radar's subtitle is.

   The line takes the head's full width without ASKING for any: the
   role column is as wide as its content (.ab-pair > .ab-role), and a
   wrapping row's content width is its items laid end to end, so a line
   that asked for its own width put the words beside the title and
   widened the column by them, which squeezed the radar until its
   subtitle wrapped and the two drawings no longer started level. Zero
   width with a minimum of the whole line asks for nothing and still
   fills it. */
.inj-row {
  flex: 0 0 auto; width: 0; min-width: 100%;
  display: flex; flex-wrap: wrap; justify-content: flex-start;
  align-items: center; gap: 2px 8px; line-height: 0;
}
.inj-list { display: flex; flex-wrap: wrap; gap: 4px; }
.role-signed {
  font-size: 11px; line-height: 15px; color: var(--text-dim);
  font-variant-numeric: tabular-nums;
}
.inj-badge { width: 16px; height: 16px; cursor: help; border-radius: 3px; }
.inj-badge:focus-visible { outline: 2px solid var(--accent-glow); outline-offset: 1px; }

/* Nothing here animates. The drawing is a still: a reading is a fact and
   not an event, and there is nothing for motion to say about it that the
   level does not already. That also means no reduced-motion branch to
   keep in step, and every reader gets the same picture. */

/* Which way the minutes are moving, in the directions a reader of this
   app already has: green for rising, red for falling, and neither for
   minutes that are holding. Never the only mark for it — the sentence on
   the drawing carries the numbers that actually moved, and the arrow
   names itself on hover. */
.role-arw { fill: none; stroke-linecap: round; stroke-linejoin: round; }
/* A dashed body and a solid head, carried a step back: solid and full
   weight it read as the subject rather than as a note on the level it
   points away from. */
.role-arw.shaft { stroke-width: 1.6; stroke-dasharray: 2 2; }
.role-arw.head { stroke: none; }
.role-mom { cursor: help; opacity: 0.7; }
.role-mom.up .role-arw { stroke: var(--green); }
.role-mom.up .role-arw.head { fill: var(--green); }
.role-mom.down .role-arw { stroke: var(--red); }
.role-mom.down .role-arw.head { fill: var(--red); }

/* The ladder as a vertical STEPPER: a dot per level on a thin track,
   the track and the dots filled up to the level the fire reached, and
   that level carrying a ring and the only name in full ink.

   Every other name falls almost to the panel, reached or not, so the
   ring and the one bold name are what the eye lands on; the others are
   there to be read on purpose, not at a glance.

   The fill is the fire's own run (`--role-step-*`, sampled from the
   picture at the saturation the page carries it): gold at the foot, red
   at the top, anchored to the track so a given step is always the same
   colour. */
.role-lbl {
  font-size: 10px; text-anchor: end;
  fill: color-mix(in srgb, var(--text-dim) 22%, var(--bg-panel));
}
.role-lbl.on { fill: var(--text); font-weight: 700; }
.role-track {
  fill: none; stroke: var(--text); stroke-opacity: 0.16; stroke-width: 1.5;
}
.role-track.on { stroke-opacity: 1; stroke-width: 2; }
.role-ring { fill: var(--bg-panel); stroke-width: 2; }
.role-dot {
  fill: var(--bg-panel); stroke: var(--text); stroke-opacity: 0.25; stroke-width: 1;
  vector-effect: non-scaling-stroke;
}
.role-s0 { stop-color: var(--role-step-0); }
.role-s1 { stop-color: var(--role-step-1); }
.role-s2 { stop-color: var(--role-step-2); }

/* A drawing too wide for the value column (the honours) goes under
   the row's words, the full width. */
.ab-row.tall { align-items: baseline; }
.ab-empty { font-size: 12px; color: var(--text-dim); padding: 8px 0; }

/* The tab bar carries the grade picker, which scopes both tabs. */
.cp-tabbar {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  border-bottom: 1px solid var(--border);
}
.cp-tabs { display: flex; gap: 2px; }
.cp-tab {
  background: none; border: 0; border-bottom: 2px solid transparent;
  color: var(--text-dim); font: inherit; font-size: 14px; font-weight: 600;
  padding: 8px 12px; margin-bottom: -1px; cursor: pointer;
}
.cp-tab:hover { color: var(--text); }
.cp-tab.active { color: var(--text); border-bottom-color: var(--accent); }
.cp-tabbar .sec-tools { margin-left: auto; padding-bottom: 6px; }
.cp-pane { display: flex; flex-direction: column; gap: 10px; min-width: 0; min-height: 0; }
.cp-pane[hidden] { display: none; }
/* The pane spaces its children with its own gap. A panel brings a
   margin-bottom of its own from the days it stood in normal flow, and
   inside a gapped flex column the two stack: 26px under the price panel
   where 10 was meant, which read as a hole between the chart and the
   strip below it. The gap owns the spacing here. */
.cp-pane > .panel { margin-bottom: 0; }

/* The strip over a table: what narrows it, and what pages it. */
.cp-strip { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.cp-strip[hidden] { display: none; }
.cp-strip-title {
  font-size: 13px; font-weight: 600; text-transform: uppercase;
  letter-spacing: 0.05em; color: var(--text-dim);
}
.cp-strip .muted { font-size: 12px; color: var(--text-dim); }
.cp-strip .sec-tools { margin-left: 0; }
.cp-strip .pager-slot { margin-left: auto; }
.cp-strip .pager { padding: 0; }

@media (min-width: 1024px) {
  /* Two columns, both exactly as tall as the window allows. The height
     itself is set in JS, which is the only place the space left under
     the breadcrumb can actually be measured. */
  .cardpage {
    display: grid; grid-template-columns: minmax(240px, 360px) minmax(0, 1fr);
    gap: 22px; align-items: stretch;
  }
  .cp-left { min-height: 0; }
  .cp-right { min-height: 0; gap: 10px; }
  /* The card takes the height left over; app.js gives it the width that
     keeps it inside the box (--card-w). */
  .cp-image { flex: 1 1 auto; }
  .cp-image #c3d-stage { display: flex; width: 100%; }
  .cp-image .c3d-wrap { flex: 1; min-height: 0; gap: 8px; }
  .cp-image .c3d-scene { flex: 1; min-height: 0; display: flex; align-items: center;
    justify-content: center; padding: 12px 24px; }
  /* The card's width comes from --card-w, which card3d.css reads
     directly (it inherits from .cp-image); nothing to set here. */
  .cp-image .cp-noimg { width: var(--card-w, min(240px, 60vw)); align-self: center; }
  .cp-hero { flex: none; }
  .cp-pane { flex: 1 1 auto; }
  /* Insights is two columns: what we know about the card, then what it
     sells for. The readings scroll inside their own column if they ever
     outgrow it; the price panel is a column whose chart stretches to
     whatever height the reading and the cards leave. */
  #pane-insights:not([hidden]) {
    display: grid; grid-template-columns: minmax(300px, 5fr) minmax(0, 6fr);
    gap: 12px; align-items: stretch;
  }
  #pane-insights .cp-hero { min-height: 0; overflow: auto; }
  /* A column of its own has nothing to fold away for: the summary is a
     title here, and only a phone gets the toggle (app.js fitCardPage
     holds it open). */
  #pane-insights .cp-hero > summary { pointer-events: none; cursor: default; }
  #pane-insights .cp-hero > summary::before { display: none; }
  #pane-insights .pp-panel {
    min-height: 0; display: flex; flex-direction: column;
  }
  #pane-insights .pp-body { flex: 1 1 auto; min-height: 0; }
  #pane-insights .pp-chart { flex: 1 1 auto; min-height: 160px; overflow: hidden; }
  /* The sales pane is the strip and a table exactly as tall as the rows
     chosen for it (app.js fitRows). */
  #pane-sales .cp-strip { flex: none; }
  #pane-sales #sales-list { flex: none; overflow: hidden; }
  #pane-sales .tbl-wrap { overflow-x: auto; }
  /* The comparison is a grid of its own; if a wide one does not fit,
     it scrolls inside its panel and not the page. */
  #pane-comparison .cmp-panel { flex: 1 1 auto; min-height: 0; overflow: auto; }
  /* It fills the pane rather than sitting in the top of it: the rows
     share the height, and every thumbnail grows with them. The version
     in the middle shows its whole card — it is the one being compared,
     so it is never cropped to fit a box. */
  #pane-comparison .cmp { height: 100%; }
  #pane-comparison .cmp-mid { grid-auto-rows: minmax(0, 1fr); }
  #pane-comparison .cmp-rows { flex: 1 1 auto; }
  #pane-comparison .cmp-row { flex: 1 1 auto; min-height: 0; }
  #pane-comparison .cmp-slot { min-height: 0; }
  /* Thumbnails grow with the row, but only to a share of the slot —
     the five attribute lines beside them still have to be readable. */
  #pane-comparison .cmp-img {
    width: 38%; max-width: 110px; height: auto; max-height: 100%;
    aspect-ratio: 5 / 7; align-self: center;
  }
  #pane-comparison .cmp-img.landscape {
    width: 53%; max-width: 154px; aspect-ratio: 7 / 5;
  }
  #pane-comparison .cmp-img img { object-fit: contain; }
  /* The version being compared gets the widest column and its whole
     card in it, at the card's own proportions. */
  #pane-comparison .cmp-mid {
    grid-template-columns: 22px minmax(0, 1fr) minmax(0, 1fr) 240px
                           minmax(0, 1fr) minmax(0, 1fr);
  }
  #pane-comparison .cmp-mid .cmp-slot.centre .cmp-img {
    width: 100%; max-width: none; height: auto; max-height: none;
    aspect-ratio: 5 / 7; flex: none;
  }
  #pane-comparison .cmp-mid .cmp-slot.centre .cmp-img.landscape { aspect-ratio: 7 / 5; }
  /* Every slot is one block, centred: the card, its lines and its
     price ride together instead of the price being pushed to the
     bottom of a row that is now much taller than they are. */
  #pane-comparison .cmp-lines { justify-content: center; }
  #pane-comparison .cmp-price { margin-top: 6px; }
  #pane-comparison .cmp-slot.centre { justify-content: center; }
  #pane-comparison .cmp-slot.centre .cmp-lines { flex: none; }
}

/* ---- price panel (docs: price-engine/price-stats.html) ------------- */
/* One reading per question, nothing labeled: title line with the window
   control, the median large with its change, three cards, one chart. */
/* The controls that scope a section, pushed to its right-hand end: the
   grade picker (a Condition caption, the grader, then that grader's
   ladder with a median beside each grade) in the card page's tab bar,
   the When control and the order over its sales table. */
.sec-tools { margin-left: auto; display: flex; gap: 8px; align-items: center; font-weight: 400; }
.sec-label {
  font-size: 12px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--text-dim); margin-right: 2px;
}
.sec-tools select { min-width: 110px; }
/* ---- "date": the sales log's day / range control ------------------- */
/* One button and one calendar in place of a From and a To. The popover
   hangs off the button and is drawn over the table below, so the strip's
   height — which app.js measures to size that table — never moves. */
.date-wrap { position: relative; display: inline-flex; align-items: center; gap: 2px; }
/* Frame, caret and height come from the shared rule above; the width it
   holds open so a range does not jostle the strip is its own. */
.date-btn { min-width: 158px; }
.date-btn[aria-expanded="true"] { border-color: var(--accent); }
/* Narrowed inside the window: the button says so, and the reset undoes it. */
.date-btn.on { color: var(--accent); border-color: var(--accent-border); }
.date-clear {
  display: inline-flex; align-items: center; justify-content: center;
  width: 26px; height: 26px; border-radius: 6px; border: 1px solid transparent;
  background: none; color: var(--text-dim); cursor: pointer; padding: 0;
}
.date-clear:hover { color: var(--text); background: var(--bg-hover); }
.date-clear:focus-visible { color: var(--text); border-color: var(--accent); outline: none; }
.date-clear[hidden] { display: none; }

.dr-pop {
  position: absolute; top: calc(100% + 6px); left: 0; z-index: 60; width: 254px;
  padding: 10px; background: var(--bg-panel);
  border: 1px solid var(--border); border-radius: 10px;
  box-shadow: var(--shadow-pop);
}
.dr-pop.up { top: auto; bottom: calc(100% + 6px); }
.dr-nav { display: flex; align-items: center; justify-content: space-between; margin-bottom: 4px; }
.dr-month { font-size: 13px; font-weight: 600; }
.dr-nav button {
  width: 26px; height: 26px; border-radius: 6px; border: 1px solid transparent;
  background: none; color: var(--text); font: inherit; font-size: 15px; cursor: pointer;
}
.dr-nav button:hover:not(:disabled) { background: var(--bg-hover); }
.dr-nav button:disabled { color: var(--text-dim); opacity: 0.35; cursor: default; }
.dr-dow, .dr-grid { display: grid; grid-template-columns: repeat(7, 1fr); }
.dr-grid { row-gap: 2px; }
.dr-dow span {
  padding-bottom: 3px; text-align: center;
  font-size: 10px; font-weight: 600; letter-spacing: 0.04em; color: var(--text-dim);
}
.dr-day, .dr-pad { height: 30px; }
.dr-day {
  border: 1px solid transparent; border-radius: 6px; background: none;
  color: var(--text); font: inherit; font-size: 12px; cursor: pointer; padding: 0;
}
.dr-day:hover:not(:disabled) { background: var(--bg-hover); }
/* The days between the two ends run together into one band, so the
   columns carry no gap and only the ends are rounded. */
.dr-day.in { background: var(--accent-soft); border-radius: 0; }
.dr-day.sel { background: var(--accent); color: var(--on-accent); font-weight: 600; }
.dr-day.cur { border-color: var(--accent); }
.dr-day:disabled { color: var(--text-dim); opacity: 0.3; cursor: default; }
/* The pick-in-progress, for a screen reader only: the calendar shows a
   sighted reader the same thing in colour. */
.dr-said {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap;
}

/* ---- the comparison (docs: price-engine/the-comparison.html) ------- */
/* One row per attribute: the two candidates below this version, the
   version itself, then the two above. Where the version has no price
   of its own the panel drops it and shows five candidates a row, at
   fixed percentiles. A slot is a thumbnail and five attribute lines,
   the differing one lit, then the price. */
.cmp-panel { padding: 14px 16px; }
.cmp { display: grid; grid-template-columns: minmax(0, 1fr); gap: 14px; }
.cmp-rows { display: flex; flex-direction: column; gap: 10px; min-width: 0; }
/* The comparison grid: attribute | two below | the version | two above.
   The attribute is set on its side in a column barely wider than the
   text, so naming the row costs the slots almost nothing. */
.cmp-mid {
  grid-template-columns: 22px minmax(0, 1fr) minmax(0, 1fr) 190px minmax(0, 1fr) minmax(0, 1fr);
  gap: 10px 8px; align-items: stretch;
}
.cmp-mid .cmp-slot.centre { flex-direction: column; }
.cmp-mid .cmp-slot.centre .cmp-img { width: 100%; height: 160px; }
.cmp-mid .cmp-slot.centre .cmp-lines { font-size: 12px; }
.cmp-mid .cmp-slot.centre .cmp-price { font-size: 18px; }
.cmp-row { display: grid; grid-template-columns: 22px minmax(0, 1fr); gap: 8px; }
.cmp-rows .cmp-row + .cmp-row { border-top: 1px solid var(--border); padding-top: 10px; }
.cmp-attr {
  font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em;
  color: var(--text-dim); display: flex; align-items: center; justify-content: center;
  writing-mode: vertical-rl; transform: rotate(180deg); white-space: nowrap;
}
/* A hairline in the gap between two rows, so the label's reach is
   clear. It stops at the version in the middle instead of crossing it. */
.cmp-rule { height: 1px; background: var(--border); align-self: start; margin-top: -5px; }
.cmp-slots { display: grid; gap: 8px; min-width: 0; }
.cmp-slots.five { grid-template-columns: repeat(5, minmax(0, 1fr)); }
.cmp-slots.sides { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0; }
.cmp-side { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; min-width: 0; }
.cmp-side.below { padding-right: 9px; }
.cmp-side.above { padding-left: 9px; border-left: 1px solid var(--border); }
.cmp-slot {
  display: flex; gap: 8px; padding: 8px; min-width: 0;
  border: 1px solid var(--border); border-radius: 8px; background: var(--bg-hover);
  color: var(--text); text-decoration: none;
}
a.cmp-slot:hover { border-color: var(--accent-border); text-decoration: none; }
.cmp-slot.empty { align-items: center; justify-content: center; color: var(--text-dim); border-style: dashed; }
.cmp-img {
  width: 40px; height: 56px; flex: none; border-radius: 4px; overflow: hidden;
  background: var(--bg); display: flex; align-items: center; justify-content: center;
  color: var(--text-dim); font-size: 9px; text-align: center;
}
.cmp-img img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* ---- a drawn face, and the notice that goes with it ---------------- */
/* The FOR REFERENCE ONLY notice is no longer baked into the pixels
   (card_images/watermark.py says why), so what keeps it on every face is
   that every face is drawn by one helper (app.js faceBox) wearing this
   class.

   The bar is sized from the box it sits in, so it holds the same
   proportions from a 24px table row to a 340px card without a breakpoint
   anywhere — and with no floor, because a floor is what stops it being
   proportional: at 24px a 5px minimum made the bar 46% of the picture.

   `min(3.8cqw, 3.8cqh)` is 3.8% of the box's SHORT side, whichever that
   is. A plain cqw would give the same physical card a taller bar in
   landscape than in portrait, since the same card is wider that way
   round. 3.8% is the figure the baked bar used, arrived at by eye. */
.facebox {
  position: relative; overflow: hidden;
  /* `size`, not `inline-size`, so cqh resolves too. Safe because every
     .facebox has a definite width and an aspect-ratio — its height never
     comes from its contents. */
  container-type: size;
}
.facebox-notice {
  position: absolute; left: 0; right: 0; bottom: 0;
  font-size: min(3.8cqw, 3.8cqh);
  line-height: 1.5; padding: 0.3em 0.4em;
  text-align: center; letter-spacing: 0.06em; font-weight: 600;
  color: rgba(255, 255, 255, 0.92);
  background: rgba(0, 0, 0, 0.42);
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);
  pointer-events: none; user-select: none; -webkit-user-select: none;
  white-space: nowrap; overflow: hidden;
}

/* The two boxes that are not sized by their own width. */
.cfg-shot { width: 132px; aspect-ratio: 5 / 7; border-radius: 3px;
  border: 1px solid var(--border); background: var(--bg); }
.cfg-shot.landscape { width: 186px; aspect-ratio: 7 / 5; }
.cfg-shot img { width: 100%; height: 100%; object-fit: cover; display: block; }
.res-shot { width: 190px; aspect-ratio: 5 / 7; border-radius: 3px;
  border: 1px solid var(--border); background: var(--bg); }
.res-shot.landscape { width: 266px; max-width: 100%; aspect-ratio: 7 / 5; }
.res-shot img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* ---- a card thumbnail, either way round ---------------------------- */
/* The stored thumb is always portrait: the pipeline warps every card
   into one 5:7 frame, so a landscape card lies sideways in it. Turning
   the box and the picture puts it upright, and the two numbers are the
   arithmetic that makes the turned picture land on the box exactly —
   un-rotated it is as wide as the box is tall and as tall as the box is
   wide. Same trick as the big card (card3d.css). */
.cmp-img.landscape { width: 56px; height: 40px; }
.cmp-img.landscape, .u-thumb.landscape { aspect-ratio: 7 / 5; }
.cmp-img.landscape img, .u-thumb.landscape img,
.res-shot.landscape img, .cfg-shot.landscape img, .bulk-shot.landscape img {
  position: absolute; left: 50%; top: 50%;
  width: 71.4286%; height: 140%;
  transform: translate(-50%, -50%) rotate(-90deg);
}

/* The unit table's own copy. One number drives it: the card's SHORT
   side, so a portrait and a landscape row are the same height and the
   column never jumps between tables.

   40px is not a taste — it is the ceiling. The stored w120 is 120x168,
   which covers exactly 40x56 CSS on a 3x screen; a pixel more and the
   table starts upscaling and would want its own stored size. So this is
   the biggest a thumbnail can be while staying a thumbnail, and the row
   grows to fit it rather than the other way round. */
/* Cells sit top-aligned by default (the `td` rule), which is right for
   a one-line row and wrong the moment one cell is 56px tall: everything
   else rides up to the top of a 71px row. The sales log already makes
   the same exception for the same reason. */
.units-tbl tbody td { vertical-align: middle; }

.u-thumb { --thumb-short: 40px; }
.u-thumb-h, .u-thumb-c {
  width: calc(40px * 7 / 5); padding-right: 0 !important;
}
/* Both sides are spelled out rather than one plus an aspect-ratio. In a
   table cell, under `container-type: size`, iOS Safari does not treat an
   aspect-ratio height as definite, so the picture's `height: 100%` came
   out far taller than the box and `cover` showed a zoomed slice of the
   card's top. Explicit sides and an absolutely placed picture leave it
   nothing to resolve. */
.u-thumb {
  width: var(--thumb-short); height: calc(var(--thumb-short) * 7 / 5);
  border-radius: 2px; overflow: hidden;
  background: var(--bg); border: 1px solid var(--border);
}
.u-thumb.landscape {
  width: calc(var(--thumb-short) * 7 / 5); height: var(--thumb-short);
}
.u-thumb img {
  position: absolute; inset: 0;
  width: 100%; height: 100%; object-fit: cover; display: block;
}
/* No photograph yet is most rows, and now that the box is 40x56 the
   space it holds is worth naming: a dashed card outline reads as
   "picture, missing", where nothing at all reads as a rendering bug.
   The same shape the big placeholder on the unit page wears. */
.u-thumb.empty {
  border-style: dashed; background: var(--bg-panel); opacity: 0.6;
}
/* A row with a unit but no photograph: the unit page's placeholder at
   table size, and the whole box is the "Submit a photo" button. Only
   the drawing fits in 40px; the words are its title. */
.u-thumb-offer {
  display: flex; flex-direction: column; align-items: center;
  justify-content: center; padding: 0; margin: 0;
  font: inherit; color: var(--text-dim); cursor: pointer;
}
.u-thumb-offer svg {
  width: 55%; height: auto;
  fill: none; stroke: currentColor; stroke-width: 3;
  stroke-linejoin: round; stroke-linecap: round;
}
.u-thumb-offer:hover, .u-thumb-offer:focus-visible {
  opacity: 1; color: var(--accent); border-color: var(--accent);
}
.cmp-lines { flex: 1; min-width: 0; display: flex; flex-direction: column; font-size: 11px; line-height: 1.35; color: var(--text-dim); }
.cmp-line { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cmp-line.lit { color: var(--text); font-weight: 600; }
.cmp-price { margin-top: auto; padding-top: 4px; font-size: 13px; font-weight: 700; color: var(--text); display: flex; gap: 6px; align-items: baseline; flex-wrap: wrap; }
.cmp-gap { font-size: 11px; font-weight: 600; }
.cmp-gap.up { color: var(--green); }
.cmp-gap.down { color: var(--red); }
.cmp-gap.flat { color: var(--text-dim); }
.cmp-none { font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-dim); }
@media (max-width: 900px) {
  /* the comparison grid stacks: the version first, then each row's
     label and its four slots in reading order */
  .cmp-mid { display: flex; flex-direction: column; gap: 8px; }
  .cmp-mid > * { grid-column: auto !important; grid-row: auto !important; }
  .cmp-mid .cmp-slot.centre { flex-direction: row; }
  .cmp-mid .cmp-slot.centre .cmp-img { width: 60px; height: 84px; }
  .cmp-mid .cmp-slot { flex: none; }
  /* Stacked, a row is a heading and a strip: the label reads across
     again and the rules have nothing to separate. */
  .cmp-attr {
    writing-mode: horizontal-tb; transform: none;
    justify-content: flex-start; margin-top: 6px;
  }
  .cmp-rule { display: none; }
  .cmp-row { grid-template-columns: 1fr; gap: 4px; }
  .cmp-slots, .cmp-slots.sides, .cmp-side { display: flex; }
  .cmp-slots { overflow-x: auto; padding-bottom: 4px; }
  .cmp-slot { flex: 0 0 200px; }
  .cmp-side.below { padding-right: 4px; }
  .cmp-side.above { padding-left: 5px; }
}

/* Every sales row stands the same height, and a short last page is
   padded with blank rows, so paging never moves the pager. */
.sales-tbl tbody tr { height: 54px; }
.sales-tbl tbody td { vertical-align: middle; }
.sales-tbl tr.filler td { background: transparent; border-bottom-color: transparent; }
.pp-panel { padding: 14px 16px 10px; }
/* The panel is a column at every width: the title and the window
   buttons on one row, the reading, the cards, then the chart under its
   own legend. */
.pp-head { display: flex; align-items: center; justify-content: space-between; gap: 10px 16px; flex-wrap: wrap; }
.pp-title { font-size: 12px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-dim); }
/* The chart's legend: one line, the marks in the chart's own ink. */
.pp-legend { display: flex; align-items: center; gap: 12px; font-size: 11px; color: var(--text-dim); white-space: nowrap; }
.pp-legend span { display: inline-flex; align-items: center; gap: 6px; }
.pp-windows { display: flex; gap: 2px; }
.pp-win {
  background: none; border: 1px solid transparent; border-radius: 6px;
  color: var(--text-dim); font: inherit; font-size: 12px; padding: 3px 8px; cursor: pointer;
}
.pp-win:hover { color: var(--text); }
.pp-win.active { color: var(--accent); background: var(--accent-soft); border-color: var(--accent-border); }
.pp-hero { display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap; margin-top: 8px; }
.pp-num { font-size: 34px; font-weight: 700; letter-spacing: -0.01em; font-variant-numeric: tabular-nums; line-height: 1.1; }
.pp-chg {
  flex-basis: 100%; min-height: 15px;
  font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em;
}
.pp-chg.up { color: var(--green); }
.pp-chg.down { color: var(--red); }
.pp-chg.flat, .pp-chg.none { color: var(--text-dim); }
/* The reading, the three cards across (as many to a row as the box
   has room for), then the chart, top down. */
.pp-body { display: flex; flex-direction: column; }
.pp-side { display: flex; flex-direction: column; min-width: 0; flex: none; }
.pp-cards {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 8px; margin: 12px 0 8px;
}
.pp-card {
  background: var(--bg-hover); border: 1px solid var(--border); border-radius: 8px;
  padding: 10px 12px; display: flex; flex-direction: column; gap: 2px; min-width: 0;
}
.pp-kl { font-size: 11px; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-dim); }
.pp-kv { font-size: 18px; font-weight: 600; font-variant-numeric: tabular-nums; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.pp-kd { font-size: 12px; color: var(--text-dim); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* Last sale and volume carry their note beside the number, not under it. */
.pp-card.beside {
  display: grid; grid-template-columns: auto minmax(0, 1fr);
  grid-template-areas: "l l" "v d"; align-items: baseline; column-gap: 8px;
}
.pp-card.beside .pp-kl { grid-area: l; }
.pp-card.beside .pp-kv { grid-area: v; }
.pp-card.beside .pp-kd { grid-area: d; }
/* The legend sits over the chart it explains. The plot starts 54px
   into the chart box (app.js drawVersionChart padL, the price labels'
   gutter); the legend starts there too. */
.pp-chart-head { display: flex; flex: none; padding-left: 54px; min-width: 0; overflow: hidden; }
.pp-chart { position: relative; margin-top: 4px; min-width: 0; }
.pp-empty { padding: 18px 2px 12px; font-size: 15px; color: var(--text-dim); }
@media (max-width: 720px) {
  /* A phone has no room for a label column beside a five-rung ladder:
     the name goes above, and every drawing takes the full width. */
  .ab-row { grid-template-columns: minmax(0, 1fr); gap: 6px; }
  .ab-row dd { justify-content: flex-start; }
  .ab-row dd .ab-rank { margin-left: auto; }
  /* The axis's two drawings stack, radar first: half of a narrow screen
     leaves both of them too small to read. The rule that separated them
     goes with the arrangement it was separating. */
  /* stacked, there is no second head to line up with */
  .rd-head { min-height: 0; }
  .ab-pair { flex-direction: column; gap: 14px 0; }
  .ab-pair > * + * { border-left: 0; padding-left: 0; }
  .ab-pair { --ab-draw-h: 280px; }
  /* a pair of radars stacks too */
  .rd-shapes { flex-direction: column; gap: 14px 0; }
  /* stacked, each takes the full width and centres itself */
  .ab-pair > .ab-role { flex: 0 0 auto; align-items: center; }
  .pp-cards { grid-template-columns: 1fr; }
  .pp-num { font-size: 28px; }
  /* No gutter to line up with on a phone, and no room for one line. */
  .pp-chart-head { padding-left: 0; }
  .pp-legend { flex-wrap: wrap; row-gap: 4px; }
}


.chip-row { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; margin: 6px 0; }
.fchip {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 3px 10px; border-radius: 999px; cursor: pointer;
  border: 1px solid var(--border); background: var(--bg-panel);
  color: var(--text-dim); font-size: 12px; font-family: inherit;
}
.fchip:hover { color: var(--text); border-color: var(--text-dim); }
.fchip.active { color: var(--accent); border-color: var(--accent-border); background: var(--accent-soft); }

/* Tooltips: one surface for every hover card in the app — the global
   title tooltip (tooltip.js, .tip-pop) and the chart's hover card
   (.mkt-tooltip) — on the panel colour with the popover shadow, so
   they follow the theme like every other pop. */
.tip-pop, .mkt-tooltip {
  pointer-events: none;
  background: var(--bg-panel); color: var(--text);
  border: 1px solid var(--border); border-radius: var(--radius);
  box-shadow: var(--shadow-pop);
  font-size: 12px; line-height: 1.4;
}
.tip-pop {
  position: fixed; z-index: 100; max-width: 320px;
  padding: 6px 10px; white-space: pre-line; overflow-wrap: anywhere;
}
/* A pressed tooltip stays up, so it has to be dismissable: it takes the
   pointer back, both to swallow the press that closes it (rather than
   letting it through to whatever sits underneath) and to say by its
   cursor that pressing it is what closes it. */
.tip-pop.pinned { pointer-events: auto; cursor: pointer; }
/* No hover on a touch screen means every tip is read pressed, at arm's
   length: give it the body size rather than the hover-card size. */
@media (pointer: coarse) {
  .tip-pop { font-size: 13px; padding: 8px 11px; }
}
.mkt-tooltip {
  position: absolute; z-index: 20;
  min-width: 230px; max-width: 320px;
  padding: 8px 10px;
}
.tt-head { display: flex; justify-content: space-between; gap: 12px; font-weight: 600; }
.tt-n { color: var(--text-dim); font-weight: 500; white-space: nowrap; }
/* Two rows of three numbers, left to right, each over its label: the
   step's own, then the trailing 30-day reading. */
.tt-strip { margin-top: 6px; padding-top: 6px; border-top: 1px solid var(--border); }
.tt-cap { font-size: 10px; color: var(--text-dim); text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 2px; }
.tt-cells { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 0 8px; }
.tt-cell { min-width: 0; }
.tt-cell .k { font-size: 10px; color: var(--text-dim); }
.tt-cell .v { font-weight: 600; font-variant-numeric: tabular-nums; white-space: nowrap; }
.tt-foot { margin-top: 6px; color: var(--text-dim); font-size: 11px; }

/* One line per title, clipped: outranks the t-std .flex wrap rule so
   every sales row keeps its height. */
table.t-std td.sales-title { max-width: 0; min-width: 160px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ---- player/team NBA sections ------------------------------------- */
.chips { display: flex; flex-wrap: wrap; gap: 6px; margin: 4px 0 8px; }
.award-chip {
  display: inline-block; padding: 4px 11px; font-size: 12px;
  border: 1px solid var(--border); border-radius: 14px;
  background: var(--bg-panel); color: var(--text); white-space: nowrap;
}
.award-chip.major { border-color: var(--accent-border); background: var(--accent-soft); }
.award-chip b { color: var(--accent); margin-left: 2px; }
.placeholder {
  border: 1px dashed var(--border); border-radius: var(--radius);
  padding: 26px; text-align: center; color: var(--text-dim); font-size: 13px;
}
.empty-note { color: var(--text-dim); font-size: 13px; padding: 6px 2px 10px; }
/* Compact pager, right-aligned inside a .section-title flex row. */
.pager-mini { margin-left: auto; display: flex; align-items: center; gap: 8px; font-weight: 400; }
.pager-btn {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 28px; height: 24px; padding: 0 8px; border-radius: 6px;
  border: 1px solid var(--border); background: var(--bg-panel);
  color: var(--text); font-size: 14px; line-height: 1; cursor: pointer; font-family: inherit;
}
.pager-btn:hover:not(:disabled) { border-color: var(--accent-border); }
.pager-btn:disabled { opacity: 0.4; cursor: default; }
.pager-info { color: var(--text-dim); font-size: 12px; }
.news-item { padding: 9px 2px; border-bottom: 1px solid var(--border); }
.news-item:last-child { border-bottom: none; }
.news-item > a { font-size: 13px; font-weight: 600; }
.news-meta { font-size: 11px; margin-top: 2px; }
.news-excerpt { font-size: 12px; margin-top: 3px; }
/* Headline-only news (player page): one line per story. */
.news-list.compact .news-item { padding: 5px 2px; display: flex; gap: 12px; align-items: baseline; }
.news-list.compact .news-date { font-size: 11px; white-space: nowrap; min-width: 74px; }
.news-list.compact .news-item > a { font-weight: 500; }

/* ---- card tree (product → set → parallel) -------------------------
   Fixed-height scroll window: expanding a branch never moves the page
   below it. Under a search, non-matching branches fade instead of
   disappearing, so the tree keeps its shape while the hits stand out. */
.tree-panel {
  background: var(--bg-panel); border: 1px solid var(--border);
  border-radius: var(--radius); margin-top: 12px;
}
.tree-bar {
  display: flex; gap: 10px; align-items: center; flex-wrap: wrap;
  padding: 8px 10px; border-bottom: 1px solid var(--border);
}
.tree-bar input.f-search {
  min-width: 320px; padding: 6px 10px; border-radius: 6px;
  border: 1px solid var(--border); background: var(--bg);
  color: var(--text); font-size: 13px; font-family: inherit; outline: none;
}
.tree-bar input.f-search:focus { border-color: var(--accent); }
.tree-bar .spacer { margin-left: auto; }
.tree-bar .result-count { font-size: 12px; color: var(--text-dim); }
.tree-bar button.chip { font: inherit; font-size: 12px; cursor: pointer; }
.tree-view { height: 480px; overflow: auto; padding: 4px 0; }
.tree-node {
  display: flex; align-items: center; gap: 8px; padding: 6px 12px;
  cursor: pointer; font-size: 13px; line-height: 1.4; user-select: none;
}
.tree-node:hover { background: var(--bg-hover); }
.tree-node.lvl2 { padding-left: 34px; }
/* CSS triangle rather than a glyph: the Misc-Technical arrows stalled
   Chrome's paint on Windows during testing, and a border triangle needs
   no font lookup at all. */
.tree-node .caret { width: 12px; flex: none; display: inline-flex; align-items: center; justify-content: center; }
.tree-node .caret::before {
  content: ''; width: 0; height: 0;
  border: 4px solid transparent; border-left: 5px solid var(--text-dim);
  transition: transform 0.12s ease;
}
.tree-node.open .caret::before { transform: rotate(90deg); }
.tree-node .tname { font-weight: 600; }
.tree-node.lvl2 .tname { font-weight: 500; }
.tree-node .tmeta, .tree-leaf .tmeta { font-size: 12px; color: var(--text-dim); white-space: nowrap; }
.tree-node .tlink { margin-left: auto; font-size: 12px; color: var(--text-dim); padding: 0 4px; }
.tree-node .tlink:hover { color: var(--blue); text-decoration: none; }
.tree-leaf {
  display: flex; align-items: center; gap: 8px; padding: 4px 12px 4px 62px;
  font-size: 13px; color: var(--text);
}
.tree-leaf:hover { background: var(--bg-hover); color: var(--blue); text-decoration: none; }
.tree-leaf.loading-leaf { cursor: default; }
.tree-leaf.loading-leaf:hover { background: none; }
.tree-leaf .run { font-size: 12px; color: var(--text-dim); }
.tree-view .dim-out { opacity: 0.35; }
.tree-view .hit > .tname, .tree-leaf.hit > .tname { color: var(--accent); }
/* Championship season: gild the whole standings row. Selector matches
   the even-row striping's specificity so the gold wins on those rows. */
tbody tr.row-champ td { background: var(--gold-soft); }
tbody tr.row-champ td:first-child { box-shadow: inset 3px 0 0 var(--gold-border); }
.po-champ { color: var(--gold); font-weight: 600; }
.wl-w { color: var(--green); font-weight: 600; }
.wl-l { color: var(--red); font-weight: 600; }
.badge.po { color: var(--purple); border-color: var(--purple); }

/* sales-log pin dialog */
.pin-form label { display: block; margin: 8px 0; }
.pin-form .pin-title { font-size: 12px; color: var(--text-dim); margin-bottom: 6px; }
.pin-form .pin-key { width: 22em; max-width: 100%; }
.pin-form .pin-actions { margin-top: 10px; }
.pin-form .pin-note { width: 28em; max-width: 100%; }
/* ---- admin panel controls ------------------------------------------
   Three kinds of control, three looks, so none is mistaken for another:
   - the tab strip switches SECTION (Tickets / Sales Outliers / Health
     Dashboard): plain text on a baseline, the active one underlined;
   - the segmented control FILTERS a list (open / resolved / …): joined
     pills, the active one filled, counts as small figures;
   - buttons ACT (Resolve / Close / History): .btn, the primary one
     wearing the brand gradient. */
.tab-strip { display: flex; gap: 22px; border-bottom: 1px solid var(--border); margin: 0 0 18px; overflow-x: auto; scrollbar-width: none; }
.tab-strip::-webkit-scrollbar { display: none; }
.tab-strip .tab {
  padding: 8px 2px 10px; margin-bottom: -1px; font-size: 14px; font-weight: 500;
  color: var(--text-dim); white-space: nowrap; border-bottom: 2px solid transparent;
}
.tab-strip .tab:hover { color: var(--text); text-decoration: none; }
.tab-strip .tab.active { color: var(--accent); border-bottom-color: var(--accent); font-weight: 600; }

.seg-row { display: flex; flex-wrap: wrap; gap: 10px 22px; margin: 0 0 14px; align-items: center; }
.seg-group { display: flex; align-items: center; gap: 8px; }
.seg-label { font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-dim); }
.seg {
  display: inline-flex; border: 1px solid var(--border); border-radius: 8px;
  background: var(--bg-panel); padding: 2px; gap: 2px;
}
.seg-item {
  display: inline-flex; align-items: center; gap: 6px; padding: 4px 11px; border-radius: 6px;
  font-size: 13px; color: var(--text-dim); white-space: nowrap; text-transform: capitalize;
}
.seg-item:hover { color: var(--text); background: var(--bg-hover); text-decoration: none; }
.seg-item.active { background: var(--accent-soft); color: var(--accent); font-weight: 600; }
.seg-n { font-size: 11px; font-weight: 600; font-variant-numeric: tabular-nums; color: var(--text-dim);
  background: var(--bg); border: 1px solid var(--border); border-radius: 999px; padding: 0 6px; line-height: 16px; }
.seg-item.active .seg-n { color: var(--accent); border-color: var(--accent-border); }

/* ---- ticket list --------------------------------------------------
   One card per ticket (My Tickets and the admin queue share it). The
   card reads top-down — state and reporter, the sale, then a grid of
   report / what the sale says now / outcome — and every cell wraps, so
   the page never scrolls sideways. */
/* One bordered panel, the tickets as rows inside it — the same frame
   the outliers list uses, so the two admin lists read alike. */
.ticket-list { border: 1px solid var(--border); background: var(--bg-panel); }
.ticket { padding: 12px 16px 14px 13px; border-top: 1px solid var(--row-border); border-left: 3px solid transparent; }
.ticket:first-child { border-top: none; }
.ticket:nth-child(even) { background: var(--zebra); }
/* The band names the state at a glance, in the badge's colour: a
   square-cornered stripe, as on the outliers list. */
.ticket-open { border-left-color: var(--orange); }
.ticket-resolved { border-left-color: var(--blue); }
.ticket-closed { border-left-color: var(--border-strong); }
.ticket-top { display: flex; align-items: center; justify-content: space-between; gap: 8px 14px; flex-wrap: wrap; }
.ticket-meta { display: flex; align-items: center; flex-wrap: wrap; gap: 6px 8px; font-size: 12px; color: var(--text-dim); min-width: 0; }
.ticket-meta b { font-weight: 600; color: var(--text); }
.ticket-actions { display: flex; gap: 6px; flex: none; margin-left: auto; }
.ticket-sale { margin-top: 8px; }
.ticket-sale-title { font-weight: 600; line-height: 1.35; overflow-wrap: anywhere; }
.ticket-sale-sub { font-size: 12px; color: var(--text-dim); margin-top: 2px; }
.ticket-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 10px 24px;
  margin-top: 12px; padding-top: 10px; border-top: 1px solid var(--border);
}
.ticket-cell { min-width: 0; }
.ticket-cell .k { font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-dim); margin-bottom: 3px; }
.ticket-cell .v { font-size: 13px; line-height: 1.5; overflow-wrap: anywhere; }
.ticket-cell .badge { vertical-align: 1px; }
.ticket-note { font-size: 12px; color: var(--text-dim); margin-top: 2px; overflow-wrap: anywhere; }

/* Ticket history: the events down a rail. Everything wraps. */
.tl-sale { font-weight: 600; line-height: 1.35; overflow-wrap: anywhere; margin-bottom: 14px; }
.timeline { list-style: none; margin: 0; padding: 0 0 0 18px; border-left: 2px solid var(--border); }
.tl-item { position: relative; padding: 0 0 16px 4px; }
.tl-item:last-child { padding-bottom: 2px; }
.tl-item::before {
  content: ""; position: absolute; left: -25px; top: 5px; width: 10px; height: 10px;
  border-radius: 50%; background: var(--bg-panel); border: 2px solid var(--border-strong);
}
.tl-item.tl-opened::before { border-color: var(--orange); }
.tl-item.tl-linked::before, .tl-item.tl-unlinked::before, .tl-item.tl-reset::before, .tl-item.tl-regraded::before { border-color: var(--blue); background: var(--blue); }
.tl-head { display: flex; align-items: center; flex-wrap: wrap; gap: 6px 8px; font-size: 13px; line-height: 20px; }
.tl-head b { font-weight: 600; }
.tl-note { font-size: 13px; margin-top: 4px; overflow-wrap: anywhere; }
.tl-change { font-size: 12px; color: var(--text-dim); margin-top: 3px; overflow-wrap: anywhere; }
.tl-change .k { display: inline-block; width: 36px; font-size: 10px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; }

/* Sales outliers: two lines a flag. Line one is what the rule saw with
   the state on the left and the actions on the right; line two is the
   sale (title clipped to one line) and what it is filed under. Both
   wrap before they scroll. */
.flag-list { border: 1px solid var(--border); background: var(--bg-panel); }
/* Fun Facts: the form a fact is added with, over the list of them. */
.fact-new { display: flex; flex-direction: column; gap: 8px; max-width: 760px; margin: 0 0 16px; padding: 12px;
  border: 1px solid var(--border); background: var(--bg-panel); }
.fact-new .run-input { width: 100%; height: auto; min-height: 2.2em; padding: 6px 8px; text-align: left;
  font-size: 13px; color: var(--text); }
.fact-new textarea.run-input, textarea.fact-edit { resize: vertical; line-height: 1.4; }
.fact-pick { position: relative; }
.fact-new-foot { display: flex; align-items: center; gap: 12px; font-size: 12px; }
.fact-new-foot .cfg-err { flex: 1 1 auto; }
.fact-new-foot .btn { margin-left: auto; }
textarea.fact-edit { width: 100%; height: auto; padding: 4px 6px; text-align: left; font-size: 13px; color: var(--text); }
select.fact-edit-unit { display: block; margin-top: 4px; max-width: 100%; font-size: 12px; }
.mini-btn.armed { color: var(--red); border-color: var(--red-border); background: var(--red-soft); }
.flag-row { padding: 9px 14px 10px 12px; border-top: 1px solid var(--row-border); border-left: 3px solid transparent; }
.flag-row:first-child { border-top: none; }
.flag-row:nth-child(even) { background: var(--zebra); }
.flag-row.flag-open { border-left-color: var(--orange); }
.flag-row.flag-resolved { border-left-color: var(--blue); }
.flag-row.flag-dismissed { border-left-color: var(--border-strong); }
.flag-l1, .flag-l2 { display: flex; align-items: center; flex-wrap: wrap; gap: 4px 8px; font-size: 13px; line-height: 20px; min-width: 0; }
.flag-l2 { margin-top: 3px; font-size: 12px; }
.flag-why { flex: 1 1 260px; min-width: 0; overflow-wrap: anywhere; }
.flag-why b { font-weight: 600; }
.flag-x { display: inline-block; padding: 0 6px; border-radius: 999px; font-size: 11px; font-weight: 700;
  line-height: 16px; color: var(--orange); border: 1px solid var(--orange-border); background: var(--orange-soft);
  font-variant-numeric: tabular-nums; }
.flag-when { font-size: 11px; white-space: nowrap; }
.flag-actions { display: flex; gap: 4px; flex: none; margin-left: auto; }
.flag-title { flex: 1 1 240px; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--text); }
.flag-meta { white-space: nowrap; }
.flag-now { flex: 0 1 auto; min-width: 0; overflow-wrap: anywhere; }
.flag-now .badge { vertical-align: 1px; }
.flag-l3 { margin-top: 3px; font-size: 12px; overflow-wrap: anywhere; }
.flag-l3 b { font-weight: 600; color: var(--text); }
#flag-list .pager-top { padding-bottom: 8px; }
#flag-list .pager-bottom { padding-top: 8px; }

/* ---- health dashboard ----------------------------------------------
   Verdict first, coloured by the worst finding; the findings under it
   link to the section (or the run) that explains them. */
.hd-verdict { border: 1px solid var(--border); border-left: 4px solid var(--green); background: var(--bg-panel); padding: 14px 18px; margin: 4px 0 6px; }
.hd-verdict.hd-warn { border-left-color: var(--orange); }
.hd-verdict.hd-fail { border-left-color: var(--red); }
.hd-verdict-head { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; font-size: 15px; }
.hd-verdict-head b { font-weight: 700; }
.hd-verdict-head .dim { font-size: 12px; }
.hd-findings { list-style: none; margin: 10px 0 0; padding: 0; display: flex; flex-direction: column; gap: 5px; font-size: 13px; }
.hd-findings li { display: flex; align-items: center; gap: 8px; }
.hdot { display: inline-block; width: 9px; height: 9px; border-radius: 50%; flex: none; background: var(--border-strong); }
.hdot-ok { background: var(--green); }
.hdot-warn { background: var(--orange); }
.hdot-fail { background: var(--red); }
.hd-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 10px; }
.hd-card { border: 1px solid var(--border); background: var(--bg-panel); padding: 12px 14px 12px; min-width: 0; }
.hd-card-head { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; font-size: 14px; }
.hd-card-head .dim { font-size: 12px; }
.hd-run { margin-left: auto; font-size: 12px; white-space: nowrap; }
.hd-card-line { font-size: 12px; margin-top: 4px; }
.hd-steps { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 8px; }
.hd-step { font-size: 11px; padding: 1px 7px; border-radius: 999px; border: 1px solid var(--border); color: var(--text-dim); background: var(--bg); }
.hd-step-ok { color: var(--green); border-color: var(--green-border); }
.hd-step-fail { color: var(--red); border-color: var(--red-border); }
.hd-step-skip { border-style: dashed; }
.hd-card-findings { list-style: none; margin: 8px 0 0; padding: 0; font-size: 12px; display: flex; flex-direction: column; gap: 4px; }
.hd-card-findings li { display: flex; align-items: flex-start; gap: 7px; overflow-wrap: anywhere; }
.hd-card-findings .hdot { margin-top: 5px; }
.hd-cost { font-size: 12px; margin-top: 18px; }

/* Sales data: a rolling year of days, weeks as columns, Sunday on top.
   Cells size from the panel's width, so the year always fits without
   scrolling; the colour is the day's health band (health.py). */
.sc-panel { padding: 14px 16px; }
.sales-cal { display: grid; grid-template-columns: auto minmax(0, 1fr); column-gap: 6px; row-gap: 4px; max-width: 900px; }
.sc-months { grid-column: 2; display: grid; grid-template-columns: repeat(var(--weeks), minmax(0, 1fr)); gap: 3px; font-size: 11px; color: var(--text-dim); }
.sc-months span { white-space: nowrap; overflow: visible; }
.sc-wd { grid-column: 1; grid-row: 2; display: grid; grid-template-rows: repeat(7, 1fr); gap: 3px; font-size: 10px; color: var(--text-dim); }
.sc-wd span { display: flex; align-items: center; line-height: 1; }
.sc-grid { grid-column: 2; grid-row: 2; display: grid; grid-template-columns: repeat(var(--weeks), minmax(0, 1fr)); grid-template-rows: repeat(7, auto); grid-auto-flow: column; gap: 3px; }
.sc { display: block; aspect-ratio: 1; border-radius: 2px; background: var(--bg-hover); }
.sc-high { background: var(--green); }
.sc-normal { background: color-mix(in srgb, var(--green) 55%, var(--bg-panel)); }
.sc-soft { background: color-mix(in srgb, var(--orange) 45%, var(--bg-panel)); }
.sc-thin { background: var(--orange); }
.sc-outage { background: var(--red); }
.sc-settling { background: repeating-linear-gradient(135deg, var(--border) 0 2px, transparent 2px 4px); box-shadow: inset 0 0 0 1px var(--border); }
.sc-unscored { background: var(--bg-hover); }
.sc-none { background: transparent; box-shadow: inset 0 0 0 1px var(--border); }
.sc-future { background: transparent; }
.sc-legend { display: flex; flex-wrap: wrap; gap: 6px 16px; margin-top: 14px; font-size: 12px; }
.sc-key { display: inline-flex; align-items: center; gap: 6px; }
.sc-key .sc { width: 11px; height: 11px; }
.sc-foot { font-size: 12px; margin-top: 10px; }
@media (max-width: 640px) {
  .sc-grid, .sc-months, .sc-wd { gap: 1px; }
  .sc { border-radius: 1px; }
  .sc-wd { font-size: 9px; }
}
.overlay-sub .badge { vertical-align: 1px; }
/* a grade state we read off the listing title ourselves (parse.py) */
.badge.read { color: var(--blue); border-color: var(--blue-border); }

/* ==== the fix dialog =================================================== */
/* Two sections, one per way a sale can be wrong, on the same inputs and
   buttons as the rest of the app (.input / .btn / .field-label). */

.fix-listing {
  font-size: 13px; color: var(--text); line-height: 1.4;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.fix-report {
  margin-top: 7px; padding: 2px 0 2px 11px; font-size: 12px; line-height: 1.5;
  color: var(--text-dim); border-left: 2px solid var(--border);
}
.fix-sec { margin-top: 16px; }
.fix-sec-head { display: flex; align-items: baseline; gap: 14px; margin-bottom: 8px; }
.fix-sec-head .field-label { margin: 0; flex: none; }
.fix-mode {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: 12px; color: var(--text-dim); cursor: pointer;
}
.fix-mode input { accent-color: var(--accent); margin: 0; }
.fix-mode:has(input:checked) { color: var(--text); }
.fix-note { margin-left: auto; font-size: 12px; color: var(--text-dim); }
.fix-form .fix-find { width: 100%; margin-bottom: 8px; }
.fix-cascade { display: grid; grid-template-columns: 1fr 1fr; gap: 10px 14px; }
.fix-field { display: block; min-width: 0; }
.fix-field .field-label { font-size: 11px; margin-bottom: 4px; }
/* The app's own selects (see the `select` rule at the top), sized to the
   dialog: full width of their column, ellipsised rather than clipped. */
.fix-form select { padding-top: 7px; padding-bottom: 7px; text-overflow: ellipsis; }
.fix-field select { width: 100%; }
.fix-grade { display: flex; gap: 8px; align-items: center; }
.fix-grade select { min-width: 7.5em; }
/* Other Grader's name, standing where that grader's ladder would be. */
.fix-grade .fx-grader-name { width: 11em; }
/* [hidden] loses to .btn's own display, so say it once and for all. */
[hidden] { display: none !important; }
.fix-hits {
  margin-bottom: 8px; max-height: 190px; overflow-y: auto;
  background: var(--bg); border: 1px solid var(--border); border-radius: 6px;
}
.fix-hit {
  display: block; width: 100%; text-align: left; cursor: pointer;
  padding: 6px 10px; font: inherit; background: none; border: 0;
  border-bottom: 1px solid var(--border); color: var(--text);
}
.fix-hit:last-child { border-bottom: 0; }
.fix-hit:hover { background: var(--bg-hover); }
.fix-hit-name { display: block; font-size: 13px; }
.fix-hit-ctx { display: block; font-size: 11px; color: var(--text-dim); margin-top: 1px; }
.fix-hit-none { padding: 7px 10px; font-size: 12px; color: var(--text-dim); }
.fix-foot {
  display: flex; align-items: center; gap: 10px;
  margin-top: 18px; padding-top: 13px; border-top: 1px solid var(--border);
}
.fix-foot .fix-note-in { flex: 1; }
.fix-foot .form-msg { flex: none; margin: 0; min-height: 0; }

/* ==== design system additions: inputs, buttons, cards ================== */

.input {
  display: block; width: 100%; box-sizing: border-box;
  padding: 9px 12px; font: inherit; font-size: 14px;
  color: var(--text); background: var(--bg);
  border: 1px solid var(--border); border-radius: 6px; outline: none;
  transition: border-color .12s, box-shadow .12s;
}
.input::placeholder { color: var(--text-dim); opacity: .8; }
/* Focus only lifts the grey border: colour on an input means pass / fail. */
.input:focus { border-color: var(--border-strong); box-shadow: 0 0 0 3px var(--ring-neutral); }
.input.ok, .input.ok:focus { border-color: var(--green-border); }
.input.ok:focus { box-shadow: 0 0 0 3px var(--green-soft); }
.input.bad, .input.bad:focus { border-color: var(--red); }
.input.bad:focus { box-shadow: 0 0 0 3px var(--red-glow); }
.input-wrap { position: relative; display: block; }
.input-wrap .input { padding-right: 66px; }
.input-wrap.no-check .input { padding-right: 36px; }
.input-check {
  position: absolute; right: 40px; top: 50%; transform: translateY(-50%);
  color: var(--green); font-weight: 700; display: none; pointer-events: none;
}
.input-wrap.ok .input-check { display: block; }
.input-toggle {
  position: absolute; right: 6px; top: 50%; transform: translateY(-50%);
  display: flex; padding: 6px; background: none; border: none; border-radius: 4px;
  color: var(--text-dim); cursor: pointer;
}
.input-toggle:hover { color: var(--text); background: var(--accent-soft); }
.field-hint .ok { color: var(--green); }
.field-hint .bad { display: block; text-align: left; color: var(--red); }
.input-sm { padding: 6px 10px; font-size: 13px; width: 200px; }
.field { display: block; margin-bottom: 14px; }
.field-label { display: block; font-size: 12px; font-weight: 600; color: var(--text-dim); margin-bottom: 6px;
  text-transform: uppercase; letter-spacing: .04em; }
.field-hint { display: block; font-size: 12px; margin-top: 6px; text-align: right; }
.check { display: flex; gap: 10px; align-items: flex-start; font-size: 13px; color: var(--text); margin: 4px 0 14px; cursor: pointer; }
.check input { margin-top: 3px; accent-color: var(--accent); }

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  padding: 9px 16px; font: inherit; font-size: 14px; font-weight: 600;
  color: var(--text); background: var(--bg-hover);
  border: 1px solid var(--border); border-radius: 6px; cursor: pointer;
  text-decoration: none; transition: background .12s, border-color .12s, color .12s;
}
.btn:hover { background: var(--bg-hover-2); border-color: var(--border-strong); text-decoration: none; }
.btn:disabled { opacity: .45; cursor: default; }
.btn:disabled:hover { background: var(--bg-hover); border-color: var(--border); }
.btn-primary {
  color: var(--on-accent); border-color: transparent; box-shadow: var(--shadow-btn);
  background: var(--brand-gradient); background-size: 170% 100%; background-position: 0 0;
  /* Painted from the border's outer edge: laid out from the padding box, the
     gradient repeats under the clear 1px border and its far end shows as a
     sliver down the left side. */
  background-origin: border-box;
  transition: background-position .25s ease, box-shadow .12s;
}
/* The gradient is restated: .btn:hover's `background` would wipe it and
   leave white text on a pale fill. */
.btn-primary:hover { background: var(--brand-gradient); background-size: 170% 100%; background-position: 100% 0;
  background-origin: border-box;
  border-color: transparent; box-shadow: 0 4px 14px var(--accent-glow); }
.btn-primary:disabled, .btn-primary:disabled:hover { opacity: 1; color: var(--text-dim);
  background: var(--bg-hover); border-color: var(--border); box-shadow: none; }
.btn-lg { padding: 12px 18px; font-size: 15px; border-radius: 8px; }
.btn-danger { color: var(--red); background: var(--red-soft); border-color: var(--red-border); }
.btn-danger:hover { background: var(--red-glow); border-color: var(--red); }
.btn-google { background: #fff; color: #1f1f1f; border-color: #dadce0; }
.btn-google:hover { background: #f4f6f8; border-color: #c6cad0; }
.btn-block { width: 100%; }
.btn-sm { padding: 6px 12px; font-size: 13px; }
.g-logo { width: 18px; height: 18px; flex: none; vertical-align: -3px; }
.form-msg { min-height: 18px; margin-top: 10px; font-size: 13px; color: var(--text-dim); }
.form-msg.inline { margin: 0 0 0 10px; }

.card {
  background: var(--bg-panel); border: 1px solid var(--border); border-radius: 10px;
  padding: 4px 20px 6px; margin-bottom: 18px;
}
.card-head { font-size: 12px; font-weight: 700; text-transform: uppercase; letter-spacing: .06em;
  color: var(--text-dim); padding: 14px 0 6px; }
.card-danger { border-color: var(--red-border); }
.row { display: flex; align-items: center; justify-content: space-between; gap: 18px;
  padding: 14px 0; border-top: 1px solid var(--border); }
.row-main { min-width: 0; }
.row-title { font-weight: 600; display: flex; align-items: center; gap: 8px; }
.row-sub { font-size: 13px; color: var(--text-dim); margin-top: 3px; }
.row-side { display: flex; align-items: center; gap: 8px; flex: none; }
.row-form { padding: 4px 0 14px; border-top: 1px dashed var(--border); }
.row-form .field { max-width: 360px; }
.row-actions { display: flex; align-items: center; gap: 8px; }

/* A segmented choice (Settings → Theme): one frame, the chosen option
   raised on the panel colour. */
.seg {
  display: inline-flex; padding: 3px; gap: 2px;
  background: var(--bg); border: 1px solid var(--border); border-radius: 8px;
}
.seg-btn {
  padding: 5px 12px; border: 0; border-radius: 6px; background: none;
  color: var(--text-dim); font: inherit; font-size: 13px; font-weight: 500; cursor: pointer;
}
.seg-btn:hover { color: var(--text); }
.seg-btn.active { background: var(--bg-panel); color: var(--accent); font-weight: 600; box-shadow: var(--shadow-btn); }
.seg-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

/* ==== the human gate (gate.html) ======================================== */
/* The same card the sign-in pages use, standing alone on the page. */
body.gate-page { min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 24px; }
.gate-wrap { width: 100%; display: flex; justify-content: center; }
.gate-card {
  width: 100%; max-width: 400px; text-align: center;
  background: var(--bg-panel); border: 1px solid var(--border); border-radius: 14px;
  padding: 36px 32px 30px; box-shadow: var(--shadow-card);
}
.gate-card .brand-lockup { margin: 0 auto 22px; width: 190px; }
.gate-card h1 { font-size: 17px; font-weight: 600; margin: 0 0 8px; }
.gate-card p { margin: 0 0 24px; color: var(--text-dim); font-size: 14px; }
.gate-widget { display: flex; justify-content: center; min-height: 66px; }
.gate-card .msg { margin-top: 18px; font-size: 13px; min-height: 18px; color: var(--text-dim); }
.gate-card .msg.err { color: var(--red); }
.gate-retry {
  margin-top: 14px; display: none; cursor: pointer; color: var(--blue);
  background: none; border: none; font: inherit; text-decoration: underline;
}
/* The same card over the running app, when the cookie runs out under
   an open tab (auth.js gateChallenge). Above every dialog. */
.gate-overlay {
  position: fixed; inset: 0; z-index: 120; display: flex;
  align-items: center; justify-content: center; padding: 24px;
  background: var(--overlay);
}

/* ==== auth pages ======================================================== */

.auth-wrap { min-height: calc(100vh - 140px); display: flex; flex-direction: column; gap: 14px;
  align-items: center; justify-content: center; padding: 24px 0; }
/* Under the card on every sign-in page: the documents, always one tap away. */
.auth-legal { font-size: 12px; color: var(--text-dim); }
.auth-legal a { color: var(--text-dim); padding: 2px 4px; border-radius: 4px; }
.auth-legal a:hover { color: var(--text); background: var(--bg-hover); text-decoration: none; }
/* The accept screen's documents: one row each, the whole row the link,
   with what changed under the name when something did. */
.auth-card-terms { max-width: 440px; }
.terms-docs { margin: 0 0 16px; border: 1px solid var(--border); border-radius: 10px; overflow: hidden; }
.terms-doc { display: flex; align-items: center; gap: 12px; padding: 11px 14px; color: var(--text);
  transition: background .12s; }
.terms-doc + .terms-doc { border-top: 1px solid var(--border); }
.terms-doc:hover { background: var(--bg-hover); text-decoration: none; }
.terms-doc:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.terms-doc-ic { flex: none; color: var(--accent); }
.terms-doc-text { flex: 1; min-width: 0; }
.terms-doc-text b { display: block; font-size: 14px; font-weight: 600; }
.terms-doc-text small { display: block; margin-top: 2px; font-size: 12px; line-height: 1.4; color: var(--text-dim); }
.terms-doc-go { flex: none; color: var(--text-dim); transition: color .12s, transform .12s; }
.terms-doc:hover .terms-doc-go { color: var(--accent); transform: translate(1px, -1px); }
/* The box a reader ticks to agree, here and on sign-up: drawn, so it
   reads as the one thing to do before the button wakes up. */
.agree { display: flex; gap: 12px; align-items: flex-start; margin: 0 0 14px; padding: 12px 14px;
  font-size: 13px; line-height: 1.5; color: var(--text); cursor: pointer;
  border: 1px solid var(--border); border-radius: 10px; transition: background .12s, border-color .12s; }
.agree:hover { border-color: var(--border-strong); }
.agree input { position: absolute; opacity: 0; width: 0; height: 0; }
.agree i { flex: none; width: 18px; height: 18px; margin-top: 1px; border-radius: 5px;
  border: 1.5px solid var(--border-strong); background: var(--bg-panel);
  display: inline-flex; align-items: center; justify-content: center; transition: background .12s, border-color .12s; }
.agree i::after { content: ""; width: 9px; height: 5px; margin-top: -2px;
  border-left: 2px solid var(--on-accent); border-bottom: 2px solid var(--on-accent);
  transform: rotate(-45deg) scale(0); transition: transform .12s; }
.agree input:checked + i { background: var(--accent); border-color: var(--accent); }
.agree input:checked + i::after { transform: rotate(-45deg) scale(1); }
.agree input:focus-visible + i { box-shadow: 0 0 0 3px var(--accent-glow); }
.agree:has(input:checked) { background: var(--accent-soft); border-color: var(--accent); }
.agree a { font-weight: 600; }
#tm-leave .btn { margin: 8px 6px 0 0; }
.auth-card-terms .form-msg:empty { display: none; }

/* ==== the documents as pages (static/legal.html) ======================= */

.legal-bar { position: sticky; top: 0; z-index: 5; display: flex; align-items: center; gap: 20px;
  padding: 10px 24px; background: var(--bg-panel); border-bottom: 1px solid var(--border); }
.legal-bar .auth-brand { margin: 0; flex: none; }
.legal-bar .auth-brand .brand-lockup { width: 96px; }
.legal-tabs { display: flex; gap: 4px; margin-left: auto; overflow-x: auto; scrollbar-width: none; }
.legal-tabs a { flex: none; padding: 7px 12px; border-radius: 999px; font-size: 13px; font-weight: 500;
  color: var(--text-dim); white-space: nowrap; }
.legal-tabs a:hover { background: var(--bg-hover); color: var(--text); text-decoration: none; }
.legal-tabs a.on { background: var(--accent-soft); color: var(--accent); font-weight: 600; }
.legal { max-width: 780px; margin: 28px auto 80px; padding: 40px 48px 36px;
  background: var(--bg-panel); border: 1px solid var(--border); border-radius: 14px;
  box-shadow: var(--shadow-card); }
.legal h1 { font-size: 32px; font-weight: 800; letter-spacing: -0.02em; margin-bottom: 6px; }
.legal-meta { font-size: 13px; color: var(--text-dim); margin-bottom: 28px; }
.legal-draft { margin: 0 0 24px; padding: 10px 14px; font-size: 13px;
  border: 1px solid var(--orange-border); border-radius: 8px;
  color: var(--orange); background: var(--orange-soft); }
.legal-body { font-size: 15px; line-height: 1.7; }
.legal-body h2 { font-size: 17px; font-weight: 700; margin: 32px 0 8px; padding-top: 20px;
  border-top: 1px solid var(--border); }
.legal-nav:empty { display: none; }
@media (max-width: 760px) {
  .legal-bar { flex-direction: column; align-items: stretch; gap: 8px; padding: 10px 12px; }
  .legal-bar .auth-brand { justify-content: flex-start; }
  .legal-tabs { margin-left: 0; }
  .legal { margin: 0; padding: 24px 18px 48px; border: 0; border-radius: 0; box-shadow: none; }
  .legal h1 { font-size: 26px; }
}
.legal-body p, .legal-body ul, .legal-body ol { margin-bottom: 12px; }
.legal-body ul, .legal-body ol { padding-left: 22px; }
.legal-body li { margin-bottom: 6px; }
.legal-body a, .legal-meta a, .legal-nav a { text-decoration: underline; }
.legal-body table { width: 100%; border-collapse: collapse; margin-bottom: 14px; font-size: 14px; }
.legal-body th, .legal-body td { text-align: left; vertical-align: top; padding: 8px 10px;
  border: 1px solid var(--border); }
.legal-body th { background: var(--bg-hover); font-weight: 600; }
.legal-nav { margin-top: 36px; padding-top: 16px; border-top: 1px solid var(--border);
  font-size: 13px; color: var(--text-dim); }
.legal-nav p { margin-bottom: 6px; }
.auth-card {
  width: 100%; max-width: 400px; padding: 30px 32px 26px;
  background: var(--bg-panel); border: 1px solid var(--border); border-radius: 12px;
  box-shadow: var(--shadow-card);
}
.auth-brand { display: flex; justify-content: center; margin-bottom: 20px; }
.auth-brand:hover { text-decoration: none; }
.auth-brand .brand-lockup { width: 176px; }
.auth-title { font-size: 22px; font-weight: 700; letter-spacing: -0.01em; margin-bottom: 4px; }
.auth-sub { color: var(--text-dim); font-size: 13px; margin-bottom: 20px; line-height: 1.5; }
.auth-form .btn-block { margin-top: 4px; }
.auth-divider { display: flex; align-items: center; gap: 10px; margin: 18px 0; color: var(--text-dim);
  font-size: 11px; text-transform: uppercase; letter-spacing: .08em; }
.auth-divider::before, .auth-divider::after { content: ""; flex: 1; border-top: 1px solid var(--border); }
.auth-turnstile { margin: 2px 0 14px; display: flex; justify-content: center; min-height: 0; }
.auth-turnstile:empty { display: none; }
.auth-foot { margin-top: 20px; padding-top: 16px; border-top: 1px solid var(--border);
  font-size: 13px; color: var(--text-dim); text-align: center; }
.auth-error { margin: 0 0 14px; padding: 10px 12px; border: 1px solid var(--red-border);
  border-radius: 6px; color: var(--red); background: var(--red-soft); font-size: 13px; }
.auth-note { margin: 0 0 14px; padding: 10px 12px; border: 1px solid var(--green-border);
  border-radius: 6px; color: var(--green); background: var(--green-soft); font-size: 13px; }
.otp-input { text-align: center; font-size: 26px; letter-spacing: .5em; padding: 12px; margin-bottom: 14px;
  font-variant-numeric: tabular-nums; }

.pw-block { margin: -6px 0 14px; }
.pw-meter { height: 4px; background: var(--border); border-radius: 2px; overflow: hidden; }
.pw-bar { height: 100%; transition: width .15s; }
.pw-bar.weak { background: var(--red); } .pw-bar.fair { background: var(--orange); }
.pw-bar.good { background: var(--gold); } .pw-bar.strong { background: var(--green); }
.pw-label { font-size: 11px; color: var(--text-dim); margin-top: 5px; }
.pw-label .weak { color: var(--red); } .pw-label .fair { color: var(--orange); }
.pw-label .good { color: var(--gold); } .pw-label .strong { color: var(--green); }
.pw-rules { list-style: none; padding: 0; margin: 6px 0 0; font-size: 11px; color: var(--text-dim);
  display: grid; grid-template-columns: 1fr 1fr; gap: 2px 10px; }
.pw-rules li.no { color: var(--text-dim); } .pw-rules li.no::before { content: "\2715  "; color: var(--red); }
.pw-rules li.ok { color: var(--green); } .pw-rules li.ok::before { content: "\2713  "; }

/* ==== top bar: profile menu ============================================ */

#account-slot { margin-left: 10px; display: flex; align-items: center; }
.avatar {
  width: 32px; height: 32px; border-radius: 50%; display: inline-flex; align-items: center; justify-content: center;
  font-size: 12px; font-weight: 700; letter-spacing: .02em; color: var(--on-accent);
  background: var(--brand-gradient); border: 1px solid transparent; cursor: pointer; font-family: inherit;
}
.avatar:hover { box-shadow: 0 0 0 3px var(--accent-glow); }
.avatar-lg { width: 44px; height: 44px; font-size: 15px; cursor: default; }
.avatar-lg:hover { box-shadow: none; }
.profile-menu { position: relative; }
.menu-dropdown {
  position: absolute; right: 0; top: calc(100% + 8px); width: 240px; display: none;
  background: var(--bg-panel); border: 1px solid var(--border); border-radius: 10px;
  box-shadow: var(--shadow-pop); padding: 6px; z-index: 60;
}
.profile-menu::after { content: ""; position: absolute; left: 0; right: 0; top: 100%; height: 10px; }
.profile-menu:hover .menu-dropdown, .profile-menu.open .menu-dropdown, .profile-menu:focus-within .menu-dropdown { display: block; }
.menu-head { padding: 8px 10px 10px; border-bottom: 1px solid var(--border); margin-bottom: 6px; }
.menu-name { font-weight: 600; font-size: 13px; display: flex; align-items: center; gap: 6px; }
.menu-email { font-size: 12px; color: var(--text-dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.menu-item { display: block; width: 100%; text-align: left; padding: 8px 10px; border-radius: 6px;
  color: var(--text); font: inherit; font-size: 13px; background: none; border: 0; cursor: pointer; }
.menu-item:hover { background: var(--bg-hover); text-decoration: none; }
.menu-sep { border-top: 1px solid var(--border); margin: 6px 0; }
.local-pill { font-size: 12px; color: var(--text-dim); padding: 4px 10px; border: 1px dashed var(--border); border-radius: 999px; }

/* ==== account area ===================================================== */

.account-layout { display: grid; grid-template-columns: 220px minmax(0, 1fr); gap: 28px; align-items: start; margin-top: 6px; }
.account-nav { position: sticky; top: 70px; }
.account-who { display: flex; align-items: center; gap: 12px; padding: 4px 10px 16px; }
.account-who-text { min-width: 0; }
.account-who-name { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.account-who-email { font-size: 12px; color: var(--text-dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.account-nav-item { display: flex; align-items: center; gap: 8px; padding: 8px 12px; border-radius: 6px;
  color: var(--text-dim); font-weight: 500; font-size: 13px; }
.account-nav-item:hover { background: var(--bg-hover); color: var(--text); text-decoration: none; }
.account-nav-item.active { background: var(--accent-soft); color: var(--accent); }
.account-title { font-size: 20px; font-weight: 700; margin: 4px 0 16px; letter-spacing: -0.01em; }
@media (max-width: 760px) {
  .account-layout { grid-template-columns: 1fr; gap: 12px; }
  .account-nav { position: static; display: flex; flex-wrap: wrap; gap: 4px; }
  .account-who { width: 100%; }
  .brand-line { display: none; }
  .brand-mark { display: block; }
}

/* Sign-in pages stand alone: the bar goes, the card takes the viewport. */
body.auth-page #topnav { display: none; }
body.auth-page .auth-wrap { min-height: calc(100vh - 44px); }

/* ==== phone widths ===================================================== */

.icon-btn {
  display: none; align-items: center; justify-content: center;
  width: 34px; height: 34px; border-radius: 50%; border: 1px solid transparent;
  background: none; color: var(--text-dim); cursor: pointer; padding: 0;
}
.icon-btn:hover { background: var(--bg-hover); color: var(--text); }

@media (max-width: 640px) {
  #topnav { padding: 8px 10px; gap: 4px; }
  .brand { margin-right: 2px; height: 28px; }
  .brand-mark { display: block; width: 28px; height: 28px; }
  .brand-line { display: none; }
  /* the section links take what is left and scroll sideways if needed,
     so the search and the avatar always stay on screen */
  .topnav-links { flex: 1 1 auto; min-width: 0; overflow-x: auto; scrollbar-width: none; padding: 2px 0; }
  .topnav-links::-webkit-scrollbar { display: none; }
  .topnav-btn { padding: 5px 9px; font-size: 13px; white-space: nowrap; }
  /* search collapses to an icon; opening it lays the input over the bar */
  #omnibox { width: auto; margin-left: 2px; flex: none; }
  .icon-btn { display: inline-flex; }
  #omnibox:not(.open) #omni-input { display: none; }
  #omnibox.open { position: absolute; left: 8px; right: 8px; top: 7px; width: auto; z-index: 61;
    display: flex; align-items: center; gap: 6px; background: var(--bg); border-radius: 8px; }
  #omnibox.open .icon-btn { color: var(--accent); }
  #omnibox.open #omni-input { display: block; flex: 1; padding: 8px 12px; }
  #omni-results { left: 0; right: 0; width: auto; max-height: 60vh; }
  #account-slot { margin-left: 2px; flex: none; }
  .local-pill { display: none; }
  .avatar { width: 30px; height: 30px; font-size: 11px; }
  .menu-dropdown { width: 220px; }

  #view { padding: 14px 12px 60px; }
  .hero { padding: 22px 0 4px; }
  .hero h1 { font-size: 22px; }
  .page-title { font-size: 19px; }
  .stat-row { gap: 8px; }
  .stat-tile { flex: 1 1 calc(33.333% - 8px); min-width: 96px; padding: 10px 8px; }
  .stat-tile .num { font-size: 17px; }
  th, td { padding: 7px 8px; font-size: 13px; }
  th { font-size: 10px; }
  .kv-grid { grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); gap: 8px 14px; }
  .overlay-box { width: calc(100vw - 20px); max-width: none; margin: 10px; }
  .pin-form .pin-key, .pin-form .pin-note { width: 100%; }
  .fix-form .fix-cascade { grid-template-columns: 1fr; }
  .auth-wrap { padding: 12px; min-height: calc(100vh - 100px); }
  .auth-card { padding: 22px 18px 20px; }
  .account-layout { margin-top: 0; }
  .account-nav-item { padding: 6px 10px; }
  .row { flex-wrap: wrap; }
  .row-side { width: 100%; justify-content: flex-start; }
  .row-side .input-sm { width: 100%; }
}
@media (max-width: 640px) {
  /* headings that carry an inline note: the note drops to its own line */
  .section-title { flex-wrap: wrap; row-gap: 2px; }
  .section-title .muted { flex-basis: 100%; }
  .sec-tools { margin-left: 0; flex-basis: 100%; flex-wrap: wrap; row-gap: 6px; margin-top: 4px; }
  .pager-full { grid-template-columns: auto 1fr; }
  .pager-full .pager-side:first-child { display: none; }
}
@media (max-width: 640px) {
  /* search results: name on one line, context below it */
  .omni-item { flex-wrap: wrap; row-gap: 2px; }
  .omni-item .primary { flex: 1 1 auto; }
  .omni-item .context { flex-basis: 100%; white-space: normal; }
}
@media (max-width: 640px) {
  /* iOS zooms into any field under 16px on focus: keep every field at 16px */
  #omni-input, .input, .run-input, select, input[type="text"], input[type="email"],
  input[type="password"], input[type="search"], textarea { font-size: 16px; }
  #omnibox.open #omni-input { font-size: 16px; }
  #omni-results { max-height: min(55vh, 420px); }
  .omni-item .primary { font-size: 15px; }

  /* Wide tables scroll sideways inside their frame; the first column
     stays pinned so a row keeps its name, and the edges shade to say
     there is more to the side. */
  .tbl-wrap {
    background:
      linear-gradient(90deg, var(--bg-panel) 30%, var(--bg-panel-0)) left / 40px 100% no-repeat local,
      linear-gradient(-90deg, var(--bg-panel) 30%, var(--bg-panel-0)) right / 40px 100% no-repeat local,
      linear-gradient(90deg, var(--edge-shade), rgba(0, 0, 0, 0)) left / 16px 100% no-repeat scroll,
      linear-gradient(-90deg, var(--edge-shade), rgba(0, 0, 0, 0)) right / 16px 100% no-repeat scroll,
      var(--bg-panel);
  }
  .tbl-wrap table { background: transparent; }
  .t-std th:first-child, .t-std td:first-child {
    position: sticky; left: 0; z-index: 1; background: var(--bg-panel);
    box-shadow: 1px 0 0 var(--border);
  }
  .t-std tbody tr:nth-child(even) td:first-child { background: var(--zebra-solid); }
  /* Pinned, the thumbnail column has its own divider, so the gap the
     wide layout borrows from the name cell's padding is on the wrong
     side of it: pad both sides and centre the box. */
  .units-tbl .u-thumb-h, .units-tbl .u-thumb-c { padding-right: 8px !important; }
  .units-tbl .u-thumb { margin: 0 auto; }
  .t-std td.flex, .t-std th.flex { min-width: 150px; }

  /* the two busiest tables drop columns that repeat or rarely matter */
  .products-tbl th:nth-child(2), .products-tbl td:nth-child(2),
  .products-tbl th:nth-child(3), .products-tbl td:nth-child(3) { display: none; }
  .designs-tbl th:nth-child(2), .designs-tbl td:nth-child(2),
  .designs-tbl th:nth-child(7), .designs-tbl td:nth-child(7) { display: none; }
  .sales-tbl th:nth-child(3), .sales-tbl td:nth-child(3),
  .sales-tbl th:nth-child(4), .sales-tbl td:nth-child(4) { display: none; }
  /* the title stays one clipped line here too; the table scrolls
     sideways and the full title rides on the cell's hover title */
  table.t-std.sales-tbl td.sales-title { min-width: 200px; }
  .sales-tbl th:first-child, .sales-tbl td:first-child { min-width: 92px; }
  .sales-tbl td:first-child { white-space: normal; font-size: 12px; }
}

/* ==== admin tooling: present in the markup, shown only to admins ======== */
body:not(.is-admin) .admin-only { display: none !important; }
body:not(.is-admin) .designs-tbl th:nth-child(7), body:not(.is-admin) .designs-tbl td:nth-child(7) { display: none; }

/* ==== pager (one style everywhere) ====================================== */
.pager { display: flex; align-items: center; justify-content: flex-end; gap: 8px; padding: 6px 2px; }
.pager-top { padding-top: 0; }
.pager-bottom { padding-bottom: 0; }
/* The full pager: page jumps centred, the page-size select at the right. */
.pager-full { display: grid; grid-template-columns: 1fr auto 1fr; }
.pager-nav { display: flex; align-items: center; gap: 6px; justify-content: center; }
.pager-side-r { display: flex; justify-content: flex-end; }
.pager-num {
  min-width: 26px; padding: 2px 6px; border-radius: 6px; line-height: 1.5; text-align: center;
  border: 1px solid transparent; background: none; color: var(--text-dim);
  font-size: 12px; cursor: pointer; font-family: inherit; font-variant-numeric: tabular-nums;
}
.pager-num:hover { color: var(--text); background: var(--bg); }
.pager-num.active { color: var(--accent); background: var(--accent-soft); border-color: var(--accent-border); font-weight: 600; cursor: default; }
.pager-gap { color: var(--text-dim); font-size: 12px; padding: 0 2px; user-select: none; }
.pager-per { display: flex; align-items: center; gap: 6px; color: var(--text-dim); font-size: 12px; }
.pager-per select {
  padding: 2px 20px 2px 6px; font-size: 12px; line-height: 16px;
  background-position: right 6px center;
}
.paging { opacity: .6; }

/* Catalog filters: the two selects and the search box share the full
   row (no count, no spacer) — the search box takes the larger share. */
.filter-bar.fill > select, .filter-bar.fill > input.f-search { flex: 1 1 0; min-width: 0; }
.filter-bar.fill > input.f-search { flex: 2 1 0; }
.badge.ok { color: var(--green); border-color: var(--green-border); }
.catalog-filters { margin-top: 22px; }
/* Document links in the product panel: label first, icon after. */
.src-link.doc-link { margin-left: 0; margin-right: 8px; gap: 3px; }

/* ---- image resolver (admin) ---------------------------------------
   The queue reuses the outliers page's furniture - seg filters, a
   server-paged list, two lines a row - so the two admin queues read as
   the same kind of screen. */

.res-list { display: flex; flex-direction: column; gap: 8px; }
.res-row {
  border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--bg-panel); padding: 10px 12px;
}
.res-l1 { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.res-l2 { font-size: 12px; margin-top: 4px; }
.res-l2 .sep { margin: 0 6px; opacity: 0.5; }
.res-card { font-weight: 600; }
.res-num {
  font-family: ui-monospace, Consolas, monospace;
  font-variant-numeric: tabular-nums; color: var(--text-dim); font-size: 12px;
}
.res-run {
  font-family: ui-monospace, Consolas, monospace; font-size: 11px;
  color: var(--text-dim);
}
.res-actions { margin-left: auto; }
.badge.ok { color: var(--green); border-color: var(--green-border); }

/* ---- bulk review: a grid of staged units, each marked keep or reject -
   The pictures open the inspect view; the Keep / Reject pair is the only
   thing that changes a mark. A tile marked reject fades, so what the
   confirm will drop reads at a glance. */
.bulk-head {
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px 16px;
  margin: 4px 0 12px;
}
.bulk-count b { font-variant-numeric: tabular-nums; }
.bulk-head-btns { display: flex; flex-wrap: wrap; gap: 8px; margin-left: auto; }
.bulk-grid {
  display: grid; gap: 10px;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
}
.bulk-tile {
  display: flex; flex-direction: column; gap: 6px;
  border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--bg-panel); padding: 8px;
}
.bulk-tile.off .bulk-faces, .bulk-tile.off .bulk-cap { opacity: 0.4; }
.bulk-faces {
  display: block; padding: 0; border: 0; background: none; cursor: zoom-in;
}
/* The picture's box, so a landscape one can be turned inside it. */
.bulk-shot {
  display: block; position: relative; overflow: hidden;
  background: var(--bg); border-radius: 2px;
}
.bulk-faces .bulk-shot { width: 100%; aspect-ratio: 5 / 7; }
.bulk-faces .bulk-shot.landscape { aspect-ratio: 7 / 5; }
.bulk-shot img { display: block; width: 100%; height: 100%; object-fit: contain; }
.bulk-cap { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 6px; font-size: 12px; }
.bulk-where { flex-basis: 100%; }
.bulk-where .sep, .bulk-inspect-meta .sep { margin: 0 6px; opacity: 0.5; }
.bulk-pick { display: flex; margin-top: auto; }
.bulk-pick button {
  position: relative;
  flex: 1; padding: 5px 0; font: inherit; font-size: 12px; cursor: pointer;
  border: 1px solid var(--border); background: var(--bg); color: var(--text-dim);
}
.bulk-pick button:first-child { border-radius: var(--radius) 0 0 var(--radius); }
/* The two share one line by overlapping it, not by dropping a border:
   whichever is on is drawn above, so its coloured edge shows whole. */
.bulk-pick button:last-child { border-radius: 0 var(--radius) var(--radius) 0; margin-left: -1px; }
.bulk-pick button.on { z-index: 1; }
.bulk-pick .bulk-pick-keep.on {
  color: var(--green); border-color: var(--green-border); font-weight: 600;
}
.bulk-pick .bulk-pick-reject.on {
  color: var(--orange); border-color: var(--orange-border); font-weight: 600;
}
.bulk-pick button:disabled { cursor: default; }
.bulk-inspect-faces { display: flex; justify-content: center; }
.bulk-inspect-faces figure { margin: 0; max-width: 100%; text-align: center; }
.bulk-inspect-faces .bulk-shot { height: 70vh; max-width: 100%; aspect-ratio: 5 / 7; }
/* A definite width: the turned picture is absolutely placed, so it
   gives the shrink-to-fit figure no width of its own to resolve 100%
   against. 70vh wide keeps the card the size a portrait one is tall. */
.bulk-inspect-faces .bulk-shot.landscape { height: auto; width: min(70vh, 80vw); aspect-ratio: 7 / 5; }
.bulk-inspect-faces figcaption {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 6px;
  font-size: 12px; color: var(--text-dim); margin-top: 6px;
}
.bulk-inspect-faces figcaption:empty { display: none; }
.bulk-inspect-bar {
  display: flex; align-items: center; gap: 10px; margin: 12px 0 8px;
}
.bulk-inspect-bar .bulk-pick { flex: 1; max-width: 320px; margin: 0 auto; }
.bulk-inspect-bar .bulk-pick button { padding: 8px 0; font-size: 14px; }
.bulk-inspect-meta { font-size: 12px; }
.bulk-closed { margin-top: 16px; }
.bulk-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; margin-top: 12px; }
.bulk-submit { display: flex; flex-direction: column; gap: 10px; }
.bulk-submit label { display: flex; flex-direction: column; gap: 4px; font-size: 12px; color: var(--text-dim); }
.bulk-submit .run-input { width: 100%; }
.bulk-tools { display: flex; gap: 8px; margin-left: auto; }
.key-made {
  display: flex; flex-direction: column; gap: 6px; margin-bottom: 14px; padding: 10px 12px;
  border: 1px solid var(--green-border); border-radius: var(--radius); background: var(--bg-panel);
}
.key-made .key-text { width: 100%; font-family: ui-monospace, Consolas, monospace; }
.key-new { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-bottom: 12px; }
.key-new #ak-name { flex: 1 1 260px; }
.key-new #ak-days { width: 5em; }

/* Invitations (admin): codes are typed by people, so they read in a
   fixed-width face; a code's own page is its counts, its facts, the
   chart of accounts it let in and the list of them. */
.iv-code { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-weight: 600; letter-spacing: 0.04em; white-space: nowrap; }
.iv-new #iv-note { flex: 1 1 260px; }
.iv-new #iv-max, .iv-new #iv-days { width: 7em; }
.iv-head { display: flex; align-items: center; flex-wrap: wrap; gap: 8px 12px; margin: 4px 0 14px; }
.iv-big { font-size: 20px; }
.iv-facts { display: grid; grid-template-columns: max-content minmax(0, 1fr); gap: 4px 16px; margin: 14px 0 0; font-size: 13px; }
.iv-facts dt { color: var(--text-dim); }
.iv-facts dd { margin: 0; overflow-wrap: anywhere; }
.iv-h { font-size: 14px; font-weight: 600; margin: 22px 0 8px; }
.iv-chart { position: relative; min-width: 0; }
.flag-actions .mini-btn.danger { color: var(--red); border-color: var(--red-border); }

/* An on/off switch (Settings -> Privacy): a pill with a knob, the brand
   colour when on. A real button with role="switch" underneath. */
.switch { position: relative; flex: none; width: 40px; height: 22px; padding: 0; border-radius: 11px;
  border: 1px solid var(--border-strong); background: var(--bg-hover, var(--row-border)); cursor: pointer;
  transition: background 0.15s, border-color 0.15s; }
.switch span { position: absolute; top: 2px; left: 2px; width: 16px; height: 16px; border-radius: 50%;
  background: var(--bg-panel); box-shadow: 0 1px 2px rgba(0, 0, 0, 0.25); transition: transform 0.15s; }
.switch.on { background: var(--accent); border-color: var(--accent); }
.switch.on span { transform: translateX(18px); }
.switch:disabled { opacity: 0.6; cursor: default; }
.switch:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Usage (admin): the feature table carries a bar of members per feature
   against the busiest one, so the order reads at a glance. */
.us-table .num { text-align: right; font-variant-numeric: tabular-nums; }
.us-bar-cell { width: 28%; min-width: 80px; }
.us-bar { display: block; height: 8px; border-radius: 4px; background: var(--accent); }
.badge.dim-badge { color: var(--text-dim); opacity: 0.75; }

/* ---- the sets, and the chosen set's cards ---------------------------
   Sets down the left with how many each has waiting; the chosen set's
   cards on the right. The list is the navigation, so it is a column of
   links, not a table. */

.res-split {
  display: grid; grid-template-columns: minmax(220px, 280px) minmax(0, 1fr);
  gap: 20px; align-items: start;
}
.res-sets {
  display: flex; flex-direction: column; gap: 1px; padding: 6px;
  border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--bg-panel);
}
.res-set-p {
  display: flex; align-items: center; gap: 6px; width: 100%; text-align: left;
  background: none; border: 0; padding: 6px 8px; border-radius: 4px;
  color: var(--text); font: inherit; font-size: 13px; font-weight: 600; cursor: pointer;
}
.res-set-p:hover { background: var(--bg-hover); }
.res-set-p .chev { flex: none; color: var(--text-dim); transition: transform .12s; }
.res-set.open > .res-set-p .chev { transform: rotate(90deg); }
/* Names wrap rather than clip: every product in a season starts with
   the same seven characters, so an ellipsis eats the part that differs. */
.res-set-name { flex: 1 1 auto; min-width: 0; line-height: 1.3; }
.res-n {
  flex: none; font-size: 11px; font-variant-numeric: tabular-nums; color: var(--text-dim);
  border: 1px solid var(--border); border-radius: 999px; padding: 0 6px; line-height: 16px;
}
.res-set-ds { display: flex; flex-direction: column; gap: 1px; padding: 0 0 6px 16px; }
/* A set's link and its gear share a row. The gear is always drawn, at
   the same quiet weight the tables use: hidden-until-hover read as
   "only the chosen set has one". */
.res-set-drow { display: flex; align-items: center; }
.res-set-drow .res-set-d { flex: 1 1 auto; min-width: 0; }
.res-set-drow .cfg-btn { margin-left: 2px; }
.res-set-drow:hover .cfg-btn, .res-set-drow .res-set-d.active + .cfg-btn { opacity: 0.9; }
.res-actions { display: inline-flex; align-items: center; gap: 4px; }
.res-set-d {
  display: flex; align-items: center; gap: 6px; padding: 4px 8px; border-radius: 4px;
  font-size: 13px; color: var(--text-dim); text-decoration: none;
}
.res-set-d:hover { background: var(--bg-hover); color: var(--text); text-decoration: none; }
.res-set-d.active { background: var(--accent-soft); color: var(--accent); font-weight: 600; }
.res-set-d.active .res-n { color: var(--accent); border-color: var(--accent-border); }
.res-main-head { font-size: 13px; color: var(--text-dim); margin: 0 0 10px; }
.res-main-head b { color: var(--text); font-weight: 600; }
.res-main-head .sep { margin: 0 6px; opacity: 0.5; }
.res-counting { font-size: 12px; margin-left: auto; }
@media (max-width: 899px) {
  .res-split { grid-template-columns: 1fr; }
  .res-sets { max-height: 40vh; overflow-y: auto; }
}

/* ---- the workbench -------------------------------------------------
   Faces on the left because they are the thing being made; the sales
   list on the right because it is the reference you read while making
   it. On a phone they stack in that same order. */

.res-work { display: flex; flex-direction: column; gap: 18px; }
@media (min-width: 900px) {
  .res-work { flex-direction: row; align-items: flex-start; gap: 22px; }
  .res-faces { flex: 0 0 auto; }
  .res-sales { flex: 1 1 auto; min-width: 0; }
}

.res-slots { display: flex; gap: 12px; }
.res-slot {
  width: 190px; display: flex; flex-direction: column; gap: 6px;
}
/* The empty slot is the button: a dashed card you click to fill. */
.res-slot .res-slot-ph {
  width: 190px; aspect-ratio: 5 / 7; border-radius: 3px; padding: 8px;
  display: flex; flex-direction: column; align-items: center;
  justify-content: center; gap: 4px; text-align: center; cursor: pointer;
  background: var(--bg); border: 1px dashed var(--border);
  color: var(--text-dim); font: inherit; font-size: 12px;
  transition: border-color .12s, color .12s;
}
.res-slot .res-slot-ph:hover, .res-slot .res-slot-ph:focus-visible {
  border-color: var(--accent); color: var(--accent);
}
.res-ph-err { color: var(--orange); font-size: 12px; line-height: 1.4; }
.res-slot.filled .res-shot { border: 1px solid var(--border); }
.res-ph-label { font-size: 13px; color: var(--text); }
.res-slot-bar {
  display: flex; align-items: center; justify-content: space-between;
  gap: 8px; font-size: 12px; color: var(--text-dim);
}
.res-warn {
  margin-left: 6px; color: var(--orange); font-size: 11px;
  border: 1px solid var(--orange-border); border-radius: 999px; padding: 0 6px;
}
.res-confirm {
  display: flex; align-items: center; gap: 10px; margin-top: 14px;
  flex-wrap: wrap;
}
.res-confirm .dim { font-size: 12px; }
.mini-btn.primary { border-color: var(--accent-border); color: var(--text); }
.mini-btn.primary:disabled { opacity: 0.45; }

.res-sales-head { font-size: 13px; margin-bottom: 8px; }
.res-sales-list {
  display: flex; flex-direction: column; gap: 2px;
  max-height: 60vh; overflow-y: auto;
}
.res-sale {
  display: grid; gap: 10px; align-items: baseline; padding: 5px 6px;
  grid-template-columns: 82px 64px 72px minmax(0, 1fr) auto;
  border-radius: 3px; font-size: 12px;
}
.res-sale:nth-child(odd) { background: var(--bg-panel); }
.res-sale-date, .res-sale-price {
  font-family: ui-monospace, Consolas, monospace;
  font-variant-numeric: tabular-nums;
}
.res-sale-grade { color: var(--text-dim); }
.res-sale-title {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  color: var(--text-dim);
}
.res-sale-link { white-space: nowrap; }
@media (max-width: 620px) {
  .res-sale { grid-template-columns: 78px 1fr auto; }
  .res-sale-grade, .res-sale-title { display: none; }
}

/* ---- the three ways a photo gets in -------------------------------- */

.res-pick {
  /* above .overlay, which is 100: this opens from inside it */
  position: fixed; inset: 0; z-index: 110; display: flex;
  align-items: center; justify-content: center;
  background: rgba(0, 0, 0, 0.55); padding: 16px;
}
.res-pick-box {
  background: var(--bg-panel); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 16px; width: min(440px, 100%);
  display: flex; flex-direction: column; gap: 12px;
}
.res-pick-title { font-size: 15px; font-weight: 600; }
/* The drop zone is most of the box: paste and drop need no target, but
   a big dashed one says they are welcome. */
.res-pick-drop {
  display: flex; flex-direction: column; align-items: center; gap: 10px;
  padding: 22px 12px; border: 1px dashed var(--border); border-radius: var(--radius);
  background: var(--bg); color: var(--text-dim); font-size: 13px; text-align: center;
  transition: border-color .12s, background .12s;
}
.res-pick-drop.over { border-color: var(--accent); background: var(--accent-soft); }
.res-pick-or { display: flex; flex-wrap: wrap; justify-content: center; gap: 8px; }
.res-pick-url { display: flex; gap: 6px; }
.res-pick-url .run-input { flex: 1 1 auto; min-width: 0; text-align: left; }
.res-pick-foot { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.res-pick-foot .dim { font-size: 12px; min-width: 0; }
.mini-btn.file-btn { cursor: pointer; }
.mini-btn.file-btn input[type="file"] { display: none; }

/* ---- configuration (admin) ----------------------------------------- */
/* One control, wherever a configurable thing appears: on its own page
   beside its facts, and on the page above it beside its row. It stays
   quiet until hovered — an admin browsing the catalogue is usually
   reading it, not configuring it. */

.cfg-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 22px; height: 22px; padding: 0; margin-left: 6px;
  vertical-align: middle; border-radius: 4px; cursor: pointer;
  background: none; border: 1px solid transparent; color: var(--text-dim);
  opacity: 0.55; transition: opacity .12s, color .12s, border-color .12s;
}
.cfg-btn:hover, .cfg-btn:focus-visible {
  opacity: 1; color: var(--accent); border-color: var(--accent-border);
}
tr:hover > td > .cfg-btn { opacity: 0.9; }

/* The panel: sections divided by hairlines, each a small heading over
   aligned key / value rows. Keys share one column across every section
   so the eye runs down one edge; a value's source sits after it, dim. */
.cfg { display: flex; flex-direction: column; }
.cfg-sec { padding: 14px 0 16px; border-top: 1px solid var(--border); }
.cfg-sec:first-child { padding-top: 4px; border-top: 0; }
.cfg-h {
  display: flex; align-items: baseline; gap: 8px; margin-bottom: 10px;
  font-size: 11px; font-weight: 600; text-transform: uppercase;
  letter-spacing: 0.07em; color: var(--text-dim);
}
.cfg-h-sub { font-weight: 400; text-transform: none; letter-spacing: 0; opacity: 0.8; }
.cfg-kind {
  display: inline-block; margin-left: 8px; padding: 1px 7px; vertical-align: 2px;
  border: 1px solid var(--border); border-radius: 999px;
  font-size: 11px; font-weight: 500; color: var(--text-dim); text-transform: capitalize;
}
.cfg-rows { display: grid; grid-template-columns: 11ch minmax(0, 1fr); gap: 10px 14px; }
.cfg-row { display: contents; font-size: 13px; }
.cfg-k { color: var(--text-dim); line-height: 2em; }
.cfg-v { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; min-height: 2em; }
.cfg-v b { font-weight: 600; font-variant-numeric: tabular-nums; }
.cfg-src { font-size: 12px; color: var(--text-dim); }
.cfg-none { color: var(--text-dim); }
.cfg-inline-btn { margin-left: 2px; }
/* Controls sit at the start of the value column, sized to themselves:
   a grid item would otherwise stretch a select across the panel. */
.cfg-row > .cfg-in, .cfg-row > .cfg-num, .cfg-row > .cfg-run { justify-self: start; }
.cfg-in { width: auto; min-width: 18ch; height: 2em; line-height: 2em; }
.cfg-num { display: inline-flex; align-items: baseline; gap: 5px; }
.cfg-num .cfg-in { min-width: 15ch; }
.cfg-text { width: min(34ch, 100%); text-align: left; }
.cfg-note { width: min(46ch, 100%); height: auto; line-height: 1.4; padding: 4px 6px; resize: vertical; }
/* The stated run: the symbol and the number read as one control. */
.cfg-run { display: inline-flex; align-items: stretch; }
.cfg-run .run-input { height: 2em; line-height: 2em; }
.cfg-run-sym {
  width: 3.2em; text-align: center; text-align-last: center;
  border-top-right-radius: 0; border-bottom-right-radius: 0; cursor: pointer;
}
.cfg-run-num { width: 9ch; border-left: 0; border-top-left-radius: 0; border-bottom-left-radius: 0; }
.cfg-run-num::placeholder { color: var(--text-dim); text-align: left; }
.cfg-shots { display: flex; gap: 12px; flex-wrap: wrap; margin-bottom: 12px; }
.cfg-shots figure { margin: 0; display: flex; flex-direction: column; gap: 5px; }
.cfg-shots figcaption { font-size: 11px; color: var(--text-dim); }
.cfg-err { font-size: 12px; color: var(--orange); grid-column: 2; }

/* ---- pinning the four corners by hand ------------------------------ */
/* Opens from inside the overlay, like .res-pick, so it sits above it.
   The stage is as big as the window allows, because the whole point is
   seeing where the card's edge actually is. */

.pin-lay {
  position: fixed; inset: 0; z-index: 110; display: flex;
  align-items: center; justify-content: center;
  background: rgba(0, 0, 0, 0.72); padding: 16px;
}
/* A grid rather than a flex column so the middle row has a DEFINITE
   height: the photo is sized with max-height:100% and there is no
   "header + footer + padding" constant to keep in sync with the markup,
   which is the kind of number that is wrong on the first phone. */
.pin-box {
  background: var(--bg-panel); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 14px;
  display: grid; grid-template-rows: auto minmax(0, 1fr) auto; gap: 12px;
  max-width: min(1100px, 100%); max-height: calc(100vh - 32px);
}
.pin-head { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.pin-h1 { font-size: 15px; font-weight: 600; }
.pin-head .dim { font-size: 12px; }
/* The loupe lives in the header, not under the cursor: a magnifier that
   follows the pointer covers the very corner being placed. */
.pin-loupe {
  margin-left: auto; width: 132px; height: 132px; flex: none;
  border: 1px solid var(--border); border-radius: 4px;
  background: var(--bg) no-repeat; background-size: auto;
  opacity: 0; transition: opacity .1s;
  position: relative; overflow: hidden;
}
.pin-loupe.on { opacity: 1; }
/* Crosshair on the loupe's centre — the point being dragged. */
.pin-loupe::before, .pin-loupe::after {
  content: ""; position: absolute; background: var(--accent);
}
.pin-loupe::before { left: 50%; top: 0; bottom: 0; width: 1px; margin-left: -0.5px; }
.pin-loupe::after { top: 50%; left: 0; right: 0; height: 1px; margin-top: -0.5px; }

/* The stage must be EXACTLY the photo's box — the outline and the four
   handles are positioned against it — so it hugs the image and cannot
   also be told how big to be. `.pin-fit` is the one that takes the grid
   row, and app.js caps the image to what it measures there: a percentage
   max-height on the image would resolve against the hugging parent's
   auto height and be ignored. */
.pin-fit {
  align-self: stretch; justify-self: stretch; min-width: 0; min-height: 0;
  display: flex; align-items: center; justify-content: center;
}
.pin-stage {
  position: relative; display: inline-block; line-height: 0; max-width: 100%;
}
.pin-stage img {
  display: block; width: auto; height: auto;
  user-select: none; -webkit-user-drag: none;
}
.pin-svg { position: absolute; inset: 0; width: 100%; height: 100%; }
.pin-svg polygon {
  fill: rgba(56, 132, 255, 0.14); stroke: var(--accent); stroke-width: 2;
  vector-effect: non-scaling-stroke;
}
/* The side that will be the cut card's top (the editor's Rotate). */
.pin-svg line {
  stroke: var(--pink); stroke-width: 6; stroke-linecap: round;
  vector-effect: non-scaling-stroke;
}
#pin-turn[aria-pressed="true"] { border-color: var(--accent-border); color: var(--accent); }
.pin-h {
  position: absolute; width: 22px; height: 22px; margin: -11px 0 0 -11px;
  border-radius: 50%; padding: 0; cursor: grab; touch-action: none;
  background: var(--bg-panel); border: 2px solid var(--accent);
  box-shadow: 0 1px 4px rgba(0, 0, 0, .5);
}
.pin-h::after {
  content: ""; position: absolute; inset: 6px;
  border-radius: 50%; background: var(--accent);
}
.pin-h:hover { transform: scale(1.15); }
/* :focus, not :focus-visible — the selected corner has to LOOK selected
   after a mouse drag too, because it is the one the arrow keys will
   nudge and the one the magnifier is showing. */
.pin-h:focus {
  outline: none; transform: scale(1.2);
  box-shadow: 0 0 0 3px var(--bg-panel), 0 0 0 5px var(--accent);
}
.pin-h.drag { cursor: grabbing; }
.pin-foot { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
/* The corner benchmark's tags: nine picture tiles in one row, the word
   under each drawing, a pressed one in the accent colour. */
.pin-tags { display: grid; grid-template-columns: repeat(9, minmax(0, 1fr)); gap: 6px; }
.pin-tags button {
  font: inherit; font-size: 11px; color: var(--text); cursor: pointer;
  background: var(--bg-panel); border: 1px solid var(--border); border-radius: 8px;
  padding: 6px 2px 4px; display: grid; justify-items: center; gap: 3px; min-width: 0;
}
.pin-tags button span { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%; }
.pin-tags svg { width: 34px; height: 34px; }
.pin-tags button:hover { border-color: var(--accent-border); }
.pin-tags button[aria-pressed="true"] {
  color: var(--accent); border-color: var(--accent-border); background: var(--accent-soft);
}
@media (max-width: 560px) { .pin-tags { grid-template-columns: repeat(5, minmax(0, 1fr)); } }
.pin-foot .dim { font-size: 12px; }

/* The filled slot's two actions share one line and wrap before clipping. */
.res-slot-acts { display: flex; gap: 6px; flex-wrap: wrap; justify-content: flex-end; }
.res-slot-bar { flex-wrap: wrap; }

/* ---- a card nobody has photographed -------------------------------- */

/* Everything inside scales with --card-w, the width app.js fits the real
   card to. At a fixed 46px the drawing and the two lines read as a
   caption stranded in the middle of a 340px card; the block has to look
   like a card standing in for a card, at whatever size the window gives
   it. The clamps are the floor and ceiling: the phone layout has no
   --card-w at all and falls back to the smaller end. */
.cp-noimg {
  --ph: var(--card-w, 240px);
  gap: clamp(6px, calc(var(--ph) * 0.03), 12px);
}
.cp-noimg svg {
  width: clamp(46px, calc(var(--ph) * 0.2), 84px); height: auto;
  fill: none; stroke: currentColor; stroke-width: 2.4;
  stroke-linejoin: round; stroke-linecap: round; opacity: 0.45;
}
.cp-noimg-t {
  font-size: clamp(13px, calc(var(--ph) * 0.048), 18px); color: var(--text);
}
.cp-noimg-s {
  font-size: clamp(11px, calc(var(--ph) * 0.036), 14px);
  max-width: 24ch; line-height: 1.45;
}
/* The invitation lives inside the card's own outline, so it costs the
   placeholder nothing: a strip underneath would take its height out of
   the same box the card is measured against. */
.cp-noimg-act { margin-top: 4px; }
/* The placeholder is sized from the window, so on a short one it can be
   asked to hold more than fits. The drawing is the part that gives: it
   is decoration, and the words and the button are what the block is
   for. */
.cp-noimg { overflow: hidden; }
.cp-noimg svg { flex: 0 1 auto; min-height: 0; }

/* ---- a reader offers a photograph ---------------------------------- */
/* Their own picture of the front, shown back as it was chosen. Nothing
   the pipeline made appears here: what they are confirming is "this is
   my card", not "this crop is right" - that is the reviewer's judgement
   and theirs alone. */

.off-form { display: flex; flex-direction: column; gap: 14px; }
.off-slots { display: flex; justify-content: center; }
/* One card-shaped slot, centred: at the dialog's full width a 5:7 box
   would be taller than most screens. */
.off-slot {
  flex: 0 1 220px; min-width: 0;
  display: flex; flex-direction: column; gap: 6px; align-items: stretch;
}
.off-shot {
  width: 100%; aspect-ratio: 5 / 7; border-radius: 3px; background: var(--bg);
  border: 1px dashed var(--border); display: flex; align-items: center;
  justify-content: center; overflow: hidden;
}
.off-shot img { width: 100%; height: 100%; object-fit: cover; display: block; }
.off-ph { font-size: 12px; color: var(--text-dim); }
.off-note { width: 100%; height: 2em; line-height: 2em; text-align: left; }
.off-actions { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.off-actions .dim { font-size: 12px; max-width: 42ch; line-height: 1.45; }

/* ---- reviewing what a reader sent ---------------------------------- */
/* The raw photographs beside the processed faces: the processed face is
   the only thing that can be wrong, and the raw one is the only way to
   tell. */

.sub-raws { display: flex; gap: 12px; flex-wrap: wrap; }
.sub-raw { margin: 0; display: flex; flex-direction: column; gap: 6px; }
/* The raw photo keeps ITS shape, not the card's: a phone shot of a card
   on a table is landscape, and forcing it into a 5:7 box would letterbox
   away the half of the frame the reviewer is judging. */
.sub-raw img {
  width: 240px; height: auto; max-height: 320px; object-fit: contain;
  display: block; border: 1px solid var(--border); border-radius: 3px;
  background: var(--bg);
}
.sub-raw-missing {
  width: 240px; aspect-ratio: 5 / 7; border: 1px dashed var(--border);
  border-radius: 3px; background: var(--bg);
  display: flex; align-items: center; justify-content: center; font-size: 12px;
}
.sub-raw figcaption {
  display: flex; align-items: center; gap: 6px; font-size: 12px;
  color: var(--text-dim);
}

/* ---- the team page's depth chart -----------------------------------
   Five positions down, five slots across. The fill is an ordered ramp
   on one hue and not five unrelated colours, because rotation status is
   a ladder: a starter plays more than a key rotation player plays more
   than a development one. A cell with no reading is left on the panel's
   own surface, which is the one thing a ramp cannot be confused with. */
.dc-meta { color: var(--text-dim); font-size: 12px; margin: -6px 0 10px; }
/* ?at=depth scrolls here: clear of the sticky bar (and the env banner). */
#tm-depth { scroll-margin-top: 70px; }
body.has-env-banner #tm-depth { scroll-margin-top: 96px; }
table.depth-chart { table-layout: fixed; width: 100%; min-width: 620px;
  border-collapse: separate; border-spacing: 4px; }
table.depth-chart th { font-size: 11px; text-transform: uppercase;
  letter-spacing: 0.05em; color: var(--text-dim); font-weight: 600;
  border: 0; background: none; padding: 2px 4px; }
/* The corner cell sizes the label column: table-layout: fixed reads the
   FIRST row, so a width on the body's own th would come too late and
   the five slot columns would not be equal. */
table.depth-chart thead th:first-child, table.depth-chart th.dc-pos { width: 46px; }
table.depth-chart th.dc-pos { text-align: left; vertical-align: middle; }
table.depth-chart td.dc-cell {
  padding: 0; border: 1px solid var(--border); border-radius: 6px;
  background: var(--bg-panel); vertical-align: top;
}
table.depth-chart td.dc-cell.empty { background: var(--zebra); border-style: dashed; }
.dc-cell a, .dc-cell > .dc-name {
  display: block; padding: 7px 9px; color: inherit; text-decoration: none;
}
.dc-cell a:hover { text-decoration: none; filter: brightness(1.06); }
.dc-name { display: block; font-size: 13px; font-weight: 600; line-height: 1.25;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dc-min { display: block; font-size: 11px; color: var(--text-dim);
  font-variant-numeric: tabular-nums; margin-top: 2px; }
.dc-inj { margin-left: 6px; font-size: 10px; font-weight: 700; letter-spacing: 0.04em;
  color: var(--orange); }
.dc-inj.out { color: var(--red); }

/* The ramp. `on-accent` text only where the fill is strong enough to
   need it; the lighter rungs keep the page's own text colour. Written
   at the same specificity as the .dc-cell base rule above, which also
   names an element and a class and would otherwise win the background.
   The swatches in the legend are not cells, so they take the class on
   its own and the two selectors are both spelled out. */
table.depth-chart td.rs-starter, i.dc-sw.rs-starter {
  background: var(--accent); border-color: var(--accent); color: var(--on-accent);
}
.rs-starter .dc-min, .rs-starter .dc-inj { color: var(--on-accent); opacity: 0.85; }
table.depth-chart td.rs-key-rotation, i.dc-sw.rs-key-rotation {
  background: color-mix(in srgb, var(--accent) 34%, var(--bg-panel));
  border-color: var(--accent-border);
}
table.depth-chart td.rs-rotation, i.dc-sw.rs-rotation {
  background: color-mix(in srgb, var(--accent) 16%, var(--bg-panel));
}
table.depth-chart td.rs-development, i.dc-sw.rs-development {
  background: color-mix(in srgb, var(--accent) 6%, var(--bg-panel));
}
table.depth-chart td.rs-none, table.depth-chart td.rs-unknown,
i.dc-sw.rs-unknown { background: var(--bg-panel); }

/* The same player, listed deeper here than at the position their status
   came from. Faded and stroked, so the eye finds the real listing first
   and the repeat still reads as the chart's own. */
.dc-cell.dc-alt { opacity: 0.55; border-style: dashed; }
.dc-cell.dc-alt .dc-name { font-style: italic; font-weight: 500; }
i.dc-sw.dc-alt { opacity: 0.55; border-style: dashed; }

.dc-legend { display: flex; flex-wrap: wrap; gap: 6px 16px; margin: 8px 0 4px;
  font-size: 11px; color: var(--text-dim); }
.dc-key { display: inline-flex; align-items: center; gap: 6px; }
.dc-sw { width: 12px; height: 12px; border-radius: 3px; border: 1px solid var(--border); }

/* The team page's draft picks: a column per draft and a row per round.
   A box is one round of one draft: on top what becomes of the team's
   own pick, under it the picks coming in. A grid rather than a table's
   own layout, so a box can be two stacked parts of any height. */
table.picks-grid { display: grid; grid-template-columns: 74px repeat(7, minmax(0, 1fr));
  gap: 6px 4px; width: 100%; min-width: 700px; padding: 4px; }
table.picks-grid thead, table.picks-grid tbody, table.picks-grid tr { display: contents; }
table.picks-grid th { font-size: 11px; text-transform: uppercase;
  letter-spacing: 0.05em; color: var(--text-dim); font-weight: 600;
  border: 0; background: none; padding: 2px 4px; text-align: left; }
table.picks-grid th.pk-row { display: flex; align-items: center; }
table.picks-grid td.pk-box {
  display: flex; flex-direction: column; padding: 0; overflow: hidden; min-height: 76px;
  border: 1px solid var(--border); border-radius: 7px; background: var(--bg-panel);
}
.pk-top { position: relative; padding: 7px 9px; border-bottom: 1px solid var(--border); }
.pk-in { position: relative; padding: 7px 9px 8px; flex: 1; }
/* Nothing coming in: the part is there, and out of use. */
.pk-in.pk-nil { background: var(--zebra); }
.pk-line { display: block; }
.pk-line + .pk-line { margin-top: 5px; }
.pk-main { display: block; font-size: 13px; font-weight: 600; line-height: 1.25; }
.pk-sub { display: block; font-size: 11px; color: var(--text-dim); margin-top: 1px; }
.pk-slot { margin-left: 6px; font-size: 11px; font-weight: 500; color: var(--text-dim); }
/* A figure that opens the sum it came from, on hover or a press. */
.pk-calc { cursor: help; text-decoration: underline dotted; text-underline-offset: 2px; }
/* A kept pick is a little green; red is one that is owed, orange one
   that is owed only if terms are met. */
.pk-top.pk-own, i.dc-sw.pk-own {
  background: color-mix(in srgb, var(--green-soft) 65%, var(--bg-panel));
}
i.dc-sw.pk-own { border-color: var(--green-border); }
.pk-top.pk-owed, i.dc-sw.pk-owed { background: var(--red-soft); border-color: var(--red-border); }
.pk-top.pk-cond, i.dc-sw.pk-cond { background: var(--orange-soft); border-color: var(--orange-border); }
/* A pick that will not be made: struck out, and hatched so it is not
   taken for a part with nothing in it. */
.pk-top.pk-void, i.dc-sw.pk-void {
  background: repeating-linear-gradient(135deg, var(--zebra) 0 4px, var(--bg-panel) 4px 8px);
  color: var(--text-dim);
}
.pk-void .pk-main { text-decoration: line-through; font-weight: 500; }
/* A pick coming in is a plus: green, or orange while it depends on terms. */
.pk-plus, .pk-swap { position: relative; padding-left: 15px; }
.pk-plus::before, .pk-swap::before { content: '+'; position: absolute; left: 0; top: -1px;
  font-size: 13px; font-weight: 700; color: var(--green); }
.pk-swap::before { content: '⇄'; top: 1px; font-size: 11px; color: var(--text-dim); }
.pk-plus.pk-maybe::before { color: var(--orange); }
.pk-legend { margin: 0 0 8px; }
.pk-mark { font-weight: 700; color: var(--green); }
.pk-mark.pk-maybe { color: var(--orange); }
/* The admin's pencil: in the cell's corner, shown on hover, and always
   on a cell that was restated (orange once the source has moved on). */
.pk-pen { position: absolute; top: 2px; right: 2px; width: 18px; height: 18px; padding: 0;
  border: 0; border-radius: 4px; background: none; color: var(--text-dim); font-size: 11px;
  line-height: 18px; cursor: pointer; opacity: 0; }
.pk-top:hover .pk-pen, .pk-in:hover .pk-pen, .pk-pen:focus-visible,
.pk-edited .pk-pen { opacity: 1; }
.pk-pen:hover { background: var(--accent-soft); color: var(--accent); }
.pk-edited .pk-pen { color: var(--accent); }
.pk-pen.warn { color: var(--orange); }
.pk-edit { margin-top: 12px; border: 1px solid var(--border); border-radius: 8px; padding: 10px 12px; }
.pk-edit-h { font-weight: 600; margin-bottom: 6px; }
.pk-note { font-size: 13px; color: var(--text-dim); margin-bottom: 8px; }
.pk-note.warn { color: var(--orange); }
.pk-tbl select.run-input { width: auto; text-align: left; }
.pk-tbl input.run-input { width: 4.5em; text-align: left; }
.pk-tbl input.pk-terms { width: 100%; }

@media (max-width: 720px) {
  table.depth-chart thead th:first-child, table.depth-chart th.dc-pos { width: 34px; }
  .dc-name { font-size: 12px; }
}

/* Sales import: a paste target, then the rows it read. */
.import-form { display: grid; grid-template-columns: minmax(0, 1fr); gap: 6px; margin-bottom: 14px; }
.import-paste { border: 2px dashed var(--border); background: var(--bg-panel); padding: 22px 16px; text-align: center; color: var(--text-dim); cursor: text; outline: none; }
.import-paste:focus { border-color: var(--accent); color: var(--text); }
.import-summary { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 14px; margin: 6px 0 10px; font-size: 13px; }
.import-actions { margin-left: auto; display: flex; flex-wrap: wrap; gap: 6px; }
.import-table tr.import-off td { color: var(--text-dim); }
/* Store Watch: the store's pages as pasted, and what is held of them. */
.store-how { margin: 0 0 8px; font-size: 13px; color: var(--text-dim); }
.store-held { margin-bottom: 14px; }
.store-held-head { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 12px; margin: 0 0 6px; font-size: 13px; }
.store-held-head b { font-size: 14px; }
.store-held-head .mini-btn { margin-left: auto; }
.store-held-head .mini-btn + .mini-btn { margin-left: 0; }
.store-held-head .mini-btn.armed { color: var(--red); border-color: var(--red-border); background: var(--red-soft); }
.import-table td.import-title { white-space: normal; min-width: 220px; overflow-wrap: anywhere; }
.import-table td.import-card { white-space: normal; font-size: 12px; overflow-wrap: anywhere; min-width: 160px; }
@media (max-width: 640px) {
  .import-table .import-minor { display: none; }
  .import-table td.import-title, .import-table td.import-card { min-width: 0; }
}
.import-done { margin: 6px 0 4px; }
.import-worksum { font-size: 12px; }
.work-players tr.work-player { cursor: pointer; }
.work-players td.work-chev { width: 1%; color: var(--text-dim); }
.work-players tr.work-days > td { padding: 0 0 10px 28px; background: var(--bg); }
.work-days-box .work-day-table { margin-top: 4px; }

/* ---- The player card (app.js playerCardHtml) --------------------------
   One panel, row by row: identity; the unit page's radar and role gauge
   side by side (their own .ab-* styles); availability and load; the
   contract beside its salaries; the injury listed now beside the record;
   the trophies. Bars are the accent and nothing else; an injury wears
   the unit page's badge. */
.pcd { background: var(--bg-panel); border: 1px solid var(--border); border-radius: 12px; overflow: hidden; }
.pcd-sec { padding: 16px 18px; min-width: 0; }
.pcd-sec + .pcd-sec, .pcd-sec + .pcd-pair, .pcd-pair + .pcd-sec, .pcd-pair + .pcd-pair {
  border-top: 1px solid var(--border);
}
.pcd-h {
  display: flex; align-items: center; min-height: 22px;
  margin: 0 0 10px; font-size: 11px; font-weight: 600; letter-spacing: 0.06em;
  text-transform: uppercase; color: var(--text-dim);
}
/* A heading's pager (the injury log's), right-set and in plain case,
   and the log's status filter beside the title, a size down from the
   page's own segmented controls. */
.pcd-h .pager-mini { letter-spacing: 0; text-transform: none; }
/* The row's headings hold the pager's height (its 24 px buttons) whether
   it shows or not: a filter that fits on one page drops the pager, and
   the log must not move up under it. Both sides, so they stay level. */
.pcd-pair-inj .pcd-h { min-height: 24px; }
.pcd-inj-filter { margin-left: 12px; letter-spacing: 0; text-transform: none; font-weight: 400; }
.pcd-inj-filter .seg { padding: 1px; }
.pcd-inj-filter .seg-item {
  padding: 1px 8px; font-size: 11.5px; gap: 5px; border: 0; background: none;
  cursor: pointer; font-family: inherit; text-transform: none;
}
/* lit by colour, not weight: a bold label is wider and nudges its neighbours */
.pcd-inj-filter .seg-item.active { background: var(--accent-soft); font-weight: 400; }
.pcd-inj-filter .seg-n { font-size: 10.5px; line-height: 14px; padding: 0 5px; }
.pcd-log th.sortable .sort-head { gap: 3px; }
.pcd .num { font-variant-numeric: tabular-nums; }

/* The identity block: the headshot flush to the card's corner and down
   the block's whole height, beside the name and its labelled grid. */
.pcd-id-sec { padding: 0; }
.pcd-id { display: flex; align-items: stretch; }
.pcd-id .pcd-shot {
  flex: none; align-self: stretch; width: 190px; height: auto; min-height: 139px;
  object-fit: cover; object-position: top center;
  border: 0; border-right: 1px solid var(--border); border-radius: 0;
  background: var(--bg-hover);
}
.pcd-id-txt { min-width: 0; flex: 1; padding: 16px 18px; }

.pcd-v { font-size: 16px; font-weight: 700; }
.pcd-big { font-size: 28px; font-weight: 700; letter-spacing: -0.01em; line-height: 1.1; }
.pcd-s { font-size: 12px; color: var(--text-dim); margin-top: 4px; line-height: 1.4; }
.pcd-code { font-size: 12px; font-weight: 600; color: var(--text-dim); }

/* Injury status: the body figure. A region lights in its listing's
   level, GTD the warning colour, OUT the critical one and OFS the
   badge's crimson; the figure's tip and name give each listing and its
   designation in words, and the log beside it letters every designation. */
.pcd-hl { display: flex; justify-content: center; }
.pcd-body { width: 150px; height: auto; cursor: help; border-radius: 6px; }
.pcd-body:focus-visible { outline: 2px solid var(--accent-glow); outline-offset: 4px; }
.pcd-code.gtd { color: var(--orange); }
.pcd-code.out { color: var(--red); }
.pcd-code.ofs { color: var(--crimson); }

.pcd-track {
  position: relative; display: block; height: 8px; border-radius: 4px;
  background: var(--accent-faint); border: 1px solid var(--row-border);
}
.pcd-track > i { position: absolute; left: 0; top: 0; bottom: 0; border-radius: 4px; background: var(--accent); }
.pcd-track > b {
  position: absolute; left: 50%; top: -3px; bottom: -3px; width: 2px;
  background: var(--text-dim); opacity: 0.5;
}

.pcd-av {
  display: grid; grid-template-columns: 58px minmax(0, 1.4fr) minmax(0, 1fr) minmax(0, 1fr);
  gap: 8px 14px; align-items: center; font-size: 12.5px;
}
.pcd-av .h {
  font-size: 10.5px; font-weight: 600; letter-spacing: 0.06em;
  text-transform: uppercase; color: var(--text-dim);
}
.pcd-av .sn { font-weight: 600; }
.pcd-av .sn.on { color: var(--accent); }
/* A playoff run's row: the season it followed, and under it the word,
   small and dim, so the regular seasons stay the table's spine. */
.pcd-av .sn.po { font-weight: 500; line-height: 1.15; }
.pcd-av .sn.po small {
  display: block; font-size: 10px; font-weight: 500; color: var(--text-dim);
}
.pcd-av .sn.po.on small { color: inherit; }
.pcd-av .c { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.pcd-av .t { display: flex; justify-content: space-between; gap: 6px; }
.pcd-av .t i { font-style: normal; color: var(--text-dim); }
.pcd-gp { display: flex; height: 10px; gap: 2px; }
.pcd-gp > i, .pcd-gp > s { height: 100%; border-radius: 3px; }
.pcd-gp > i { background: var(--accent); }
.pcd-gp > s {
  box-sizing: border-box; border: 1px solid var(--border);
  background: repeating-linear-gradient(135deg, var(--border-strong) 0 2px, transparent 2px 5px);
}

/* Two sections to a row, the state on the left and its history on the
   right, each row split by what it holds: a salary line beside a chart
   of seasons, a figure 150 px wide beside a list. */
.pcd-pair { display: grid; }
.pcd-pair-ct { grid-template-columns: minmax(0, 1fr) minmax(0, 2fr); }
.pcd-pair-inj { grid-template-columns: 250px minmax(0, 1fr); }
.pcd-pair > .pcd-sec + .pcd-sec { border-top: 0; position: relative; }
/* The split between the two, drawn inside the row's padding so it stops
   short of the lines above and below, as the radar's and the role
   gauge's does. */
.pcd-pair > .pcd-sec + .pcd-sec::before {
  content: ""; position: absolute; left: 0; top: 16px; bottom: 16px;
  width: 1px; background: var(--border);
}
.pcd-ct { display: flex; align-items: flex-end; gap: 6px; height: 110px; padding-top: 16px; }
.pcd-ct .b {
  flex: 1; min-width: 0; height: 100%;
  display: flex; flex-direction: column; align-items: center; justify-content: flex-end;
}
.pcd-ct .bar {
  position: relative; width: 100%; max-width: 38px;
  border-radius: 4px 4px 0 0; background: var(--border-strong);
}
.pcd-ct .b.now .bar { background: var(--accent); }
.pcd-ct .b.fa .bar {
  background: transparent; border: 1.5px dashed var(--border-strong);
  border-bottom: 0; box-sizing: border-box;
}
.pcd-ct .amt {
  position: absolute; bottom: 100%; left: 50%; transform: translateX(-50%);
  padding-bottom: 2px; font-size: 10.5px; font-weight: 700; white-space: nowrap;
}
.pcd-ct .b.fa .amt { color: var(--text-dim); }
.pcd-ct .yr { margin-top: 4px; font-size: 10px; color: var(--text-dim); white-space: nowrap; }
.pcd-ct .b.now .yr { color: var(--text); font-weight: 700; }
/* The injury log: a table, both ends of every listing with their year,
   its days, its designation and the injury behind its badge. Each
   column lines up with its heading: the dates and the injury to the
   left, the days and the designation centred under theirs. A heading
   and its cells share one padding, inset from
   the zebra band's edge, and the Detail heading sits over the injury's
   words, its badge hanging in the gutter before them. */
/* A phone too narrow for both dates scrolls the table, not the page. */
.pcd-log-wrap { overflow-x: auto; }
.pcd-log { width: 100%; border-collapse: collapse; font-size: 12.5px; line-height: 18px; }
.pcd-log th, .pcd-log td { padding: 6px 10px; text-align: left; white-space: nowrap; }
.pcd-log th {
  padding-top: 0; font-size: 10.5px; font-weight: 600;
  letter-spacing: 0.06em; text-transform: uppercase; color: var(--text-dim);
  border-bottom: 1px solid var(--border);
}
.pcd-log td { vertical-align: middle; border-bottom: 1px solid var(--row-border); }
.pcd-log tr:last-child td { border-bottom: 0; }
.pcd-log th:last-child, .pcd-log td:last-child { width: 100%; white-space: normal; }
.pcd-log th:last-child { padding-left: 36px; }       /* 10 + the 18 px badge + its 8 px gap */
.pcd-log td.num, .pcd-log th.num { text-align: left; }
.pcd-log td.c, .pcd-log th.c { text-align: center; }
.pcd-log .d { color: var(--text-dim); }
.pcd-log .pcd-code { font-size: inherit; font-weight: 700; }
.pcd-inj { display: flex; align-items: center; gap: 8px; }
.pcd-inj .inj-badge { display: block; flex: none; cursor: default; }
.pcd .ab-tally { margin-top: 0; }
/* Here the radar and the role gauge split the row evenly, and a season
   drawn twice (its regular season and its run) takes two shares. */
.pcd .ab-pair > .ab-radar, .pcd .ab-pair > .ab-role { flex: 1 1 0; min-width: 0; }
.pcd .ab-pair > .ab-radar.rd-two { flex-grow: 2; }
/* Between a phone and a desktop a third of the row each is too small
   to read the spokes' names: the pair stacks in the radar's own share. */
@media (max-width: 1000px) {
  .pcd .rd-shapes { flex-direction: column; gap: 14px 0; }
  .pcd .ab-pair > .ab-radar.rd-two { flex-grow: 1; }
}

@media (max-width: 720px) {
  .pcd-pair-ct, .pcd-pair-inj { grid-template-columns: 1fr; }
  .pcd-pair > .pcd-sec + .pcd-sec { border-top: 1px solid var(--border); }
  .pcd-pair > .pcd-sec + .pcd-sec::before { display: none; }
  .pcd-av { grid-template-columns: 52px minmax(0, 1fr) minmax(0, 1fr); }
  .pcd-av > :nth-child(4n) { display: none; }
  .pcd-id .pcd-shot { width: 110px; min-height: 0; }
}
