/* The application's own layout and components.
 *
 * The frame: one row - wordmark, a single segmented control, actions - painted
 * directly on the window chrome, with the content floating on a rounded page
 * inset from the edge. The inset is the whole trick: the header is not a band
 * above the app, it is the space around it.
 *
 * There is no build step and no CDN. Everything the browser loads is in this
 * folder, which is the same bargain `research/charts.py` already makes for the
 * HTML report: a file you can open in ten years is worth more than a toolchain.
 */

*, *::before, *::after { box-sizing: border-box; }

html, body {
  margin: 0;
  height: 100%;
  background: var(--bg-app);
  color: var(--fg);
  font-family: var(--sans);
  font-size: var(--fs-body);
  -webkit-font-smoothing: antialiased;
}

body { overflow: hidden; }

button, input, select, textarea { font: inherit; color: inherit; }

/* Links are underlined accent only inside prose. Everywhere else in this
   application an anchor is a *surface* - a shelf tile, a result row, a button -
   and those carry their own colour, so the browser default would paint the card
   name inside every tile a hyperlink blue. */
a { color: inherit; text-decoration: none; }
.help-body a, .body a, .hint a { color: var(--accent); }
.help-body a:hover, .body a:hover, .hint a:hover { text-decoration: underline; }

::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
  background: rgba(246, 242, 236, 0.13);
  border-radius: 5px;
  border: 2px solid transparent;
  background-clip: content-box;
}
::-webkit-scrollbar-thumb:hover { background: rgba(246, 242, 236, 0.26);
  background-clip: content-box; }
* { scrollbar-width: thin; scrollbar-color: rgba(246,242,236,.16) transparent; }

/* ---------------------------------------------------------------- shell --- */

#app { display: flex; flex-direction: column; height: 100%; min-width: 0; }

.header {
  display: flex;
  align-items: center;
  gap: var(--s3);
  padding: 12px 14px 8px;
  flex: none;
  min-width: 0;
}

.header-inner {
  display: flex;
  align-items: center;
  gap: var(--s3);
  width: 100%;
  min-width: 0;
  min-height: 46px;
}

.brand {
  display: flex;
  align-items: center;
  gap: 9px;
  background: none;
  border: none;
  cursor: pointer;
  padding: 4px 6px;
  border-radius: var(--r-md);
}
.brand:hover { background: rgba(246, 242, 236, 0.05); }
.brand svg { width: 22px; height: 22px; flex: none; }
.brand-name { font-weight: 700; letter-spacing: -0.2px; }

.header-divider {
  width: 1px;
  height: 22px;
  background: rgba(246, 242, 236, 0.1);
  flex: none;
}

/* The navigation track is *recessed*, not merely tinted. A control the same
   lightness as the surface behind it reads as loose text with one highlighted
   word; sinking the track makes the whole thing read as one control with a
   travelling selection, which is what it is. */
.segmented {
  display: flex;
  gap: 2px;
  min-width: 0;
  background: var(--bg-sunken);
  border: 1px solid var(--hairline);
  border-radius: var(--r-pill);
  padding: 3px;
  position: relative;
  overflow-x: auto;
  scrollbar-width: none;
}
.segmented::-webkit-scrollbar { display: none; }
.segment {
  background: transparent;
  border: none;
  border-radius: var(--r-pill);
  padding: 7px 17px;
  color: var(--fg-muted);
  font-size: var(--fs-small);
  font-weight: 500;
  cursor: pointer;
  white-space: nowrap;
  transition: color var(--dur-fast) var(--ease),
              background var(--dur-fast) var(--ease);
}
.segment:hover { color: var(--fg-dim); }
.segment[aria-current="page"] {
  color: var(--fg);
  font-weight: 600;
  background: var(--bg-elevated);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.07);
}

.header-spacer { flex: 1; }

.search-button {
  display: flex;
  align-items: center;
  gap: 9px;
  background: var(--bg-elevated);
  border: 1px solid transparent;
  border-radius: var(--r-pill);
  padding: 8px 12px 8px 13px;
  color: var(--fg-muted);
  font-size: var(--fs-small);
  cursor: pointer;
  min-width: 210px;
}
.search-button:hover { background: var(--bg-hover); color: var(--fg-dim); }
.search-button kbd { margin-left: auto; }

kbd {
  font-family: var(--sans);
  font-size: var(--fs-caption);
  color: var(--fg-faint);
  background: rgba(246, 242, 236, 0.06);
  border-radius: 5px;
  padding: 2px 6px;
  white-space: nowrap;
}

.cluster {
  display: flex;
  align-items: center;
  gap: var(--s2);
  background: rgba(246, 242, 236, 0.03);
  border-radius: var(--r-pill);
  padding: 4px 6px 4px 12px;
}

.freshness { display: flex; align-items: center; gap: 7px; cursor: help; }
.freshness-dot {
  width: 7px; height: 7px; border-radius: 50%;
  background: currentColor; flex: none;
}
.freshness-text { color: var(--fg-muted); font-size: var(--fs-micro); }

.icon-button {
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--r-md);
  padding: 7px;
  color: var(--fg-muted);
  cursor: pointer;
  display: inline-flex;
  line-height: 0;
}
.icon-button:hover { background: rgba(246, 242, 236, 0.07); color: var(--fg); }
.icon-button:disabled { opacity: 0.45; cursor: default; }
.icon-button svg { width: 17px; height: 17px; }

.progress {
  height: 2px;
  flex: none;
  overflow: hidden;
  background: transparent;
}
.progress.on::after {
  content: "";
  display: block;
  height: 100%;
  width: 40%;
  background: var(--accent);
  animation: slide 1.4s var(--ease-soft) infinite;
}
@keyframes slide {
  0% { transform: translateX(-100%); }
  100% { transform: translateX(350%); }
}

/* The operator's own maintenance notice - a plain strip, not a lockout.
   `[hidden]` needs its own rule: the class rule below ties its specificity
   with the browser's default `[hidden] { display: none }`, and an author
   stylesheet only wins that tie by being written explicitly. */
.maintenance-banner {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: none;
  margin: 0 14px;
  padding: 8px 12px;
  border-radius: var(--r-md);
  background: rgba(250, 178, 25, 0.1);
  color: var(--warn);
  font-size: var(--fs-small);
}
.maintenance-banner svg { flex: none; }
.maintenance-banner[hidden] { display: none; }

/* The page: a lighter, rounded slab inset from the window edge. */
.page {
  flex: 1;
  min-height: 0;
  min-width: 0;
  margin: 2px 14px 14px;
  background: var(--bg-page);
  border-radius: var(--r-xl);
  overflow: hidden;
  position: relative;
}

.view { height: 100%; overflow: auto; display: none; min-width: 0; }
.view.active { display: block; animation: fade var(--dur-base) var(--ease-soft); }
@keyframes fade { from { opacity: 0; transform: translateY(4px); } }

.scroll-page { padding: 26px 30px 34px; }

/* ------------------------------------------------------------- typography - */

.display { font-size: var(--fs-display); font-weight: 600; letter-spacing: -0.8px; }
.title { font-size: var(--fs-title); font-weight: 600; letter-spacing: -0.4px; margin: 0; }
.heading { font-size: var(--fs-heading); font-weight: 600; letter-spacing: -0.1px; margin: 0; }
.subtitle { color: var(--fg-dim); font-size: var(--fs-body); margin: 0; }
.body { color: var(--fg-dim); font-size: var(--fs-small); }
.muted { color: var(--fg-muted); font-size: var(--fs-small); }
.faint { color: var(--fg-faint); font-size: var(--fs-micro); }
.overline {
  color: var(--fg-faint); font-size: var(--fs-caption); font-weight: 700;
  letter-spacing: 0.7px; text-transform: uppercase;
}
.mono { font-family: var(--mono); font-variant-numeric: tabular-nums; }
.measure { max-width: 900px; }

.section-header { margin: 10px 0 4px; }
.section-header .heading { margin-bottom: 3px; }
.section-header .subtitle { font-size: var(--fs-small); color: var(--fg-muted); }
.section-header-row { display: flex; align-items: flex-end; gap: var(--s3); }
.section-header-row .section-header { flex: 1; }

hr.rule { border: none; border-top: 1px solid var(--grid); margin: var(--s5) 0; }

.hint {
  color: var(--fg-muted);
  font-size: var(--fs-small);
  background: var(--bg-sunken);
  border-radius: var(--r-md);
  padding: 13px 15px;
  line-height: 1.55;
}

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

/* A panel is a lit surface: slightly brighter at the top than the bottom, with
   a hairline that catches the light. That is what separates "raised" from
   "merely a different grey" - a flat fill ten levels lighter than the page is
   invisible at a glance, and a 1px outline on a flat fill is the developer-tool
   look this design exists to leave behind. */
.panel {
  background: linear-gradient(180deg, #2b2522, #221e1b);
  border: 1px solid rgba(246, 242, 236, 0.055);
  border-radius: var(--r-lg);
}
.tile { border-radius: var(--r-md); }
.quiet {
  background: var(--bg-sunken);
  border: 1px solid var(--hairline);
  border-radius: var(--r-lg);
}

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

.btn {
  background: linear-gradient(180deg, #322b27, #292320);
  border: 1px solid rgba(246, 242, 236, 0.07);
  border-radius: var(--r-md);
  padding: 7px 14px;
  color: var(--fg-dim);
  font-size: var(--fs-small);
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease),
              color var(--dur-fast) var(--ease),
              border-color var(--dur-fast) var(--ease);
  white-space: nowrap;
}
.btn:hover {
  background: linear-gradient(180deg, #3d3530, #332c28);
  border-color: rgba(246, 242, 236, 0.12);
  color: var(--fg);
}
.btn:active { background: var(--bg-panel); }
.btn:disabled { color: var(--fg-faint); background: rgba(45, 39, 36, 0.5);
  cursor: default; }
.btn[aria-pressed="true"] {
  background: var(--accent-wash); color: var(--fg);
  border-color: rgba(57, 135, 229, 0.5);
}

.btn.primary {
  background: linear-gradient(180deg, var(--pos-strong), var(--accent));
  color: #fff;
  border: 1px solid rgba(246, 242, 236, 0.22);
  font-weight: 600;
  padding: 8px 16px;
}
.btn.primary:hover {
  background: linear-gradient(180deg, #8dbbf1, var(--pos-strong));
  border-color: rgba(246, 242, 236, 0.34);
}
.btn.ghost {
  background: transparent; border: none; color: var(--fg-muted);
  padding: 5px 8px;
}
.btn.ghost:hover { color: var(--accent); background: rgba(57, 135, 229, 0.1); }

/* A chip is a filter you can toggle, so it looks like a switch, not a button. */
.chip {
  padding: 6px 14px;
  font-size: var(--fs-small);
  border-radius: var(--r-pill);
  background: rgba(246, 242, 236, 0.05);
  border: 1px solid transparent;
  color: var(--fg-dim);
  cursor: pointer;
  white-space: nowrap;
}
.chip:hover { background: rgba(246, 242, 236, 0.09); color: var(--fg); }
.chip[aria-pressed="true"] {
  background: var(--accent); color: #fff; border-color: var(--accent);
  font-weight: 600;
}
.chip-row { display: flex; flex-wrap: wrap; gap: 6px; }

.filter-chip {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 5px 8px 5px 10px;
  font-size: var(--fs-micro);
  border-radius: var(--r-pill);
  background: var(--accent-wash);
  border: 1px solid rgba(57, 135, 229, 0.35);
  color: var(--fg);
  cursor: pointer;
}
.filter-chip:hover { background: rgba(57, 135, 229, 0.28); }
.filter-chip svg { width: 11px; height: 11px; }

.count-badge {
  position: absolute; top: -5px; right: -5px;
  min-width: 17px; height: 17px; padding: 0 4px;
  border-radius: var(--r-pill);
  background: var(--accent); color: #fff;
  font-size: 10px; font-weight: 700;
  display: grid; place-items: center;
}

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

.badge {
  display: inline-flex; align-items: center;
  border-radius: var(--r-pill);
  padding: 2px 9px;
  font-size: var(--fs-micro);
  font-weight: 600;
  border: 1px solid currentColor;
  background: color-mix(in srgb, currentColor 13%, transparent);
  white-space: nowrap;
}

/* ---------------------------------------------------------------- fields -- */

/* Filled, not outlined. The border appears on focus, where it means something. */
input[type="text"], input[type="number"], input[type="search"], select, textarea {
  background: var(--bg-elevated);
  border: 1px solid transparent;
  border-radius: var(--r-md);
  padding: 7px 11px;
  color: var(--fg);
  outline: none;
  width: 100%;
}
input:hover, select:hover { background: var(--bg-hover); }
input:focus, select:focus, textarea:focus { border-color: var(--accent); }
input::placeholder, textarea::placeholder { color: var(--fg-faint); }
textarea { resize: vertical; line-height: 1.5; font-family: var(--sans); }

.search-field {
  font-size: 14px; padding: 10px 15px; border-radius: var(--r-pill);
}

label.check {
  display: flex; align-items: flex-start; gap: 9px;
  color: var(--fg-dim); padding: 3px 0; cursor: pointer;
  font-size: var(--fs-small);
}
label.check:hover { color: var(--fg); }
input[type="checkbox"] {
  appearance: none; width: 16px; height: 16px; margin: 1px 0 0;
  border: 1px solid var(--border); border-radius: 5px;
  background: var(--bg-elevated); cursor: pointer; flex: none;
}
input[type="checkbox"]:checked {
  background: var(--accent); border-color: var(--accent);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='white' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3.5 8.5l3 3 6-7'/%3E%3C/svg%3E");
  background-size: contain;
}

input[type="range"] {
  appearance: none; width: 100%; height: 4px; padding: 0; margin: 10px 0;
  background: var(--bg-elevated); border-radius: 2px; cursor: pointer;
}
input[type="range"]::-webkit-slider-thumb {
  appearance: none; width: 14px; height: 14px; border-radius: 50%;
  background: var(--fg); border: none; cursor: pointer;
}
input[type="range"]::-moz-range-thumb {
  width: 14px; height: 14px; border-radius: 50%;
  background: var(--fg); border: none; cursor: pointer;
}

select {
  appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 6' fill='none' stroke='%238e867b' stroke-width='1.5'%3E%3Cpath d='M1 1l4 4 4-4'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 11px center;
  background-size: 10px;
  padding-right: 30px;
}
select option { background: var(--bg-elevated); }

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

/* Fields default to `width: 100%`, which inside a flex row makes every one of
   them fight for the whole line. In a row they size to their content unless a
   `.grow` wrapper says otherwise. */
.row { display: flex; align-items: center; gap: var(--s2); }
.row > select, .row > input:not(.grow) { width: auto; flex: none; }
.row > .grow > input, .row > .grow > select { width: 100%; }
.col { display: flex; flex-direction: column; }
.grow { flex: 1; min-width: 0; }
.stack-4 > * + * { margin-top: var(--s1); }
.stack-8 > * + * { margin-top: var(--s2); }
.stack-12 > * + * { margin-top: var(--s3); }
.stack-16 > * + * { margin-top: var(--s4); }
.stack-24 > * + * { margin-top: var(--s5); }

/* ------------------------------------------------------------ empty state - */

.empty {
  display: grid; place-items: center; text-align: center;
  padding: 60px 30px; gap: var(--s3); height: 100%;
  align-content: center;
}
.empty svg { width: 46px; height: 46px; color: var(--fg-faint); }
.empty h3 { margin: 0; font-size: var(--fs-heading); font-weight: 600; }
.empty p { margin: 0; max-width: 460px; color: var(--fg-muted);
  font-size: var(--fs-small); line-height: 1.6; }

/* ------------------------------------------------------------- card art --- */

.art {
  position: relative;
  border-radius: 7px;
  overflow: hidden;
  flex: none;
  background: linear-gradient(135deg, #2f2925, #211c19);
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.45);
  aspect-ratio: 5 / 7;
}
/* The image is in the plate from the start but only shown once it has decoded,
   so a half-loaded or broken picture never flashes over the placeholder. */
.art img {
  width: 100%; height: 100%; object-fit: contain; display: block;
  opacity: 1; transition: opacity var(--dur-base) var(--ease-soft);
}
.art.placeholder img { opacity: 0; }
/* A card-shaped plate with a ball watermark, not a grey slab with a dot in it.
   Several of the highest-scoring printings in this dataset genuinely have no
   artwork at TCGplayer, so this stands in at hero size fairly often - a blank
   rectangle there reads as a failed image rather than as a card we have no
   picture of. */
.art.placeholder::after {
  content: "";
  position: absolute; inset: 9%;
  border: 1px solid rgba(255, 255, 255, 0.06);
  border-radius: 4px;
  background: no-repeat center / 34% url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='rgba(255,255,255,0.12)' stroke-width='1.6'%3E%3Ccircle cx='12' cy='12' r='9'/%3E%3Ccircle cx='12' cy='12' r='3.2'/%3E%3Cpath d='M3 12h5.8M15.2 12H21'/%3E%3C/svg%3E");
}

/* ----------------------------------------------------------- rarity pips -- */

.pip { width: 11px; height: 11px; flex: none; display: inline-block; }
/* A bigger cut of the same foil pip, for the two places Lifetime Supporter
   is *mentioned* rather than a card's rarity is *reported* - the header
   button and the locked-feature upsell panel. Same glyph, deliberately: it
   is what the foil pip already means ("the rarest, most wanted version of a
   card"), reused as the one consistent mark for the site's one paid tier
   instead of a badge invented separately for billing. */
.pip-lg { width: 20px; height: 20px; }

/* ----------------------------------------------------------------- toast -- */

.toast {
  position: fixed; left: 50%; bottom: 26px;
  transform: translate(-50%, 14px);
  background: var(--bg-floating);
  border: 1px solid rgba(246, 242, 236, 0.09);
  border-radius: var(--r-md);
  padding: 11px 17px;
  font-size: var(--fs-small);
  box-shadow: 0 12px 34px rgba(0, 0, 0, 0.55);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--dur-base) var(--ease-soft),
              transform var(--dur-base) var(--ease-soft);
  z-index: 90;
  max-width: min(680px, 88vw);
  display: flex; align-items: center; gap: 9px;
}
.toast.on { opacity: 1; transform: translate(-50%, 0); pointer-events: auto; }
.toast::before {
  content: ""; width: 6px; height: 6px; border-radius: 50%;
  background: currentColor; flex: none;
}
.toast span { color: var(--fg-dim); }

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

.overlay {
  position: fixed; inset: 0; z-index: 100;
  background: rgba(6, 5, 4, 0.62);
  backdrop-filter: blur(2px);
  display: none;
  animation: fade var(--dur-fast) var(--ease-soft);
}
.overlay.on { display: block; }

.dialog {
  position: absolute; left: 50%; transform: translateX(-50%);
  top: 84px;
  width: min(880px, 92vw);
  max-height: calc(100vh - 150px);
  background: var(--bg-floating);
  border: 1px solid rgba(246, 242, 236, 0.09);
  border-radius: var(--r-lg);
  box-shadow: 0 26px 70px rgba(0, 0, 0, 0.6);
  display: flex; flex-direction: column;
  overflow: hidden;
}

.popover {
  position: absolute;
  background: var(--bg-floating);
  border: 1px solid rgba(246, 242, 236, 0.09);
  border-radius: var(--r-lg);
  box-shadow: 0 22px 60px rgba(0, 0, 0, 0.55);
  padding: var(--s4) var(--s5) var(--s5);
  max-height: calc(100vh - 120px);
  overflow: auto;
  z-index: 101;
}

/* --------------------------------------------------------------- account -- */

/* The sign-in panel is narrow on purpose. The dialog pattern it borrows is
   sized for the command palette and the help window, both of which are lists
   to scan; this is a form to fill in, and a 880px-wide text field reads as a
   search box rather than as something to type a password into. */
.dialog.supporter-modal { width: min(480px, 92vw); }

/* -------------------------------------------------------- shared upsell -- */
/* The compact panel every locked personal surface shows instead of its
   content - the report's three personal tabs, and the Collection, Watchlist
   and Notes views. One rule set, so the same calm explanation looks the same
   everywhere it appears rather than reading as four different features. */
.upsell {
  padding: 18px 20px;
  display: flex;
  align-items: flex-start;
  gap: var(--s4);
}
.upsell-icon {
  flex: none;
  width: 36px; height: 36px;
  border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: var(--accent-wash);
  color: var(--accent);
}
.upsell h3 { margin: 0 0 4px; }
.upsell .btn { flex: none; align-self: center; }
@media (max-width: 560px) {
  .upsell { flex-direction: column; }
  .upsell .btn { align-self: flex-start; }
}

.dialog.auth {
  width: min(430px, 92vw);
  top: 12vh;
  padding: var(--s6);
  gap: var(--s5);
  overflow: auto;
}
.auth-head { display: flex; flex-direction: column; gap: 6px; }
.auth-title { margin: 0; font-size: var(--fs-heading); font-weight: 600; }
.auth-subtitle {
  margin: 0; font-size: var(--fs-body); font-weight: 600; color: var(--fg-dim);
}
.auth-blurb, .auth-reason {
  margin: 0; font-size: var(--fs-small); color: var(--fg-muted);
  line-height: 1.5;
}
/* The sentence that says which action asked for an account. It is the whole
   reason the panel is on screen, so it is the one thing in it with colour. */
.auth-reason {
  color: var(--fg-dim);
  border-left: 2px solid var(--accent);
  padding-left: 10px;
}
.auth-form { display: flex; flex-direction: column; gap: var(--s4); }
.auth-field { display: flex; flex-direction: column; gap: 5px; }
.auth-label { font-size: var(--fs-micro); color: var(--fg-muted); }
.auth-hint {
  font-size: var(--fs-micro); color: var(--fg-faint); line-height: 1.45;
}
.auth-field input {
  width: 100%;
  padding: 9px 11px;
  font-size: var(--fs-body);
  color: var(--fg);
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
}
.auth-field input:focus {
  outline: none;
  border-color: var(--accent);
}
.auth-forgot {
  align-self: flex-start; background: none; border: none; padding: 0;
  font-size: var(--fs-micro); color: var(--fg-muted); cursor: pointer;
}
.auth-forgot:hover { color: var(--accent); text-decoration: underline; }
.auth-legal {
  margin: 0; font-size: var(--fs-micro); color: var(--fg-faint);
  line-height: 1.5;
}
.auth-legal a { color: var(--fg-muted); }
.auth-legal a:hover { color: var(--accent); }
.auth-error:empty { display: none; }
.auth-error {
  margin: 0; font-size: var(--fs-small); color: var(--crit); line-height: 1.5;
}
.auth-actions { display: flex; align-items: center; gap: var(--s3); }
.auth-actions .grow { flex: 1; }
.auth-swap { flex: 1; justify-content: flex-start; padding-left: 0; }
.auth-submit { min-width: 130px; justify-content: center; }
.auth-foot {
  margin: 0; font-size: var(--fs-micro); color: var(--fg-faint);
  line-height: 1.5;
}

.account-facts { display: flex; gap: var(--s5); }
.account-fact .cap {
  font-size: var(--fs-micro); color: var(--fg-muted);
}
.account-fact .value {
  font-size: var(--fs-body); color: var(--fg-dim); font-weight: 600;
}

.account-button { display: flex; align-items: center; gap: 7px; }
.supporter-button { display: flex; align-items: center; gap: 7px; }
.account-name { max-width: 130px; overflow: hidden; text-overflow: ellipsis;
                white-space: nowrap; }
.avatar {
  display: inline-flex; align-items: center; justify-content: center;
  width: 22px; height: 22px; border-radius: 50%;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  font-size: 10px; font-weight: 700; letter-spacing: 0.02em;
  color: var(--fg-dim);
}

/* The invitation that replaces the personal tabs on a card report. */
.signin-prompt {
  display: flex; flex-direction: column; align-items: flex-start;
  gap: var(--s3);
  padding: var(--s5);
}
.signin-prompt h3 { margin: 0; font-size: var(--fs-body); font-weight: 600; }
.signin-prompt p { margin: 0; max-width: 52ch; line-height: 1.55; }

/* A phone gets the panel as a sheet rather than a floating card: at 380px a
   centred dialog with margins is mostly margin. */
@media (max-width: 520px) {
  .dialog.auth {
    top: auto; bottom: 0; left: 0; transform: none;
    width: 100%;
    max-height: 92vh;
    border-radius: var(--r-lg) var(--r-lg) 0 0;
  }
  .account-name { display: none; }
}

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

/* Horizontal separation only. A full grid makes every cell look equally
   load-bearing, which is exactly the spreadsheet feel to avoid. */
.data-table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--fs-body);
  background: var(--bg-panel);
  border-radius: var(--r-lg);
  overflow: hidden;
}
.data-table th {
  position: sticky; top: 0; z-index: 2;
  background: var(--bg-panel);
  color: var(--fg-muted);
  font-size: var(--fs-micro);
  font-weight: 600;
  text-align: right;
  padding: 10px;
  border-bottom: 1px solid var(--border);
  cursor: pointer;
  white-space: nowrap;
  user-select: none;
}
.data-table th.left, .data-table td.left { text-align: left; }
.data-table th:hover { background: var(--bg-hover); color: var(--fg-dim); }
.data-table td {
  padding: 0 10px;
  height: var(--compact-row);
  border-bottom: 1px solid var(--grid);
  text-align: right;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 240px;
}
.data-table tbody tr { cursor: pointer; }
.data-table tbody tr:hover td { background: var(--bg-hover); }
.data-table tbody tr[aria-selected="true"] td { background: var(--bg-select); }
.data-table td.name { font-weight: 600; }
.data-table td.mono { font-family: var(--mono); font-variant-numeric: tabular-nums; }

.table-wrap { overflow: auto; border-radius: var(--r-lg); height: 100%; }

/* The lifecycle pill on the Sets table. Deliberately colourless - see
   columns.LIFECYCLE_ORDER for why - stepping in lightness from the earliest
   stage to the latest. */
.stage-pill {
  /* A default, so the rule still resolves if a row arrives with no stage. An
     unset custom property inside calc() invalidates the whole declaration, and
     the pill silently degrades to bare text. */
  --step: 0.5;
  display: inline-block;
  border-radius: var(--r-pill);
  padding: 2px 10px;
  font-size: var(--fs-micro);
  background: rgb(255 255 255 / calc(4% + 8% * var(--step)));
  border: 1px solid rgb(255 255 255 / calc(5.5% + 8.6% * var(--step)));
  color: color-mix(in srgb, var(--fg) calc(var(--step) * 80%), var(--fg-muted));
}

/* ------------------------------------------------------------ responsive -- */

/* Narrow screens. Nothing is removed - the navigation, the search and the
   freshness dot all survive - but the parts that carry only identity give up
   their width first, and the navigation becomes a scrolling strip rather than
   pushing the rest of the header off the side. */
@media (max-width: 1024px) {
  .header { padding: 10px 10px 6px; }
  .header-divider, .search-button .label, .search-button kbd { display: none; }
  .header-spacer { display: none; }
  .segmented { flex: 1 1 auto; }
  .search-button { min-width: 0; padding: 8px 10px; }
  .cluster { flex: none; padding: 4px; gap: 4px; }
  .page { margin: 2px 8px 8px; border-radius: var(--r-lg); }
  .scroll-page { padding: 18px 16px 28px; }
}

/* Below this the header stops being one row. Squeezing six navigation items,
   a search field and the data controls onto a phone-width line leaves every one
   of them too small to hit; the navigation gets its own full-width strip
   instead, which is where a phone expects it anyway. */
@media (max-width: 620px) {
  .header-inner { flex-wrap: wrap; row-gap: var(--s2); }
  .segmented { order: 3; flex: 1 0 100%; }
  .search-button { flex: 1; }
  .brand-name { display: none; }
  .freshness-text { display: none; }
  .segment { padding: 7px 14px; }
}
