* { box-sizing: border-box; margin: 0; }

/* ===========================================================================
 * RPV house palette — Renaissance Park & Vespera brand system.
 *
 * THE PALETTE IS PINNED ON `body`, NOT ON `:root`, and this is deliberate.
 * `tools/house-ui/apply.py` appends the house standard to the END of this file
 * and redefines tokens on `:root`. A custom property set on the element itself
 * beats one inherited from `:root` whatever order the two rules appear in, so
 * re-running that script cannot reach inside the desk and half-repaint it.
 * That is the same guard the token pages and the receipt screen already use;
 * it is now the whole stylesheet's, because the whole stylesheet is light.
 *
 * (Before this, the script's light `--bg` landed on top of a dark `--tx` that
 * nothing redefined, so every heading sitting straight on the body — the page
 * title, the tab strip, the footer — was near-white on near-white.)
 * ------------------------------------------------------------------------ */
:root {
  /* Brand — exact values, never approximated. */
  --rpv-midnight:   #0D1B2A;
  --rpv-slate:      #1C3A52;
  --rpv-steel:      #2E5F80;
  --rpv-horizon:    #4A90B8;
  --rpv-terracotta: #C4622D;
  --rpv-ember:      #E07B45;
  --rpv-sand:       #F0C89A;
  --rpv-cream:      #FAF7F2;
  --rpv-mist:       #EEF2F5;
  --rpv-stone:      #8FA3B1;
  --rpv-charcoal:   #2C3E4A;

  --sidebar-w: 244px;
  --shadow-sm: 0 1px 3px rgba(13,27,42,.08), 0 1px 2px rgba(13,27,42,.04);
  --shadow-md: 0 4px 12px rgba(13,27,42,.10), 0 2px 4px rgba(13,27,42,.06);
  --shadow-lg: 0 18px 48px rgba(13,27,42,.18);
}

body {
  /* The legacy token names that every rule below — and 111KB of markup built by
     app.js — already speaks, remapped onto the brand. Nothing downstream of
     here had to be renamed.

     EVERY COLOUR A RULE USES IS A TOKEN, and the semantic ones come in bg/fg
     pairs. That is what makes the dark palette below a swap of this block
     rather than a second copy of the stylesheet: the rules never learn which
     theme is on. */
  --bg: #FAF7F2;                    /* Cream — page canvas, never pure white  */
  --panel: #FFFFFF;                 /* White — cards, tables, modals          */
  --panel-2: #EEF2F5;               /* Mist — sunk surfaces, table heads      */
  --line: rgba(44, 62, 74, .12);
  --line-strong: rgba(44, 62, 74, .22);
  --tx: #2C3E4A;                    /* Charcoal — body text, never pure black */
  /* `--dim` is secondary TEXT, so in the light theme it is Ink-3 and not Stone:
     Stone (#8FA3B1) is a 2.6:1 label colour on Cream, and `--dim` here carries
     table headings and whole sentences. On the dark ground Stone clears 5.8:1,
     so there it is exactly what the brand says it is. `--faint` stays for
     decorative micro-labels that repeat something already on the row. */
  --dim: #5C7284;
  --faint: #8FA3B1;
  --acc: #2E5F80;                   /* Steel — interactive / informational    */
  --act: #C4622D;                   /* Terracotta — the action colour         */
  --act-hover: #A8521F;
  --act-ink: #FFFFFF;               /* what reads on top of the action fill   */
  --head: #0D1B2A;                  /* Midnight — headings and big figures    */
  --btn-ink: #1C3A52;               /* Slate — the ghost/secondary button ink */
  --ok: #2D7A4F;
  --warn: #B87333;
  --bad: #B83232;

  /* Status pairs — the brand's badge set. */
  --ok-bg: #D4EDDF;   --ok-fg: #2D7A4F;
  --warn-bg: #F5E6D0; --warn-fg: #8B5A1A;
  --bad-bg: #F5D4D4;  --bad-fg: #8B2020;
  --info-bg: #D4E5F5; --info-fg: #1C4A72;
  --mute-bg: #EEF2F5; --mute-fg: #5A7080;

  /* Whole-row washes: a tint over the table ground, not a badge. */
  --row-hover: #F5F8FA;
  --row-bad: rgba(184, 50, 50, .06);
  --row-warn: rgba(184, 115, 51, .08);
  --row-info: rgba(74, 144, 184, .08);
  --row-ok: #F2FAF5;

  --field-bg: #FFFFFF;
  --field-line: #D0DBE3;
  --ring: rgba(46, 95, 128, .16);   /* focus halo                             */

  --rail: #0D1B2A;                  /* Midnight — the nav, dark in both themes */
  --bg-blur: rgba(250, 247, 242, .9);
  --scrim: rgba(13, 27, 42, .4);
  --overlay: rgba(13, 27, 42, .44);

  background: #FAF7F2;
  color: #2C3E4A;
  font: 14px/1.55 system-ui, sans-serif;
  padding: 0;
  -webkit-text-size-adjust: 100%;
}

/* ===========================================================================
 * The shell: a fixed Midnight rail beside a scrolling work surface.
 * ------------------------------------------------------------------------ */
.shell { display: flex; min-height: 100vh; }

.sidebar {
  position: fixed; inset: 0 auto 0 0; z-index: 30;
  width: var(--sidebar-w); flex: none;
  display: flex; flex-direction: column;
  background: var(--rail); color: #fff;
  overflow: hidden;
}

/* Brand mark — RPV wordmark with the Ember dot, per the logo system. */
.brand { display: flex; align-items: center; gap: 11px; padding: 20px 20px 18px; flex: none; }
.brand-mark {
  display: inline-flex; align-items: baseline; flex: none;
  font-size: 19px; font-weight: 600; letter-spacing: -.01em; color: #fff;
}
.brand-mark i { font-style: normal; color: var(--rpv-ember); margin-left: 2px; }
.brand-txt { display: block; font-size: 10px; line-height: 1.35; color: var(--rpv-stone);
  letter-spacing: .1em; text-transform: uppercase; }

/* Nav. `#tabs` keeps its id, its `button[data-tab]` children and its `.on`
   class, so app.js drives the rail exactly as it drove the old tab strip.
   The group headings are plain divs — `querySelectorAll("button")` there
   still returns only the tabs. */
#tabs.tabs {
  flex: 1 1 auto; overflow-y: auto; overscroll-behavior: contain;
  display: flex; flex-direction: column; gap: 1px;
  padding: 4px 12px 16px; margin: 0; border: 0;
}
#tabs .nav-group {
  padding: 15px 10px 5px; font-size: 10px; font-weight: 500;
  letter-spacing: .12em; text-transform: uppercase; color: #5A7288;
}
#tabs .nav-group:first-child { padding-top: 2px; }
#tabs button {
  position: relative;
  display: flex; align-items: center; gap: 8px; width: 100%;
  background: none; border: 0; border-radius: 8px;
  padding: 9px 12px; text-align: left;
  font: inherit; font-weight: 400; color: #AFC0CE; cursor: pointer;
  transition: background 120ms, color 120ms;
}
#tabs button:hover { background: rgba(255,255,255,.06); color: #fff; border-color: transparent; }
#tabs button.on { background: rgba(224,123,69,.14); color: #fff; font-weight: 500; }
#tabs button.on::before {
  content: ""; position: absolute; left: -12px; top: 50%; margin-top: -10px;
  width: 3px; height: 20px; border-radius: 0 3px 3px 0; background: var(--rpv-ember);
}
#tabs button:focus-visible { outline: none; box-shadow: 0 0 0 2px rgba(224,123,69,.6); }
/* The count each tab carries. `.n.up` is app.js saying this one needs
   somebody — it is the only place the rail spends a warning colour. */
#tabs button .n {
  margin-left: auto; flex: none; min-width: 20px; padding: 1px 6px;
  border-radius: 999px; background: rgba(255,255,255,.1); color: #C9D6E2;
  font-size: 11px; font-weight: 500; text-align: center;
  font-variant-numeric: tabular-nums;
}
#tabs button .n:empty { display: none; }
#tabs button .n.up { background: var(--rpv-terracotta); color: #fff; }

/* Live state, kept at the foot of the rail where it is glanceable but not loud. */
.rail-foot { flex: none; padding: 12px 16px calc(14px + env(safe-area-inset-bottom));
  border-top: 1px solid rgba(255,255,255,.08); display: flex; flex-wrap: wrap; gap: 6px; }
.rail-foot .pill { border-color: rgba(255,255,255,.18); color: var(--rpv-stone); background: none; }
.rail-foot .pill.ok { color: #7FD6A5; border-color: rgba(127,214,165,.4); background: none; }
.rail-foot .pill.bad { color: #F0A0A0; border-color: rgba(240,160,160,.45); background: none; }

.content { flex: 1 1 auto; min-width: 0; margin-left: var(--sidebar-w); padding: 0 0 72px; }

/* ===========================================================================
 * Topbar
 * ------------------------------------------------------------------------ */
header {
  position: sticky; top: 0; z-index: 20;
  display: flex; justify-content: space-between; align-items: center;
  gap: 16px; flex-wrap: wrap;
  padding: 14px 32px; margin: 0;
  background: var(--bg-blur); backdrop-filter: blur(8px);
  border-bottom: 1px solid var(--line);
}
.head-left { display: flex; align-items: center; gap: 12px; min-width: 0; }
h1 { font-size: 22px; font-weight: 600; letter-spacing: -.01em; color: var(--head); }
.sub { color: var(--dim); font-size: 13px; margin-top: 2px; }
.head-right { display: flex; gap: 8px; align-items: center; }
.pill { border: 1px solid var(--line-strong); border-radius: 999px; padding: 4px 11px;
  font-size: 12px; color: var(--dim); background: var(--panel); white-space: nowrap; }
.pill.ok { color: var(--ok-fg); border-color: rgba(45,122,79,.35); background: var(--ok-bg); }
.pill.bad { color: var(--bad-fg); border-color: rgba(184,50,50,.35); background: var(--bad-bg); }

/* The drawer handle. Only ever on screen under 960px. */
#nav-toggle {
  display: none; flex: none; width: 38px; height: 38px; padding: 0;
  align-items: center; justify-content: center;
  background: var(--panel); border: 1px solid var(--line-strong); border-radius: 8px;
  color: var(--head);
}
#nav-toggle svg { width: 18px; height: 18px; fill: none; stroke: currentColor;
  stroke-width: 2; stroke-linecap: round; }
.scrim { display: none; }

main.work { padding: 24px 32px 0; }

/* ===========================================================================
 * Controls
 * ------------------------------------------------------------------------ */
input, textarea, select {
  background: var(--field-bg); color: var(--tx);
  border: 1px solid var(--field-line); border-radius: 6px; padding: 8px 11px; font: inherit;
}
input::placeholder, textarea::placeholder { color: var(--faint); }
input:focus-visible, textarea:focus-visible, select:focus-visible {
  outline: none; border-color: var(--acc); box-shadow: 0 0 0 3px var(--ring);
}
button {
  background: var(--panel); color: var(--btn-ink);
  border: 1px solid var(--line-strong); border-radius: 6px; padding: 8px 14px;
  font: inherit; font-weight: 500; cursor: pointer;
  transition: background 120ms, border-color 120ms, color 120ms;
}
button:hover { background: var(--panel-2); border-color: var(--acc); }
button:focus-visible { outline: none; box-shadow: 0 0 0 3px var(--ring); }
button:disabled { opacity: .55; cursor: default; }

/* Terracotta is the action colour and is spent sparingly — the one button up
   here that reaches out to SAERP gets it, nothing else does. */
#btn-refresh { background: var(--act); border-color: var(--act); color: var(--act-ink); }
#btn-refresh:hover:not(:disabled) { background: var(--act-hover); border-color: var(--act-hover); }
#btn-refresh:disabled { background: var(--act); border-color: var(--act); color: var(--act-ink); }

/* ===========================================================================
 * Stat cards
 * ------------------------------------------------------------------------ */
.cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: 16px; margin-bottom: 24px; }
/* (0,2,0), so the house-ui block's looser `.card` padding does not reach these. */
.cards .card {
  background: var(--panel-2); border: 1px solid transparent; border-radius: 12px;
  padding: 16px 18px; margin: 0;
}
.cards .card .k { color: var(--dim); font-size: 11px; font-weight: 500;
  text-transform: uppercase; letter-spacing: .06em; }
.cards .card .v { font-size: 26px; font-weight: 600; letter-spacing: -.015em;
  color: var(--head); margin: 6px 0 3px; font-variant-numeric: tabular-nums; }
.cards .card .v.muted { color: var(--faint); }
.cards .card .s { color: var(--dim); font-size: 12px; }
.cards .card.danger.active { background: var(--bad-bg); border-color: rgba(184,50,50,.28); }
.cards .card.danger.active .v { color: var(--bad-fg); }
.cards .card.danger.active .k, .cards .card.danger.active .s { color: var(--bad-fg); opacity: .78; }

.banner { background: var(--bad-bg); border: 1px solid rgba(184,50,50,.3); border-radius: 12px;
  padding: 12px 16px; margin-bottom: 16px; color: var(--bad-fg); }
.banner b { color: var(--bad-fg); }
.hidden { display: none; }

/* ===========================================================================
 * Tables
 * ------------------------------------------------------------------------ */
.tbl-wrap { overflow-x: auto; background: var(--panel); border: 1px solid var(--line);
  border-radius: 12px; box-shadow: var(--shadow-sm); }
table { border-collapse: collapse; width: 100%; }
/* (0,0,2), so the house-ui block's bare `td`/`th` padding does not undercut it. */
table th, table td { padding: 10px 14px; text-align: left; border-bottom: 1px solid var(--line); white-space: nowrap; }
tbody tr:last-child td { border-bottom: 0; }
th { color: var(--dim); font-size: 11px; font-weight: 500; text-transform: uppercase;
  letter-spacing: .06em; position: sticky; top: 0; background: var(--panel-2); }
td.r, th.r { text-align: right; font-variant-numeric: tabular-nums; }
tbody tr:hover { background: var(--row-hover); }
tr.viol td { background: var(--row-bad); }
tr.exempt td { color: var(--dim); }
.acct-no { color: var(--faint); font-size: 12px; }
.neg { color: var(--warn); }
.up { color: var(--bad); } .down { color: var(--ok); }
td small { display: block; color: var(--dim); font-size: 11.5px; }
tr.click { cursor: pointer; }
.empty { color: var(--dim); padding: 28px; text-align: center; }
.warnrow td { background: var(--row-warn); }

/* Pagination -- app.js `paginate`. Rows are hidden, never removed, so every
   handler wired to them survives a page change. */
tr.pg-hide { display: none; }
.tbl-wrap { scroll-margin-top: 104px; }
/* Left-aligned, beside its count: the ask widget floats bottom-right and sat
   on the » button whenever the page was scrolled to its end. */
.pager { display: flex; align-items: center; flex-wrap: wrap;
  gap: 8px; margin: 8px 2px 16px; color: var(--dim); font-size: 12.5px;
  font-variant-numeric: tabular-nums; }
.pager .pg-range { margin-right: 6px; }
.pager button { padding: 4px 10px; font-size: 12.5px; }

/* ===========================================================================
 * Badges — the brand's status pill set.
 * ------------------------------------------------------------------------ */
.tag { display: inline-block; border-radius: 999px; padding: 2px 9px;
  font-size: 11px; font-weight: 500; letter-spacing: .02em; }
.tag.MONITOR { background: var(--info-bg); color: var(--info-fg); }
.tag.MUST_CLEAR { background: var(--bad-bg); color: var(--bad-fg); }
.tag.MUST_CLEAR.clear { background: var(--ok-bg); color: var(--ok-fg); }
.tag.EXEMPT { background: var(--mute-bg); color: var(--mute-fg); }
.tag.STORAGE { background: var(--ok-bg); color: var(--ok-fg); }
.tag.PROJECT { background: var(--info-bg); color: var(--info-fg); }
.tag.PURCH, .tag.CONVERT, .tag.PURCHRET, .tag.SALESRET { background: var(--mute-bg); color: var(--mute-fg); }
.tag.CRITICAL { background: var(--bad-bg); color: var(--bad-fg); }
.tag.DRAFT { background: var(--mute-bg); color: var(--mute-fg); }
.tag.SENT { background: var(--warn-bg); color: var(--warn-fg); }
.tag.RECEIVED, .tag.CLOSED, .tag.VERIFIED { background: var(--ok-bg); color: var(--ok-fg); }
.tag.SHORT, .tag.CANCELLED { background: var(--bad-bg); color: var(--bad-fg); }
/* Not red. Abandoning is a decision somebody made on purpose, with a
   consequence written down beside it — not a failure. */
.tag.ABANDONED { background: var(--warn-bg); color: var(--warn-fg); }
.tag.OPEN { background: var(--info-bg); color: var(--info-fg); }
.tag.COUNTED { background: var(--warn-bg); color: var(--warn-fg); }
.tag-nosub { background: var(--info-bg); color: var(--info-fg); border-radius: 999px; padding: 1px 7px;
  font-size: 11px; font-weight: 500; }
/* The bpar code (s_bpartner.description) — what separates "IPA 51" from
   "IPA 52" when the name on both is the same person. */
.bp-code { background: var(--panel-2); color: var(--dim); border-radius: 5px; padding: 1px 6px;
  font-size: 11px; font-variant-numeric: tabular-nums; margin-left: 4px; }

.decided { color: var(--dim); font-size: 12px; max-width: 260px; overflow: hidden; text-overflow: ellipsis; }
.foot { color: var(--dim); font-size: 12px; margin-top: 16px; padding: 0 32px; }

/* ===========================================================================
 * Modals
 * ------------------------------------------------------------------------ */
.modal { position: fixed; inset: 0; background: var(--overlay);
  display: flex; align-items: flex-start; justify-content: center; padding: 40px 16px; z-index: 40; }
.modal-box { background: var(--panel); border: 1px solid var(--line); border-radius: 16px;
  width: min(980px, 100%); max-height: 85vh; overflow-y: auto; padding: 20px 24px;
  box-shadow: var(--shadow-lg); }
.modal-box.small { width: min(520px, 100%); }
.modal-head { display: flex; justify-content: space-between; align-items: center;
  margin-bottom: 14px; font-weight: 600; font-size: 17px; color: var(--head); }
.modal-head button { border: none; background: none; font-size: 16px; color: var(--dim); padding: 4px 8px; }
.modal-head button:hover { background: var(--panel-2); color: var(--tx); }
.cls-sel { padding: 4px 8px; font-size: 12.5px; font-weight: 500; border-radius: 6px; }
.cls-sel.MONITOR { color: var(--acc); border-color: rgba(46,95,128,.45); }
.cls-sel.MUST_CLEAR { color: var(--ok); border-color: rgba(45,122,79,.45); }
.cls-sel.MUST_CLEAR.viol { color: var(--bad); border-color: rgba(184,50,50,.45); }
.cls-sel.EXEMPT { color: var(--dim); }
.sub-tbl { width: 100%; }
.sub-tbl td, .sub-tbl th { padding: 8px 12px; font-size: 13px; }
.sub-tbl td.zero { color: var(--faint); }
.entries { margin: 4px 0 12px; padding: 10px 12px; background: var(--panel-2);
  border-radius: 8px; font-size: 12.5px; }
.entries .exp { color: var(--dim); white-space: normal; min-width: 240px; }
.mini { font-size: 12px; color: var(--dim); }

/* --- drill-down: business-partner breakdown + range of the overdue --- */
#rows tr { cursor: pointer; }
.dd-top { display: flex; gap: 28px; flex-wrap: wrap; margin-bottom: 14px; }
.dd-top .k { color: var(--dim); font-size: 11px; font-weight: 500;
  text-transform: uppercase; letter-spacing: .06em; }
.dd-top .v { font-size: 20px; font-weight: 600; color: var(--head);
  margin: 4px 0 1px; font-variant-numeric: tabular-nums; }
.dd-top .s { color: var(--dim); font-size: 12px; }

.age-strip { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
  gap: 10px; margin-bottom: 16px; }
.age-chip { background: var(--panel-2); border: 1px solid transparent; border-radius: 10px; padding: 10px 12px; }
.age-chip.old { background: var(--bad-bg); border-color: rgba(184,50,50,.25); }
.age-chip .k { color: var(--dim); font-size: 10px; font-weight: 500;
  text-transform: uppercase; letter-spacing: .06em; }
.age-chip .v { font-size: 15px; font-weight: 600; color: var(--head);
  margin: 3px 0 1px; font-variant-numeric: tabular-nums; }
.age-chip .s { color: var(--dim); font-size: 11px; }

.sub-row { cursor: pointer; }
.sub-row:hover td { background: var(--row-hover); }
.sub-row.nosub td { background: var(--row-info); }
.ent-row > td { padding: 0 12px; }
.ent-row.hidden { display: none; }
.dd-note { margin-top: 12px; white-space: normal; }

/* ===========================================================================
 * Toolbars, chips, odds and ends
 * ------------------------------------------------------------------------ */
.toolbar { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; margin-bottom: 14px; }
.toolbar input[type=search] { min-width: 240px; }
.chip { border: 1px solid var(--line-strong); border-radius: 999px; padding: 5px 13px;
  font-size: 12.5px; color: var(--dim); background: var(--panel); cursor: pointer; }
.chip:hover { border-color: var(--acc); }
.chip.on { color: var(--act-ink); background: var(--act); border-color: var(--act); }
.chip.bad.on { color: var(--act-ink); background: var(--bad); border-color: var(--bad); }

.note { color: var(--dim); font-size: 12.5px; margin: -4px 0 14px; max-width: 70ch; }
.row2 { display: flex; gap: 10px; flex-wrap: wrap; align-items: flex-end; }
.fld { display: flex; flex-direction: column; gap: 4px; }
.fld span { color: var(--dim); font-size: 12px; }
.msgbox { background: var(--panel-2); border: 1px solid var(--line); border-radius: 8px;
  padding: 12px 14px; white-space: pre-wrap;
  font: 12.5px/1.5 ui-monospace, SFMono-Regular, Menlo, monospace; color: var(--dim); }

/* Reconcile: candidate SAERP documents for a handover. Ranked, never applied
   automatically — every row needs a person to press Use. */
.btn { display: inline-block; text-decoration: none; text-align: center;
  background: var(--panel); color: var(--btn-ink); border: 1px solid var(--line-strong);
  border-radius: 6px; padding: 7px 12px; font: inherit; font-weight: 500; cursor: pointer; }
.btn:hover, button.ghost:hover { border-color: var(--acc); background: var(--panel-2); }
.ghost { color: var(--dim); background: none; border-color: var(--line-strong); }
.cand { display: flex; gap: 10px; align-items: flex-start; padding: 10px 12px;
  border: 1px solid var(--line); border-radius: 10px; margin-top: 8px; background: var(--panel); }
.cand small { display: block; color: var(--dim); font-size: 12px; }
.cand small.viol { color: var(--warn); }
.cand-pick { flex: none; }
/* The one that matched line for line, so the eye lands on it first. */
.cand-exact { border-color: var(--ok); background: var(--row-ok); }
.cand-exact strong { color: var(--ok); }

/* ===========================================================================
 * Narrow: the rail becomes a drawer.
 *
 * Under 960px the rail slides off-canvas and the topbar grows a handle. It
 * moves by transform rather than by width so opening it does not reflow every
 * table on the page while it animates.
 * ------------------------------------------------------------------------ */
@media (max-width: 960px) {
  .sidebar { transform: translateX(-100%); transition: transform 200ms cubic-bezier(.32,.72,0,1);
    box-shadow: var(--shadow-lg); }
  body.nav-open .sidebar { transform: none; }
  .content { margin-left: 0; }
  #nav-toggle { display: inline-flex; }
  .scrim { display: block; position: fixed; inset: 0; z-index: 25;
    background: var(--scrim); opacity: 0; pointer-events: none; transition: opacity 200ms; }
  body.nav-open .scrim { opacity: 1; pointer-events: auto; }
  header { padding: 12px 16px; }
  main.work { padding: 16px 16px 0; }
  .foot { padding: 0 16px; }
}
@media (max-width: 560px) {
  .cards { grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 10px; }
  .cards .card { padding: 12px 14px; }
  .cards .card .v { font-size: 21px; }
  h1 { font-size: 19px; }
}
/* No animation for anyone who asked not to have one. */
@media (prefers-reduced-motion: reduce) {
  .sidebar, .scrim, button, .chip { transition: none; }
}

/* ===========================================================================
 * Token pages: the links sent to Discord
 *
 * THE PHONE IS THE BASE CASE, not the fallback. These pages are only ever
 * reached by tapping a DM — one-handed, in a bodega, on whatever phone is in
 * the pocket. Nobody opens them on the operator's monitor. So the narrow
 * layout is what is written plainly here and the wide screen is the media
 * query, rather than a desk table squeezed until it fits.
 *
 * Three of these rules are not cosmetic:
 *
 *   16px CONTROLS. Below that, iOS Safari zooms the viewport when a field
 *     takes focus, so tapping a quantity box shoves the form out from under
 *     the thumb and leaves the page scrolled sideways. It is the single most
 *     common reason a mobile form feels broken, and it is a font size.
 *   44px TARGETS. A mis-tap here is not a mis-tap, it is a quantity against
 *     the wrong item — a stock discrepancy somebody reconciles weeks later.
 *   STACKED ROWS. A table with an input per row cannot be scrolled sideways
 *     and typed into at the same time. Under 560px each row becomes a block
 *     carrying its own column labels, so nothing is ever off-screen.
 *
 * THESE PAGES STILL OWN THEIR PALETTE. They are on the house light theme now
 * rather than the old dark one, but the properties stay declared ON
 * `body.form-page` rather than on `:root`, for the reason at the top of this
 * file: a property set on the element beats one inherited from `:root` in any
 * order, so re-running apply.py cannot reach inside them. The two that
 * actually broke last time — the ground and the ink — are literals, not
 * `var()`, so the page holds even if something later mutates a token.
 *
 * (0,1,1) against (0,0,1) for both `body` rules, so order is not load-bearing.
 * ------------------------------------------------------------------------ */
body.form-page {
  --bg: #FAF7F2; --panel: #FFFFFF; --panel-2: #EEF2F5;
  --line: rgba(44,62,74,.14); --line-strong: rgba(44,62,74,.24);
  --tx: #2C3E4A; --dim: #5C7284; --faint: #8FA3B1;
  --acc: #2E5F80; --act: #C4622D; --act-hover: #A8521F; --act-ink: #FFFFFF;
  --ok: #2D7A4F; --warn: #B87333; --bad: #B83232;
  --ok-bg: #D4EDDF; --ok-fg: #2D7A4F;
  --warn-bg: #F5E6D0; --warn-fg: #8B5A1A;
  --field-bg: #FFFFFF; --field-line: #D0DBE3;
  --overlay: rgba(13, 27, 42, .5);
  background: #FAF7F2;
  color: #2C3E4A;
}

body.form-page { display: flex; justify-content: center;
  padding: 16px 12px 24px; -webkit-text-size-adjust: 100%; }
body.form-page main { width: min(640px, 100%); padding: 0; }
body.form-page h1 { font-size: 21px; line-height: 1.25; margin-bottom: 6px; color: var(--head); }
body.form-page .sub { font-size: 13.5px; }
/* The Discord commands quoted back on the refusal and expiry pages. */
body.form-page code { background: var(--panel-2); border: 1px solid var(--line);
  border-radius: 6px; padding: 2px 7px; word-break: break-all; color: var(--btn-ink);
  font: 13.5px/1.5 ui-monospace, SFMono-Regular, Menlo, monospace; }

body.form-page label { display: block; margin: 16px 0; font-weight: 500; }
body.form-page input, body.form-page textarea, body.form-page select {
  width: 100%; font-size: 16px; padding: 11px 12px; margin-top: 6px;
  min-height: 44px; accent-color: var(--act); }
/* A select with appearance:none loses its arrow on iOS, where it then reads as
   an empty text box and people try to type into it. So it gets one back. The
   stroke is Stone, url-encoded, so it has to track `--faint` by hand. */
body.form-page select { -webkit-appearance: none; appearance: none; padding-right: 38px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8'%3E%3Cpath fill='none' stroke='%238FA3B1' stroke-width='2' d='M1 1.5 6 6.5l5-5'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 13px center; }

/* TYPE-TO-FIND (combo.js). The native select stays in the form and keeps
   submitting -- it is only moved out of sight, so with JS off every one of
   these is still an ordinary select. `.combo-native` is that hiding, and it
   must never become `display:none`: a hidden-by-display control is still
   submitted, but some browsers skip it for constraint validation, and the
   destination select is `required`. Clipping keeps validation working. */
body.form-page .combo { position: relative; margin-top: 6px; }
body.form-page .combo-native { position: absolute; width: 1px; height: 1px;
  opacity: 0; pointer-events: none; clip: rect(0 0 0 0); overflow: hidden; }
body.form-page .combo-input { margin-top: 0; }
body.form-page .combo-list { position: absolute; z-index: 20; left: 0; right: 0; top: 100%;
  margin: 4px 0 0; padding: 4px; list-style: none; max-height: 44vh; overflow-y: auto;
  background: var(--panel); border: 1px solid var(--line-strong); border-radius: 8px;
  box-shadow: 0 10px 28px rgba(0,0,0,.45); }
body.form-page .combo-opt { padding: 11px 12px; border-radius: 6px; font-size: 15px;
  cursor: pointer; white-space: normal; }
body.form-page .combo-opt:hover, body.form-page .combo-opt.on { background: var(--panel-2); }
body.form-page .combo-none { padding: 11px 12px; color: var(--dim); font-size: 14px; }
/* The items filter sits above the table and is not a label/field pair -- it
   filters what is already on screen rather than collecting an answer. */
body.form-page .item-filter { margin: 16px 0 6px; }
body.form-page .item-filter input { margin-top: 0; }
body.form-page .item-filter .note { display: block; margin-top: 6px; font-size: 13px; color: var(--dim); }
/* The filter hides rows with the `hidden` property, and below 560px the table
   collapses to blocks -- `table.items tr { display: block }` is an author rule
   and beats the browser's own `[hidden] { display: none }`, so on a phone, the
   screen this form is FOR, filtered rows would stay visible. The attribute
   selector is more specific than that rule, so this wins wherever it sits. */
body.form-page table.items tr[hidden] { display: none; }
/* The same trap, wider: `body.form-page button { display: block }` and friends
   beat `[hidden]` too, so the receiving page's "Scan the QR" button showed on an
   iPhone, which has no QR reader for it to start, and did nothing when pressed.
   Anything a page script hides stays hidden. */
body.form-page [hidden] { display: none !important; }

body.form-page button { display: block; width: 100%; margin: 10px 0 0;
  padding: 14px 18px; min-height: 48px; font-size: 16px; font-weight: 500; border-radius: 8px; }
/* One obvious way forward. The second button on the receipt is the one that
   needs deciding about, so it is present but visibly not the default. */
body.form-page form button { background: var(--act); border-color: var(--act); color: var(--act-ink); }
body.form-page form button:hover { background: var(--act-hover); border-color: var(--act-hover); }
body.form-page form button.warn { background: var(--panel); border-color: var(--warn); color: var(--warn-fg); }

/* The submit stays in reach at the bottom of a long list of items rather than
   below it, so a storekeeper filling in eight of forty lines does not scroll
   past thirty-two empty boxes to find the button. */
body.form-page .actions { position: sticky; bottom: 0; z-index: 5;
  margin-top: 16px; padding: 12px 0 calc(12px + env(safe-area-inset-bottom));
  background: var(--bg); border-top: 1px solid var(--line); }

body.form-page table { width: 100%; background: var(--panel); border: 1px solid var(--line);
  border-radius: 12px; margin: 16px 0; box-shadow: var(--shadow-sm); }
body.form-page td, body.form-page th { white-space: normal; }
body.form-page td.num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
body.form-page td.num.muted { color: var(--dim); }
/* !mytransfers -- one block per open transfer. */
body.form-page .mine-card { background: var(--panel); border: 1px solid var(--line); border-radius: 12px;
  padding: 14px 16px; margin: 16px 0; box-shadow: var(--shadow-sm); }
body.form-page .mine-card h2 { margin: 0 0 4px; font-size: 18px; }
body.form-page .mine-yours { font-size: 12px; color: var(--ok-fg); font-weight: 500; }
body.form-page .mine-lines { margin: 8px 0; padding-left: 18px; }
body.form-page .mine-lines small { display: block; color: var(--dim); }
body.form-page .mine-card form { margin-top: 10px; border-top: 1px solid var(--line); padding-top: 6px; }
body.form-page .mine-card .hint { color: var(--dim); font-size: 12.5px; margin: 6px 0 0; }
body.form-page .msg-bad { background: var(--bad-bg); border: 1px solid rgba(184,50,50,.35);
  border-radius: 8px; padding: 12px 14px; color: var(--bad-fg); }
body.form-page .done { background: var(--ok-bg); border: 1px solid rgba(45,122,79,.3);
  border-radius: 8px; padding: 12px 14px; color: var(--ok-fg); }

/* Pick lists — which locator the stock is leaving. A row, not a link inside a
   row: the whole band is the target, because the area name alone is about
   nine millimetres of it. */
body.form-page ul.picks { list-style: none; margin: 16px 0; padding: 0; overflow: hidden;
  background: var(--panel); border: 1px solid var(--line); border-radius: 12px;
  box-shadow: var(--shadow-sm); }
body.form-page ul.picks li + li { border-top: 1px solid var(--line); }
body.form-page ul.picks a { display: flex; align-items: center; justify-content: space-between;
  gap: 12px; padding: 13px 16px; min-height: 56px; color: var(--tx); text-decoration: none; }
body.form-page ul.picks a:active { background: var(--panel-2); }
body.form-page ul.picks a:hover { background: var(--panel-2); }
body.form-page .pick-main small { display: block; color: var(--dim); font-size: 12.5px; margin-top: 2px; }
body.form-page .pick-n { flex: none; text-align: right; color: var(--dim);
  font-size: 14px; font-variant-numeric: tabular-nums; }
body.form-page .pick-n em { display: block; font-style: normal; font-size: 11px;
  text-transform: uppercase; letter-spacing: .06em; color: var(--faint); }

/* ---- Narrow: item tables become blocks -------------------------------- */
@media (max-width: 559px) {
  /* The header row goes away, so each figure carries its own name instead.
     Hidden this way rather than display:none because a screen reader should
     still be able to reach it. */
  body.form-page table.items thead { position: absolute; width: 1px; height: 1px;
    overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
  body.form-page table.items tbody { display: block; }
  body.form-page table.items tr { display: block; padding: 12px 14px; border-bottom: 1px solid var(--line); }
  body.form-page table.items tr:last-child { border-bottom: 0; }
  body.form-page table.items td { display: block; padding: 0; border: 0; text-align: left; }
  body.form-page table.items td.num { text-align: left; }
  body.form-page table.items td[data-l]::before { content: attr(data-l) " ";
    color: var(--dim); font-size: 11.5px; text-transform: uppercase; letter-spacing: .06em; }
  body.form-page table.items td.entry { margin-top: 8px; }
  body.form-page table.items td.entry::before { display: block; margin-bottom: 2px; }
  body.form-page table.items td.entry input { width: 100%; margin-top: 0; }
}

/* ---- Wide enough for a real table ------------------------------------- */
@media (min-width: 560px) {
  body.form-page { padding: 32px 14px 40px; }
  body.form-page table.items td.entry { text-align: right; }
  body.form-page table.items td.entry input { width: 120px; margin-top: 0; }
  body.form-page button { display: inline-block; width: auto; margin: 12px 10px 0 0; }
  body.form-page .actions { position: static; padding: 0; background: none; border-top: 0; }
}

/* --- The confirmation sheet (confirm.js) -------------------------------- *
 *
 * It rises from the bottom edge on a phone, because that is where the thumb
 * already is and where the button that opened it was; a box dropped in the
 * middle of the screen makes people hunt for the answer. Once there is room
 * for a dialog to be a dialog, it becomes one.
 *
 * Built by script and appended to <body>, so these rules cannot lean on being
 * inside the form -- the buttons carry their own colour rather than inheriting
 * the form's.
 */
body.form-page dialog.confirm {
  width: 100%; max-width: 560px; margin: auto auto 0;
  max-height: 88vh; overflow-y: auto;
  background: var(--panel); color: var(--tx);
  border: 1px solid var(--line); border-bottom: 0; border-radius: 16px 16px 0 0;
  padding: 20px 18px calc(16px + env(safe-area-inset-bottom));
  box-shadow: var(--shadow-lg);
}
body.form-page dialog.confirm::backdrop { background: var(--overlay); }
body.form-page dialog.confirm h2 { font-size: 18px; line-height: 1.3; margin-bottom: 6px;
  color: var(--head); }
body.form-page .cf-where { color: var(--dim); font-size: 13.5px; }
body.form-page .cf-where:empty, body.form-page .cf-foot:empty { display: none; }

/* The read-back itself. Sunk against the sheet rather than raised on it: it is
   what is being checked, not another thing to press. */
body.form-page .cf-lines { list-style: none; margin: 14px 0 0; padding: 0;
  background: var(--panel-2); border: 1px solid var(--line); border-radius: 10px; overflow: hidden; }
body.form-page .cf-lines:empty { display: none; }
body.form-page .cf-lines li { display: flex; align-items: baseline; justify-content: space-between;
  gap: 14px; padding: 10px 12px; font-size: 14px; }
body.form-page .cf-lines li + li { border-top: 1px solid var(--line); }
body.form-page .cf-lines b { flex: none; font-variant-numeric: tabular-nums; }
body.form-page .cf-lines small { display: block; color: var(--dim); font-size: 11.5px; margin-top: 2px; }
body.form-page .cf-foot { color: var(--dim); font-size: 13px; margin-top: 12px; }

body.form-page .cf-act { display: flex; flex-direction: column; gap: 8px; margin-top: 18px; }
body.form-page .cf-act button { margin: 0; }
body.form-page .cf-go { background: var(--act); border-color: var(--act); color: var(--act-ink); }
body.form-page .cf-no { background: var(--panel); border-color: var(--line-strong); color: var(--dim); }
/* A shortage is not the ordinary answer, so its confirm is not dressed as one. */
body.form-page dialog.confirm.is-warn .cf-go { background: var(--warn-bg); border-color: var(--warn); color: var(--warn-fg); }

@media (min-width: 560px) {
  body.form-page dialog.confirm { margin: auto; border-radius: 16px; border-bottom: 1px solid var(--line); }
  /* Reversed so the DOM leads with the confirm (it is the point of the sheet)
     while the eye still reads Go back, then the action. */
  body.form-page .cf-act { flex-direction: row-reverse; justify-content: flex-start; }
  body.form-page .cf-act button { display: inline-block; width: auto; }
}


/* ===========================================================================
 * The theme switch
 *
 * The button shows the theme you would switch TO, so it reads as an offer
 * rather than a status: a moon while the lights are on, a sun while they are
 * off. With no JavaScript at all neither icon is hidden by a `[data-theme]`
 * rule that never matched, so the moon is the one that shows.
 * ------------------------------------------------------------------------ */
#theme-toggle {
  flex: none; width: 38px; height: 38px; padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--panel); border: 1px solid var(--line-strong); border-radius: 8px;
  color: var(--head);
}
#theme-toggle svg { width: 17px; height: 17px; fill: none; stroke: currentColor;
  stroke-width: 1.9; stroke-linecap: round; stroke-linejoin: round; }
#theme-toggle .ic-sun { display: none; }
[data-theme="dark"] #theme-toggle .ic-sun { display: block; }
[data-theme="dark"] #theme-toggle .ic-moon { display: none; }

/* ===========================================================================
 * Dark
 *
 * A SWAP OF THE TOKEN BLOCK, not a second stylesheet. Every rule above reads
 * `--panel`, `--ok-bg`, `--head` and the rest, so nothing up there has to know
 * which theme is on, and a rule added later inherits the dark ground for free.
 *
 * The brand has no dark system of its own, so this is built out of the palette
 * it does define: Midnight is the canvas it was always meant to be, Stone
 * finally becomes the muted text it is described as (2.6:1 on Cream, 5.8:1
 * here), Horizon takes over from Steel for anything interactive, and Ember
 * takes the action colour from Terracotta — white on Terracotta is 4.1:1 and
 * would be the one thing on the screen that got harder to read in the dark, so
 * the fill goes Ember and the ink on it goes Midnight, which is 5.9:1.
 *
 * Anchored on `html[data-theme="dark"] body` — (0,1,2) — so it outranks the
 * light `body` block whatever order they land in, and still cannot be reached
 * by a `:root` that `apply.py` appends later.
 * ------------------------------------------------------------------------ */
html[data-theme="dark"] body {
  --bg: #0D1B2A;                    /* Midnight — the anchor, as the canvas   */
  --panel: #16273A;                 /* the card lifted off it                 */
  --panel-2: #1D3049;               /* heads and sunk surfaces, lifted again  */
  --line: rgba(233, 240, 246, .10);
  --line-strong: rgba(233, 240, 246, .20);
  --tx: #EEF2F5;                    /* Mist — body text on dark               */
  --dim: #8FA3B1;                   /* Stone — what the brand always meant    */
  --faint: #6E8496;
  --acc: #4A90B8;                   /* Horizon — interactive on dark          */
  --act: #E07B45;                   /* Ember — the action fill                */
  --act-hover: #EE9160;
  --act-ink: #0D1B2A;
  --ok: #7FD6A5;
  --warn: #E8B06A;
  --bad: #F08C8C;

  --ok-bg: rgba(45, 122, 79, .28);    --ok-fg: #8FE0B4;
  --warn-bg: rgba(184, 115, 51, .28); --warn-fg: #EFBE84;
  --bad-bg: rgba(184, 50, 50, .28);   --bad-fg: #F5A3A3;
  --info-bg: rgba(74, 144, 184, .26); --info-fg: #93C7E6;
  --mute-bg: rgba(233, 240, 246, .08); --mute-fg: #9FB4C7;

  --row-hover: rgba(233, 240, 246, .05);
  --row-bad: rgba(240, 140, 140, .10);
  --row-warn: rgba(232, 176, 106, .10);
  --row-info: rgba(74, 144, 184, .12);
  --row-ok: rgba(127, 214, 165, .09);

  --field-bg: #112132;
  --field-line: rgba(233, 240, 246, .22);
  --ring: rgba(74, 144, 184, .32);

  --head: #EEF2F5;
  --btn-ink: #D7E2EC;

  --rail: #081321;                  /* deeper than the canvas, so the rail
                                       still reads as the edge of the page    */
  --bg-blur: rgba(13, 27, 42, .9);
  --scrim: rgba(3, 8, 14, .62);
  --overlay: rgba(3, 8, 14, .66);

  /* A shadow is a light-theme device; on a dark ground it is the border that
     separates a card from what is under it. */
  --shadow-sm: 0 1px 2px rgba(0, 0, 0, .40);
  --shadow-md: 0 4px 12px rgba(0, 0, 0, .45);
  --shadow-lg: 0 18px 48px rgba(0, 0, 0, .58);

  background: #0D1B2A;
  color: #EEF2F5;
}

/* The card is a tint on the canvas rather than a white block, so it needs the
   border to be visible rather than nearly not. */
html[data-theme="dark"] .tbl-wrap,
html[data-theme="dark"] .modal-box,
html[data-theme="dark"] .cards .card { border-color: var(--line); }
html[data-theme="dark"] .cards .card { border-color: rgba(233, 240, 246, .07); }

/* Token pages, same swap. (0,2,2), so it outranks their own light block. */
html[data-theme="dark"] body.form-page {
  --bg: #0D1B2A; --panel: #16273A; --panel-2: #1D3049;
  --line: rgba(233,240,246,.12); --line-strong: rgba(233,240,246,.22);
  --tx: #EEF2F5; --dim: #8FA3B1; --faint: #6E8496;
  --acc: #4A90B8; --act: #E07B45; --act-hover: #EE9160; --act-ink: #0D1B2A;
  --ok: #7FD6A5; --warn: #E8B06A; --bad: #F08C8C;
  --ok-bg: rgba(45,122,79,.28); --ok-fg: #8FE0B4;
  --warn-bg: rgba(184,115,51,.28); --warn-fg: #EFBE84;
  --field-bg: #112132; --field-line: rgba(233,240,246,.24);
  --head: #EEF2F5; --btn-ink: #D7E2EC;
  --overlay: rgba(3, 8, 14, .66);
  background: #0D1B2A;
  color: #EEF2F5;
}
/* The iOS select arrow is a url-encoded literal and cannot read a token. */
html[data-theme="dark"] body.form-page select {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8'%3E%3Cpath fill='none' stroke='%238FA3B1' stroke-width='2' d='M1 1.5 6 6.5l5-5'/%3E%3C/svg%3E");
}

/* The Ask widget ships its own styles so a host page cannot break it, which
   also means it cannot follow one. Reached at (0,2,1) against its (0,1,0),
   and only the four surfaces that are a flat white sheet in the dark. */
html[data-theme="dark"] .ask-panel { background: var(--panel); border-color: var(--line); color: var(--tx); }
html[data-theme="dark"] .ask-log { background: var(--bg); }
html[data-theme="dark"] .ask-m.a { background: var(--panel-2); border-color: var(--line); }
html[data-theme="dark"] .ask-form { background: var(--panel); border-top-color: var(--line); }
html[data-theme="dark"] .ask-form textarea { background: var(--field-bg); border-color: var(--field-line); }
html[data-theme="dark"] .ask-foot { background: var(--panel); color: var(--dim); }

/* === house-ui standard (2026-09-15) — appended by tools/house-ui/apply.py; re-run to refresh, do not edit inside === */
/* Geist (Vercel, OFL 1.1), self-hosted from fonts/ — no third-party request, nothing a CSP can block.
   One variable file each for sans and mono covers every weight; swap keeps first paint on the system stack. */
@font-face { font-family: 'Geist'; src: url('fonts/Geist-Variable.woff2') format('woff2'); font-weight: 100 900; font-style: normal; font-display: swap; }
@font-face { font-family: 'Geist Mono'; src: url('fonts/GeistMono-Variable.woff2') format('woff2'); font-weight: 100 900; font-style: normal; font-display: swap; }
:root {
  --pop: #E07B45;
  --pop-hover: #C4622D;
  --pop-bg: #FBECE2;
  --pop-soft: #F0C89A;
  --navy-50: #F1F5F9;
  --navy-100: #E3EAF1;
  --navy-200: #C9D6E2;
  --navy-300: #9FB4C7;
  --navy-500: #4A6A85;
  --navy-700: #1C3A52;
  --navy-900: #0D1B2A;
  --ink: #1F2D3D;
  --ink-2: #566B7D;
  --ink-3: #5C7284;
  --bg: #F6F8FB;
  --radius: 14px;
  --radius-sm: 8px;
  --pill: 999px;
  --line-input: #7F93A3;
}
body { font-family: 'Geist', system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; background-image: none; -webkit-font-smoothing: antialiased; }
code, kbd, pre, .mono { font-family: 'Geist Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: .93em; }
table, .num, .stat .v, .hero-n, .verdict { font-variant-numeric: tabular-nums; }
.stat .v, .hero-n, .hero-stat b, .verdict { font-weight: 500; letter-spacing: -.015em; }
/* air */
main { padding: 32px 32px 96px; }
.card { padding: 26px 30px; margin-bottom: 24px; border-radius: var(--radius); }
.card h2 { font-size: 17px; margin-bottom: 16px; letter-spacing: -.01em; }
.stats { gap: 16px; margin-bottom: 24px; }
.stat { padding: 20px 22px; border-radius: var(--radius); }
td { padding: 12px 10px; } th { padding: 10px 10px; }
.top { padding: 18px 32px; } .tabs { padding-left: 28px; padding-right: 28px; } .tabs button { padding: 11px 16px; }
.live-dot { background: #F0A070; box-shadow: 0 0 0 0 rgba(240,160,112,.6); } .live-dot.dark { background: var(--pop); }
@media (max-width: 720px) { main { padding: 20px 16px 96px; } .card { padding: 18px 18px; } }
/* ⓘ — fewer words on the page (MD, 2026-09-15). A line shows the fact; the reason, the rule, the caveat go behind
   an info icon and appear on hover or focus. <span class="info" tabindex="0" data-tip="…"><svg class="ic">…</svg></span>
   Keyboard reaches it (tabindex), a phone taps it (focus), a screen reader reads aria-label. Never hide the ONLY copy
   of something the MD must act on; the tip is for "why" and "how", not for the figure itself. */
.info { position: relative; display: inline-flex; align-items: center; justify-content: center; width: 16px; height: 16px; margin-left: 4px; border-radius: 50%; color: var(--ink-3); cursor: help; vertical-align: -3px; outline: none; }
.info .ic { width: 14px; height: 14px; }
.info:hover, .info:focus-visible { color: var(--steel); }
.info:focus-visible { box-shadow: 0 0 0 2px var(--sand); }
.info::after { content: attr(data-tip); position: absolute; left: 50%; bottom: calc(100% + 8px); transform: translate(-50%, 4px); width: max-content; max-width: min(300px, 80vw); padding: 8px 11px; border-radius: var(--radius-sm); background: var(--navy); color: #fff; font-size: var(--fs-small); font-weight: 400; line-height: 1.45; letter-spacing: 0; text-transform: none; text-align: left; white-space: normal; box-shadow: var(--shadow-pop); opacity: 0; pointer-events: none; transition: opacity var(--ease), transform var(--ease); z-index: 20; }
.info::before { content: ""; position: absolute; left: 50%; bottom: calc(100% + 3px); transform: translateX(-50%); border: 5px solid transparent; border-top-color: var(--navy); opacity: 0; transition: opacity var(--ease); }
.info:hover::after, .info:focus::after, .info:hover::before, .info:focus::before { opacity: 1; }
.info:hover::after, .info:focus::after { transform: translate(-50%, 0); }
.info.left::after { left: 0; transform: translate(0, 4px); } .info.left:hover::after, .info.left:focus::after { transform: none; } .info.left::before { left: 8px; }
.info.right::after { left: auto; right: 0; transform: translate(0, 4px); } .info.right:hover::after, .info.right:focus::after { transform: none; } .info.right::before { left: auto; right: 3px; }
.info.below::after { bottom: auto; top: calc(100% + 8px); } .info.below::before { bottom: auto; top: calc(100% + 3px); border-top-color: transparent; border-bottom-color: var(--navy); }
.on-dark .info, .top .info, .hero .info { color: rgba(255,255,255,.6); } .on-dark .info:hover, .top .info:hover, .hero .info:hover { color: #fff; }
.on-dark .info::after, .top .info::after, .hero .info::after { background: #fff; color: var(--ink); } .on-dark .info::before, .top .info::before, .hero .info::before { border-top-color: #fff; }
/* graphics — the page gets a face or a shape, not another sentence. .rings paints two concentric arcs in the pop colour
   off one corner of a dark panel (hero, header card); .face is a gradient avatar with initials, or an <img> when a
   photo is on file (Discord avatar), always round; .art-panel is a tinted panel for a heading + illustration slot. */
.rings { position: relative; overflow: hidden; }
.rings::before, .rings::after { content: ""; position: absolute; border-radius: 50%; pointer-events: none; z-index: 0; }
.rings::before { width: 420px; height: 420px; right: -140px; top: -180px; background: radial-gradient(circle, transparent 54%, var(--pop) 54.5%, var(--pop) 62%, transparent 62.5%); opacity: .55; }
.rings::after { width: 260px; height: 260px; right: -60px; top: -100px; background: radial-gradient(circle, var(--pop) 0 46%, transparent 47%); opacity: .35; }
.rings > * { position: relative; z-index: 1; }
.rings.soft::before { opacity: .22; } .rings.soft::after { opacity: .14; }
.face { flex: 0 0 auto; width: 48px; height: 48px; border-radius: 50%; display: inline-flex; align-items: center; justify-content: center; color: #fff; font-weight: 600; font-size: 16px; letter-spacing: .02em; background: linear-gradient(135deg, var(--navy-700), var(--pop)); box-shadow: 0 0 0 3px #fff, 0 0 0 4px var(--line-2); overflow: hidden; }
.face img { width: 100%; height: 100%; object-fit: cover; display: block; }
.face.lg { width: 72px; height: 72px; font-size: 24px; } .face.sm { width: 32px; height: 32px; font-size: 12px; box-shadow: none; }
.face.a { background: linear-gradient(135deg, #1C3A52, #4A90B8); } .face.b { background: linear-gradient(135deg, #2E5F80, #E07B45); } .face.c { background: linear-gradient(135deg, #E07B45, #F0C89A); } .face.d { background: linear-gradient(135deg, #0D1B2A, #2E5F80); }
.faces { display: flex; } .faces .face { margin-left: -10px; box-shadow: 0 0 0 2px #fff; } .faces .face:first-child { margin-left: 0; }
.art-panel { display: flex; align-items: center; justify-content: space-between; gap: 24px; background: var(--pop-bg); border-radius: var(--radius); padding: 28px 32px; margin-bottom: 24px; }
.art-panel.navy { background: var(--navy-50); }
.art-panel h2 { font-size: 22px; font-weight: 600; letter-spacing: -.015em; margin: 0 0 6px; color: var(--navy-900); } .art-panel p { margin: 0; color: var(--ink-2); }
.art-panel .art { flex: 0 0 auto; width: 160px; height: 120px; }
.pop { color: var(--pop); } .bg-pop { background: var(--pop-bg); }
.btn.pop { background: var(--pop); border-color: var(--pop); color: #fff; } .btn.pop:hover { background: var(--pop-hover); border-color: var(--pop-hover); }
.chip.pop { background: var(--pop-bg); color: var(--pop-hover); }
/* === /house-ui standard === */
