/*
 * Shared foundations and legacy benefit-list primitives. This is the first responsibility in the
 * components.css cascade declared by ../components.css; host builds flatten every responsibility into the
 * same external components.css asset used by web and OTA/iOS.
 */
* {
  box-sizing: border-box;
}
body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font-sans);
  font-size: var(--text-md);
  letter-spacing: -0.006em;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

/* ===== Broadsheet shell: full-width masthead lockup on top, main below (BROAD-V-SHELL converted the
   former left sidebar to an editorial topbar — the lockup / tabrow / syncchip styles live in the
   V-SHELL section near the foot of this file). ===== */
.shell {
  display: block;
  min-height: 100vh;
}
.export-locked {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  color: var(--ink-3);
  cursor: default;
}

@media (hover: hover) and (pointer: fine) {
  .export-locked:hover {
    background: transparent;
    color: var(--ink-3);
  }
}
.export-upsell {
  margin-top: var(--space-1);
  border-top: 1px solid var(--line);
  color: var(--blue);
  font-weight: 600;
}
.lock-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: 1px var(--space-2);
  border: 1px solid var(--amber-border);
  border-radius: 999px;
  background: var(--amber-bg);
  color: var(--amber-head);
  font-size: var(--text-xs);
  font-weight: 700;
  letter-spacing: 0.02em;
}
.lock-chip-ic {
  display: inline-flex;
}
.lock-chip-ic .icon {
  width: 11px;
  height: 11px;
}
.sec-head--locked {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}
.link-btn {
  padding: 0;
  border: none;
  background: transparent;
  color: var(--blue);
  font-weight: 600;
  cursor: pointer;
}
/* PARITY-13: a link-style button needs mouse-hover feedback like the other text-link buttons
   (.fb-reset/.fb-mini) — without it a clickable control (the cancel-flow buttons in growth.ts) reads
   as static text. Keyboard focus is already covered by the global :focus-visible outline below. */
@media (hover: hover) and (pointer: fine) {
  .link-btn:hover {
    text-decoration: underline;
  }
}
.main {
  min-width: 0;
  padding: 22px 30px 40px;
}
/* the flat airy list: one panel, hairline rows, no per-item boxes (Design B) */
.flist {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 2px 18px;
}
.fl-group,
.fl-subgroup {
  display: contents;
}
.fl-lead {
  font-size: 12.5px;
  color: var(--ink-3);
  font-variant-numeric: tabular-nums;
  margin: 0 0 14px 2px;
}
.fl-lead b {
  color: var(--ink);
  font-weight: 600;
}
/* group headers are real <button>s (RD-53): reset chrome, lay out caret | text | total */
.fl-gh {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 15px 0 9px;
  border: none;
  border-bottom: 1.5px solid var(--line);
  background: transparent;
  font: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
}
.fl-gh:first-child {
  padding-top: 8px;
}
.gh-text {
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
}
.g-caret {
  display: inline-flex;
  color: var(--ink-3);
  transition: transform var(--t-2) var(--ease-out);
}
.g-caret.closed {
  transform: rotate(-90deg);
}
.g-caret .icon {
  width: 15px;
  height: 15px;
}
.fl-gt {
  font-weight: 700;
  font-size: 14px;
  color: var(--ink);
}
.fl-gm {
  font-size: 11.5px;
  color: var(--ink-3);
  font-weight: 500;
}
.fl-gv {
  font-weight: 700;
  font-size: 14px;
  color: var(--red);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.fl-gv.calm {
  color: var(--ink-2);
}
.empty-pad {
  padding: 40px 0;
}
/* flat rows are flex so the card-art media + owner avatar are optional (Design B) */
.fl-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 0;
  border-bottom: 1px solid var(--line-2);
}
.fl-row:last-child {
  border-bottom: none;
}
.fl-sub {
  color: var(--ink-3);
}
/* collapsible group headers — click to minimize (a caret + pointer only where toggleable) */
@media (hover: hover) and (pointer: fine) {
  .fl-gh:hover .fl-gt {
    color: var(--blue);
  }
}
.fl-gv small {
  font-size: 10.5px;
  font-weight: 500;
  color: var(--ink-3);
}
.fl-warn-head {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  padding: 11px 0 5px;
}
.fl-warn--crit .fl-warn-head {
  color: var(--red);
}
.fl-warn--soon .fl-warn-head {
  color: var(--amber-head);
}
/* compact Act now rows + a one-line "already used up" chip strip per group (RD-48) */
.flist-compact .fl-row.fl-compact {
  padding: 6px 0;
  gap: 10px;
}
.flist-compact .cidmedia,
.flist-compact .cidart {
  width: 34px;
  height: 22px;
}
.flist-compact .ownav {
  width: 15px;
  height: 15px;
  font-size: 9px;
}
.flist-compact .fl-v {
  flex-direction: row;
  align-items: baseline;
  gap: 5px;
}
.fl-used {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px;
  padding: 7px 0 9px 44px;
  border-bottom: 1px solid var(--line-2);
}
.fl-used-lbl {
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.fl-uchip {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: 11.5px;
  color: var(--ink-3);
  font-variant-numeric: tabular-nums;
  background: var(--line-2);
  border-radius: 999px;
  padding: 2px 9px 2px 3px;
}
.fl-uav {
  width: 15px;
  height: 15px;
  border-radius: 50%;
  color: var(--white);
  font-size: 9px;
  font-weight: 700;
  display: flex;
  align-items: center;
  justify-content: center;
}
.fl-brow .fl-nm {
  white-space: normal;
}
/* hierarchy: the amount left is the hero; the card/benefit label is quiet context
   (owner: "what's important is the amount left — don't highlight the card"). RD-49 */
.fl-brow .fl-nm b {
  font-weight: 500;
  color: var(--ink-2);
}
/* collapsible card sections in Explore By-card (RD-49) */
/* card-section header is a <button> now (RD-53) — reset chrome, keep the divider */
.acct-head {
  display: flex;
  align-items: center;
  gap: 13px;
  padding: 14px 18px;
  cursor: pointer;
  width: 100%;
  border: none;
  border-bottom: 1px solid var(--line-2);
  background: var(--surface);
  font: inherit;
  color: inherit;
  text-align: left;
}

@media (hover: hover) and (pointer: fine) {
  .acct-head:hover {
    background: var(--line-2);
  }
}
.acct-caret {
  color: var(--ink-3);
  display: flex;
  flex: none;
  /* VELVET-8: the caret rotates through the shared motion scale as the card opens/closes. */
  transition: transform var(--t-3) var(--ease-in-out);
}
.acct.closed .acct-caret {
  transform: rotate(-90deg);
}
.acct.closed .acct-head {
  border-bottom: none;
}
/* benefit sub-groups inside Cycle (RD-50): a lighter, indented collapsible <button> */
.fl-subh {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: 8px;
  width: 100%;
  padding: 9px 0 8px 16px;
  border: none;
  border-bottom: 1px solid var(--line-2);
  background: transparent;
  font: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
}
.fl-subh .g-caret .icon {
  width: 13px;
  height: 13px;
}

@media (hover: hover) and (pointer: fine) {
  .fl-subh:hover .fl-subt {
    color: var(--blue);
  }
}
.fl-subt {
  font-weight: 600;
  font-size: 12.5px;
  color: var(--ink);
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.fl-subr {
  font-size: 11px;
  color: var(--ink-3);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.fl-subrows {
  padding-left: 28px;
}
/* RD-51: the fraction row — money left / cap, identical across every grouping.
   Amount (green hero) left, the differing detail in the middle, deadline right. */
.fl-brow {
  display: grid;
  grid-template-columns: 110px minmax(0, 1fr) auto;
  gap: 16px;
  align-items: center;
  padding: 10px 18px;
}
/* a benefit the issuer stopped returning (AUD-147/D36): kept but demoted — greyed, not actionable */
.fl-brow--missing {
  opacity: 0.55;
}
.fl-amt {
  text-align: right;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
.fl-amt b {
  font-weight: 700;
  font-size: 15px;
  letter-spacing: -0.01em;
}
.fl-amt b.green {
  color: var(--green);
}
/* a near-empty credit ($10 of $209) is muted vs a full one (RD-68) */
.fl-amt b.green-low {
  color: var(--green-low);
}
.fl-amt b.grey {
  color: var(--ink-4);
}
/* a perk you HAVE (lounge access), not a dollar amount (RD-67) */
.fl-incl {
  font-size: 12px;
  font-weight: 500;
  color: var(--ink-3);
}
.fl-amt b.ink {
  color: var(--ink);
}
.fl-amt .frac {
  font-size: 12.5px;
  color: var(--ink-4);
  font-weight: 500;
}
.bl-mid {
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 9px;
  font-size: 13px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.bl-card {
  color: var(--ink-3);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.bl-credit {
  color: var(--ink);
  font-weight: 500;
}
.fl-when2 {
  font-size: 11.5px;
  white-space: nowrap;
}
/* The manual "mark used" affordance (EPIC MARK-USED, MU-5): a quiet inline pill beside the deadline cell,
   offered only on a stale-tracker credit (the read model's `canMarkUsed`). Reads calm until hovered so it
   never competes with the amount/deadline; the icon inherits the button colour. */
.mark-used {
  display: inline-flex;
  gap: var(--space-1);
  align-items: center;
  margin-left: var(--space-2);
  padding: 2px var(--space-2);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: none;
  color: var(--ink-3);
  font-size: var(--text-xs);
  font-weight: 600;
  white-space: nowrap;
  cursor: pointer;
}

@media (hover: hover) and (pointer: fine) {
  .mark-used:hover {
    border-color: var(--blue);
    color: var(--blue);
  }
}
.mark-used .icon {
  width: 13px;
  height: 13px;
}
/* The "undo" variant (EPIC MARK-CROSS-CLIENT, MW-9): same quiet pill, but it sits on an already-"used up"
   row, so its hover reverts to a neutral ink (a correction) rather than the blue call-to-action the mark
   pill uses — undo is a step back, not a primary action. */
@media (hover: hover) and (pointer: fine) {
  .mark-used--undo:hover {
    border-color: var(--ink-3);
    color: var(--ink-2);
  }
}
/* one urgency scale (RD-62): critical → red, soon → amber, near → muted, quiet → grey */
.d-crit {
  color: var(--red);
  font-weight: 600;
}
.d-soon {
  color: var(--amber);
  font-weight: 500;
}
.d-near {
  color: var(--ink-2);
}
.d-used {
  color: var(--ink-4);
}
.d-cal {
  color: var(--ink-3);
}
.flist-compact .fl-brow {
  padding: 6px 18px;
}
/* By login: the owner's card panels nested under a login header (RD-51) */
.login-block {
  margin-bottom: 18px;
}
.login-cards {
  display: grid;
  gap: 12px;
  padding: 12px 0 2px;
}
.login-cards .acct {
  margin-bottom: 0;
}
/* a plain (non-collapsible) list header — Upcoming renewals, Loyalty balances (RD-54/58) */
.flist-head {
  display: flex;
  align-items: baseline;
  gap: 9px;
  padding: 15px 0 9px;
  border-bottom: 1.5px solid var(--line);
}
/* Bonuses: spent / target as a fraction, then the goal progress bar (RD-55) */
.bonus-amt {
  font-size: 13px;
  color: var(--ink-2);
  margin-bottom: 6px;
  font-variant-numeric: tabular-nums;
}
.bonus-amt b {
  /* POP: the spent-so-far figure is the bonus card's focal number — money-green, larger, glowing. */
  color: var(--green);
  font-size: var(--text-lg);
  font-weight: 700;
  text-shadow: var(--glow-text);
}
.bonus-desc {
  font-size: 12px;
  color: var(--ink-3);
  margin-top: 6px;
  line-height: 1.45;
}
/* gap below the bonuses panel before the renewal callouts on Annual (RD-75) */
.annual-section {
  margin-bottom: 14px;
}
/* POP: a gradient hairline under a section (fades left→right) — a softer divider than a flat line. */
.annual-section::after {
  content: "";
  display: block;
  height: 1px;
  margin-top: 14px;
  background: linear-gradient(90deg, var(--line), transparent);
}
/* Credit history (PRD-C) */
.vh-card {
  padding: var(--space-3) 0;
  border-top: 1px solid var(--line-2);
  display: flex;
  align-items: flex-start;
  gap: var(--space-4);
}
.vh-ring {
  flex: 0 0 auto;
}
.vh-ring .ring {
  width: 88px;
  height: 88px;
}
.vh-body {
  flex: 1;
  min-width: 0;
}
.vh-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 4px;
}
.vh-name {
  font-weight: 700;
  color: var(--ink);
}
.vh-roll {
  font-size: 11.5px;
  color: var(--ink-3);
  font-variant-numeric: tabular-nums;
}
.vh-cap {
  color: var(--green);
}
.vh-net {
  font-weight: 800;
}
.vh-net--pos {
  color: var(--green);
}
.vh-net--neg {
  color: var(--red);
}
.vh-prow {
  display: grid;
  grid-template-columns: 1fr auto auto auto;
  gap: 10px;
  align-items: baseline;
  padding: 3px 0 3px 10px;
  font-size: 12px;
  font-variant-numeric: tabular-nums;
}
.vh-pname {
  color: var(--ink-2);
}
.vh-pwhen {
  color: var(--ink-3);
  font-size: 11px;
}
.vh-pcap {
  color: var(--green);
  font-weight: 700;
}
.vh-pfor {
  color: var(--ink-3);
}
/* by-category breakdown (PRD-C) — where the captured value came from, by theme */
.vh-cats {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-bottom: var(--space-3);
}
.vh-cat {
  display: inline-flex;
  align-items: baseline;
  gap: 6px;
  padding: 3px 11px;
  font-size: var(--text-xs);
  font-variant-numeric: tabular-nums;
  background: var(--bg-2);
  border-radius: 999px;
}
.vh-cat-name {
  font-weight: 700;
  color: var(--ink-2);
}
.vh-cat-cap {
  font-weight: 700;
  color: var(--green);
}
.vh-cat-for {
  color: var(--ink-3);
}
.vh-cat-share {
  color: var(--ink-3);
  font-weight: 700;
}
/* loyalty balances row (RD-58) */
.ref-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 10px 0;
  border-bottom: 1px solid var(--line-2);
}
.ref-row:last-child {
  border-bottom: none;
}
.ref-prog {
  font-size: 13px;
  color: var(--ink-2);
}
.ref-prog b {
  color: var(--ink);
  font-weight: 600;
}
.ref-who {
  font-size: 11.5px;
  color: var(--ink-3);
  margin-left: 6px;
}
.ref-exp {
  font-size: 11px;
  color: var(--ink-3);
  margin-left: 8px;
}
.ref-amt {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  font-weight: 700;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.ref-val {
  font-size: 11px;
  font-weight: 600;
  color: var(--ink-3);
}
/* POP: the loyalty balance as a hero — a subtle --grad-navy tile, a big balance number, and a
   glowing money-green cash-value sub. On the navy fill the identity text flips to on-dark tones. */
.ref-row--hero {
  margin-bottom: var(--space-2);
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius);
  border-bottom: none;
  background: var(--grad-navy);
  /* GRAPHIC-KIT §3: the glass-highlight top edge lifts the navy loyalty hero off its ground. */
  box-shadow: var(--shadow), var(--inset-hi);
}
.ref-row--hero .ref-prog,
.ref-row--hero .ref-prog b {
  color: var(--white);
}
.ref-row--hero .ref-who,
.ref-row--hero .ref-exp {
  color: var(--on-dark-60);
}
.ref-row--hero .ref-amt {
  color: var(--white);
  font-size: var(--text-2xl);
  letter-spacing: -0.02em;
}
.ref-row--hero .ref-val {
  /* the ≈ $… cash value — a light money-green legible on the dark navy hero tile. */
  color: var(--green-low);
  font-size: var(--text-sm);
  text-shadow: var(--glow-text);
}
.fl-empty,
.fl-usedup {
  font-size: 12px;
  color: var(--ink-3);
  padding: 11px 0;
}
.fl-usedup {
  border-top: 1px solid var(--line-2);
}
.fl-when {
  font-size: 11.5px;
  color: var(--ink-3);
  text-align: right;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.fl-when.soon {
  color: var(--amber);
  font-weight: 600;
}
.fl-when small {
  color: var(--ink-3);
  font-weight: 400;
}
.provider-banner {
  display: contents;
}
/* the decision callouts (RD-63): each urgency state has its own tint, accent edge,
   and head colour so a posted refund-window never looks like an upcoming renewal */
.fl-warn {
  border: 1px solid var(--line);
  border-left: 3px solid var(--ink-3);
  border-radius: var(--radius);
  padding: 2px 18px 8px;
  margin-bottom: 14px;
}
.fl-warn--crit {
  background: var(--red-bg);
  border-color: var(--red-border);
  border-left-color: var(--red);
}
.fl-warn--soon {
  background: var(--amber-bg);
  border-color: var(--amber-border);
  border-left-color: var(--amber);
}
.fl-warn-row {
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 9px 0;
}
.fl-warn-ic {
  display: flex;
  flex: none;
}
.fl-warn-ic .icon {
  width: 15px;
  height: 15px;
}
.fl-warn-tx {
  font-size: 12.5px;
  color: var(--ink-2);
}
.fl-warn--crit .fl-warn-tx {
  color: var(--red-text);
}
.fl-warn--soon .fl-warn-tx {
  color: var(--amber-text);
}
.fl-warn-tx b {
  color: var(--ink);
  font-weight: 600;
}
/* A card reference that links to its Explore overview (RD-77): a bare button that
   keeps its content's styling; only the label name underlines + turns violet on hover. */
.card-link {
  display: inline;
  background: none;
  border: none;
  padding: 0;
  margin: 0;
  font: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
}

@media (hover: hover) and (pointer: fine) {
  .card-link:hover .bl-credit,
  .card-link:hover b {
    color: var(--blue);
    text-decoration: underline;
  }
}
.card-link:focus-visible {
  outline: 2px solid var(--blue);
  outline-offset: 2px;
  border-radius: 4px;
}
/* The signup-bonus identity row (AR72-UI-8): lay the card art + title as flex tracks with a
   token gap, so the absolutely-positioned `.ownav` avatar (right: -6px off the thumbnail) sits
   in its own track and never pokes over the first characters of the card title. Scoped to
   `.bonus-fig .card-link` — the class the bonus panel actually emits (fees-bonuses.ts). */
.bonus-fig .card-link {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-width: 0;
}
/* Hover-preview popover (RD-78): wrapper anchors the floating card overview; CSS reveals
   it on hover/focus, so the click-through stays untouched. */
.card-link-wrap {
  position: relative;
  display: inline-flex;
}
.card-preview {
  position: absolute;
  z-index: 50;
  left: 0;
  top: calc(100% + 8px);
  width: 300px;
  max-width: 80vw;
  padding: 12px 14px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: 0 12px 32px var(--shadow-pop);
  opacity: 0;
  visibility: hidden;
  transform: translateY(-4px);
  transition:
    opacity var(--t-1) var(--ease-out),
    transform var(--t-1) var(--ease-out),
    visibility var(--t-1);
  pointer-events: none;
}

@media (hover: hover) and (pointer: fine) {
  .card-link-wrap:hover .card-preview {
    opacity: 1;
    visibility: visible;
    transform: translateY(0);
  }
}
/* Keyboard reveal stays reachable on EVERY device (not gated behind pointer:fine) —
   a tablet-with-keyboard user tabbing in must still get the preview. */
.card-link-wrap:focus-within .card-preview {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
}
.cp {
  display: flex;
  flex-direction: column;
  gap: 7px;
}
.cp-head {
  display: flex;
  align-items: center;
  gap: 9px;
}
.cp-name {
  font-weight: 650;
  color: var(--ink);
  font-size: 13px;
  line-height: 1.3;
}
.cp-sub {
  font-size: 11.5px;
  color: var(--ink-2);
}
.cp-sub b {
  color: var(--green);
  font-weight: 600;
}
.cp-rows {
  display: flex;
  flex-direction: column;
  gap: 5px;
  margin-top: 2px;
}
.cp-row {
  display: flex;
  align-items: baseline;
  gap: 8px;
  font-size: 12px;
}
.cp-amt {
  flex: 0 0 86px;
  text-align: right;
  font-variant-numeric: tabular-nums;
}
.cp-cn {
  color: var(--ink-2);
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.cp-more,
.cp-empty {
  font-size: 11.5px;
  color: var(--ink-3);
}
/* Settings: a checkbox row with a sub-hint (RD-78) */
.check-row {
  display: flex;
  align-items: flex-start;
  gap: 9px;
  cursor: pointer;
}
.check-row input {
  margin-top: 2px;
}
.check-hint {
  display: block;
  font-size: 12px;
  color: var(--ink-3);
  margin-top: 2px;
  font-weight: 400;
}
/* Bonuses */
.bonus-row {
  padding: 13px 0;
  border-bottom: 1px solid var(--line-2);
}
.bonus-row:last-child {
  border-bottom: none;
}
.bonus-id {
  flex: 1;
  min-width: 0;
}
/* The deadline pill takes its colour from the urgency class on it (RD-65); a passed
   deadline is red, looming amber, far-off quiet, an earned bonus green. */
.bonus-by {
  font-size: 12px;
  font-weight: 600;
  white-space: nowrap;
}
.bonus-by.done {
  color: var(--green);
}
.bonus-pace {
  font-size: 12px;
  color: var(--ink-2);
  margin-bottom: 6px;
}
.bonus-outcome {
  font-size: 12.5px;
  font-weight: 700;
}
.bonus-outcome--earned {
  color: var(--green);
}
.bonus-outcome--expired {
  color: var(--ink-3);
}
.bonus-reward {
  display: block;
  font-size: var(--text-xs);
  font-weight: 700;
  color: var(--ink-2);
  font-variant-numeric: tabular-nums;
}
.bonus-bar {
  height: 7px;
  border-radius: 99px;
  background: var(--line);
  overflow: hidden;
  margin-bottom: 7px;
}
.bonus-fill {
  display: block;
  height: 100%;
  border-radius: 99px;
  /* signup-bonus spend progress is money-positive (matches the green spend ring) → money-green, not
     the orange interaction accent (BROAD-F1 accent/money decouple). */
  background: var(--pos);
}
.bonus-pct {
  color: var(--pos);
  font-weight: 600;
}
/* Explore: card sections + group lists + facet rail */
.explore {
  display: flex;
  align-items: flex-start;
  gap: 22px;
}
.explore-main {
  flex: 1;
  min-width: 0;
}
.ex-controls {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin-bottom: 16px;
}
.ex-controls .search {
  margin-left: auto;
}
/* VELVET-4: the benefit list dims while a re-query is in flight (the `queryPending` STATE drives the
   `--pending` modifier — no view timer). The short opacity transition keeps the settle from popping. */
.benefit-list {
  transition: opacity var(--t-1) var(--ease-out);
}
.benefit-list--pending {
  opacity: 0.55;
}
/* The 2px azure progress hair under the controls row, rendered only while pending. Its reveal is DELAYED
   150ms so a fast (in-memory) query that resolves first never flashes it; the inner sliver slides to read
   as indeterminate progress. */
.query-hair {
  height: 2px;
  margin: -8px 0 12px;
  border-radius: 999px;
  background: var(--sky);
  overflow: hidden;
  opacity: 0;
  animation: query-hair-reveal var(--t-1) var(--ease-out) var(--t-2) forwards;
}

.query-hair::after {
  content: "";
  display: block;
  width: 38%;
  height: 100%;
  border-radius: 999px;
  background: var(--azure);
  animation: query-hair-slide calc(var(--t-4) * 2) var(--ease-in-out) infinite;
}

@keyframes query-hair-reveal {
  to {
    opacity: 1;
  }
}

@keyframes query-hair-slide {
  0% {
    transform: translateX(-100%);
  }
  100% {
    transform: translateX(360%);
  }
}

@media (prefers-reduced-motion: reduce) {
  .benefit-list {
    transition: none;
  }
  .query-hair {
    animation-delay: 0s;
    animation-duration: 0s;
  }
  .query-hair::after {
    width: 100%;
    animation: none;
  }
}
.acct {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  margin-bottom: 14px;
  overflow: hidden;
}
/* a card sitting in its renewal refund window gets a red edge + a badge (RD-73) */
.acct.acct-crit {
  border-color: var(--red-border);
  border-left: 3px solid var(--red);
}
.acct-flag {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-top: 6px;
  font-size: 11.5px;
  font-weight: 600;
  padding: 3px 9px;
  border-radius: 999px;
}
.acct-flag .icon {
  width: 13px;
  height: 13px;
}
.acct-flag.crit {
  color: var(--red);
  background: var(--red-bg);
}
.acct-flag.info {
  color: var(--blue-head);
  background: var(--blue-bg);
}
.acct-flag.muted {
  color: var(--ink-3);
  background: var(--grey-bg);
}
/* a card we've stopped seeing (PRD-D): quiet, greyed, set apart — kept, never deleted */
.acct--missing {
  opacity: 0.66;
}
/* card lifecycle actions (PRD-A): a quiet row of pill buttons under a card's benefits */
.acct-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  padding: 12px 0 4px;
}
.acct-act {
  font-size: 12px;
  font-weight: 600;
  color: var(--ink-2);
  background: var(--line-2);
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 5px 12px;
  cursor: pointer;
}

@media (hover: hover) and (pointer: fine) {
  .acct-act:hover {
    background: var(--grey-bg);
    color: var(--ink);
  }
}
.acct-act[data-card-action="forget"] {
  color: var(--red);
}

@media (hover: hover) and (pointer: fine) {
  .acct-act[data-card-action="forget"]:hover {
    background: var(--red-bg);
    border-color: var(--red-border);
  }
}
.missing-group {
  margin-top: 14px;
}
.acct-id {
  flex: 1;
  min-width: 0;
}
.acct-name {
  margin: 0;
  font-size: 14.5px;
  font-weight: 600;
  color: var(--ink);
}
.acct-meta {
  font-size: 11.5px;
  color: var(--ink-3);
  margin-top: 2px;
}
.acct-avail {
  font-weight: 700;
  /* POP: the per-card "$X left" is the panel's focal number — larger, money-green, glowing. */
  font-size: var(--text-xl);
  color: var(--green);
  text-shadow: var(--glow-text);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.acct-avail span {
  font-size: 11px;
  color: var(--ink-3);
  font-weight: 500;
}
/* VELVET-8b: the benefit body is now kept MOUNTED when the card collapses (`.acct-body` wraps the list
   in card-section.ts), so open AND close both ride ONE grid-template-rows 1fr→0fr transition through the
   shared motion scale — a symmetric glide either way, replacing the old open-only keyframe (which the
   instant node-drop made asymmetric). The row track is the animated axis; `.acct-list` clips its content
   to 0 as the track closes. The global prefers-reduced-motion guard zeroes the transition. */
.acct-body {
  display: grid;
  grid-template-rows: 1fr;
  transition: grid-template-rows var(--t-3) var(--ease-in-out);
}
.acct.closed .acct-body {
  grid-template-rows: 0fr;
}
.acct-list {
  border: none;
  border-radius: 0;
  padding: 2px 18px;
  /* min-height:0 + hidden lets the grid row collapse to 0 and clip the padded body cleanly. */
  min-height: 0;
  overflow: hidden;
}
/* POP: subtle zebra on the card-panel benefit rows so a long ledger stays readable at a glance. */
.acct-list .fl-row:nth-child(even) {
  background: var(--tile-2);
}
.ref-list {
  margin-top: 14px;
}
.brand {
  display: flex;
  align-items: center;
  gap: 12px;
}
.section-icon .icon {
  width: 15px;
  height: 15px;
  color: var(--ink-3);
}
.icon {
  width: 16px;
  height: 16px;
  flex: none;
}
/* The base .btn is a neutral bordered button on the cream/light surface (REDESIGN — the prior
   dark-topbar leftover with white text on an on-dark wash rendered invisible on cream). Modifiers:
   `--primary` = filled money-green accent, `--ghost` = transparent. */
.btn {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  background: var(--surface);
  color: var(--ink);
  border: 1px solid var(--line);
  padding: 8px 14px;
  border-radius: var(--radius-xs);
  font-size: 13px;
  font-weight: 550;
  cursor: pointer;
  transition:
    background var(--t-2) var(--ease-out),
    transform var(--t-1) var(--ease-out),
    border-color var(--t-2) var(--ease-out);
}

@media (hover: hover) and (pointer: fine) {
  .btn:hover {
    background: var(--line-2);
    border-color: var(--line);
  }
}
/* the filled primary button uses the DARKER `--accent-down` at rest, not the bright `--accent-fill`:
   white text on the bright orange fill is only ~3.5:1 (below WCAG AA 4.5), whereas `--accent-down`
   clears it; hover darkens further to `--accent-fill-hover` (BROAD-F1 AA fix). */
.btn--primary {
  background: var(--accent-down);
  color: var(--white);
  border-color: var(--accent-down);
}

@media (hover: hover) and (pointer: fine) {
  .btn--primary:hover {
    background: var(--accent-fill-hover);
    border-color: var(--accent-fill-hover);
  }
}
.btn--ghost {
  background: transparent;
  border-color: transparent;
}

@media (hover: hover) and (pointer: fine) {
  .btn--ghost:hover {
    background: var(--line-2);
    border-color: transparent;
  }
}
.menu {
  position: relative;
}
.menu-list.hidden {
  display: none;
}

.content {
  max-width: 1200px;
  margin: 0 auto;
  padding: 32px 32px 96px;
}
/* the primary jobs nav reads as the main navigation, a touch bigger than sub-controls */
.jobs-nav .seg {
  font-size: 14px;
}

.filterbar {
  width: 268px;
  flex: none;
  position: sticky;
  top: 84px;
  align-self: flex-start;
  /* each facet's option list scrolls internally (capped below); the bar scrolls only
     as a last resort if the sum still overflows — no flex-shrink so rows never overlap. */
  max-height: calc(100vh - 108px);
  overflow-y: auto;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 18px 20px 22px;
}
.fb-head {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-bottom: 12px;
  border-bottom: 1px solid var(--line);
}
.fb-head-title {
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.fb-reset {
  background: none;
  border: none;
  color: var(--blue);
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
  padding: 0;
}

@media (hover: hover) and (pointer: fine) {
  .fb-reset:hover:not(:disabled) {
    text-decoration: underline;
  }
}
.fb-reset:disabled {
  color: var(--ink-3);
  cursor: default;
  opacity: 0.5;
}
.fb-group {
  padding: 14px 0 4px;
  border-top: 1px solid var(--line-2);
}
.fb-group:first-of-type {
  border-top: none;
}
.fb-group-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 8px;
  flex: none;
}
.fb-title {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--ink-2);
}
.fb-group-actions {
  display: flex;
  align-items: center;
  gap: 4px;
  opacity: 0;
  transition: opacity var(--t-1) var(--ease-out);
}

@media (hover: hover) and (pointer: fine) {
  .fb-group:hover .fb-group-actions {
    opacity: 1;
  }
}
.fb-dot {
  color: var(--ink-3);
  font-size: 11px;
}
.fb-mini {
  background: none;
  border: none;
  color: var(--blue);
  font-size: 11px;
  font-weight: 600;
  cursor: pointer;
  padding: 0;
}

@media (hover: hover) and (pointer: fine) {
  .fb-mini:hover:not(:disabled) {
    text-decoration: underline;
  }
}
.fb-mini:disabled {
  color: var(--ink-3);
  opacity: 0.4;
  cursor: default;
}
.fb-opts {
  max-height: 210px;
  overflow-y: auto;
  margin: 0 -4px;
  padding: 0 4px;
  scrollbar-width: thin;
  scrollbar-color: var(--line) transparent;
}
.fb-opts::-webkit-scrollbar {
  width: 8px;
}
.fb-opts::-webkit-scrollbar-thumb {
  background: var(--line);
  border-radius: 8px;
}
.fb-opt {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 6px 8px;
  margin: 0 -8px;
  border-radius: 8px;
  font-size: 13px;
  cursor: pointer;
  line-height: 1.3;
}

@media (hover: hover) and (pointer: fine) {
  .fb-opt:hover {
    background: var(--bg);
  }
}
/* POP: a checked facet gets a soft tint fill + accent-tone label so the active filters read at a glance. */
.fb-opt:has(input:checked) {
  background: var(--green-bg);
}
.fb-opt:has(input:checked) .fb-opt-label {
  color: var(--blue);
  font-weight: 600;
}
.fb-opt input {
  /* POP: the branded checkbox tick. */
  accent-color: var(--accent-fill);
  width: 15px;
  height: 15px;
  flex: none;
}
.fb-opt.dim {
  opacity: 0.42;
}
.fb-opt-label {
  flex: 1;
  min-width: 0;
  /* long card names ("Charles Schwab Platinum Card® ····82002") clamp to 2 lines
     with an ellipsis; the full label is in the title tooltip on hover. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  overflow-wrap: anywhere;
}
.fb-count {
  color: var(--ink-3);
  font-variant-numeric: tabular-nums;
  font-size: 12px;
  font-weight: 600;
  flex: none;
}
/* VELVET-7 — ONE press law for every flat pressable: a transform-only scale so a tap
   reads identically on a button, tab, segment, icon button, facet row or card header.
   Transform-only ⇒ no layout shift; `:not(:disabled)` leaves inert controls untouched;
   the keyboard `:focus-visible` ring below is never suppressed; the global
   prefers-reduced-motion block (further down) neutralises the easing. This replaces the
   two divergent `translateY(1px)` rules it consolidates. (The `.btn-primary` CTA keeps
   its own lift-on-hover / press-down pair — a deliberate, in-family choreography.)
   VELVET-7b extends the set to the remaining flat pressables VELVET-7 missed: the
   filter subheader, the annual account-header row, and the masthead owner/sync chips. */
:is(
    .btn,
    .tab,
    .seg,
    .iconbtn,
    .acct-head,
    .card-link,
    .mark-used,
    .fb-opt,
    .danger-btn,
    .fl-subh,
    .an-acct-head,
    .acct-chip,
    .syncchip
  ):active:not(:disabled) {
  transform: scale(0.97);
}
/* the pressables that carried no transition of their own now ease BOTH their press-scale
   and their hover (background / border / ink) in-family — the same combined transition the
   Horizon `.press` primitive uses. (.btn/.seg/.tab/.iconbtn already animate; transform is
   appended to their existing lists in place, so nothing is clobbered.) */
:is(.acct-head, .card-link, .mark-used, .fb-opt, .danger-btn, .fl-subh, .an-acct-head, .syncchip) {
  transition:
    transform var(--t-1) var(--ease-out),
    background var(--t-1) var(--ease-out),
    border-color var(--t-1) var(--ease-out),
    color var(--t-1) var(--ease-out);
}
:focus-visible {
  outline: 2px solid var(--blue);
  outline-offset: 2px;
  border-radius: 6px;
}
/* =====================================================================
   WARM BENTO KIT (REDESIGN) — reusable primitives the job views re-lay
   against (see docs/design-explorations/4-warm-bento.html). Shared by
   the TS shared.ts helpers (bento/tile/chipTint/ring). Colours are all
   tokens; ring stroke tone is a `.ring--*` modifier, never raw hex.
   ===================================================================== */

/* bento grid — responsive auto-fit columns; `--dash` is the dashboard
   main grid where tiles vary their span via `.tile--wide`. */
.bento {
  display: grid;
  gap: var(--space-4);
  /* auto-FILL (not auto-fit) keeps empty tracks so a lone tile stays its natural width
     instead of ballooning to fill the whole row */
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
}
.bento--dash {
  /* ASYMMETRIC two-zone layout (elite re-review): a tall hero panel beside a right rail of the group
     tiles + reclaimable bar. `align-items: stretch` makes the hero grow to the rail's stacked height, so
     the screen fills top-to-bottom instead of leaving full-width bands over a dead lower third. */
  grid-template-columns: minmax(0, 0.82fr) minmax(0, 1.18fr);
  align-items: stretch;
}
.dash-hero {
  display: grid;
}
.dash-hero > .tile {
  height: 100%;
}
.dash-rail {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}
.tile--wide {
  grid-column: span 2;
}

@media (width <= 900px) {
  .bento--dash {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* the bento tile — soft card with warm shadow + generous radius. */
.tile {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: var(--space-4);
  /* POP: a subtle fade-in + ~8px rise entrance, staggered per tile below. Motion is DEFAULT; the
     global reduced-motion guard collapses the duration to nothing. */
  animation: pop-rise var(--t-4) var(--ease-out) both;
}
/* POP: stagger the first few tiles so the grid settles in sequence rather than all at once. */
.bento > .tile:nth-child(1) {
  animation-delay: calc(var(--t-1) / 6);
}
.bento > .tile:nth-child(2) {
  animation-delay: calc(var(--t-1) / 2);
}
.bento > .tile:nth-child(3) {
  animation-delay: calc(var(--t-1) * 5 / 6);
}
.bento > .tile:nth-child(4) {
  animation-delay: calc(var(--t-1) * 7 / 6);
}
.bento > .tile:nth-child(5) {
  animation-delay: var(--t-2);
}
.bento > .tile:nth-child(n + 6) {
  animation-delay: calc(var(--t-2) + var(--t-1) / 3);
}
.tile--hero {
  position: relative;
  display: flex;
  overflow: hidden;
  border-radius: var(--radius-lg);
  /* POP: the loudest moment — the at-risk hero becomes the glowing --grad-money money tile. */
  background: var(--grad-money);
  border: none;
  color: var(--white);
  /* GRAPHIC-KIT §3: the glass-highlight top edge (`--inset-hi`) makes the money hero read as a lit
     pane, layered over its existing float shadow + on-dark hairline ring. */
  box-shadow:
    0 16px 40px -12px var(--money-glow-strong),
    inset 0 0 0 1px var(--on-dark-14),
    var(--inset-hi);
}
/* POP: a soft radial sheen so the hero reads as a lit surface, not a flat block. */
.tile--hero::after {
  content: "";
  position: absolute;
  inset: 0;
  background: radial-gradient(120% 80% at 80% 0%, var(--on-dark-22), transparent 60%);
  pointer-events: none;
}
.tile--flat {
  box-shadow: var(--shadow-sm);
}
/* category-tint tiles — fill is the tint, text the matching deep ink.
   The retired pastel category tints (sage/peach/lilac) are DELETED (HORIZON-S4): Horizon §02 bans
   pastel tint tiles as chrome, and the per-category signal is now the still-life vignette imagery
   (`categoryVignette`). Only the azure interaction tint (--sky) and the ember urgency tint (--rose)
   remain — neither is a category pastel. */
/* POP: tint tiles get a tint-family drop-shadow (the Raycast depth trick) — colored, not neutral. */
.tile-tint--sky {
  background: var(--sky);
  color: var(--sky-ink);
  border-color: var(--sky);
  box-shadow: 0 8px 24px -14px var(--sky-ink);
}
/* rose urgency tint — the critical (refund-window) fee tile, distinct from amber "soon". */
.tile-tint--rose {
  background: var(--red-bg);
  color: var(--red-text);
  border-color: var(--red-border);
  box-shadow: 0 8px 24px -14px var(--red);
}
/* hero tile layout: a tall panel — eyebrow + big at-risk number + count at the top, an honest
   soonest-deadline chip pinned at the bottom (the misleading countdown ring was removed). */
.hero-body {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: var(--space-5);
  height: 100%;
}
.hero-fig {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.hero-eyebrow {
  color: var(--white);
  font-size: var(--text-xs);
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  opacity: 0.82;
}
.hero-num {
  font-size: 52px;
  font-weight: 700;
  line-height: 1;
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
  /* POP: white on the --grad-money tile, with a soft dark halo so it reads crisp on the green. */
  color: var(--white);
  text-shadow: 0 2px 12px var(--on-dark-28);
}
.hero-sub {
  font-size: var(--text-md);
  color: var(--white);
  max-width: 24ch;
  opacity: 0.92;
}
/* the honest countdown — a glassy chip stating the soonest reset, pinned to the panel foot. */
.hero-soon {
  display: inline-flex;
  align-items: center;
  align-self: flex-start;
  gap: var(--space-2);
  padding: 7px 13px;
  border-radius: 999px;
  background: var(--on-dark-14);
  color: var(--white);
  font-size: var(--text-sm);
  font-weight: 600;
}
.hero-soon .icon {
  width: 15px;
  height: 15px;
}
/* signup-bonus on-track / behind status pill. */
.bonus-status {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: var(--text-xs);
  font-weight: 600;
  padding: 3px 11px;
  border-radius: 999px;
}
/* POP: a leading status dot (currentColor) so the pill reads as a live indicator. */
.bonus-status::before {
  content: "";
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
}
.bonus-status--ontrack {
  background: var(--green-bg);
  color: var(--green);
  /* POP: a faint money glow when on track. */
  box-shadow: 0 2px 8px -3px var(--money-glow);
}
.bonus-status--behind {
  background: var(--red-bg);
  color: var(--red-text);
  /* POP: a rose glow when behind pace. */
  box-shadow: 0 2px 8px -3px var(--red);
}

/* leading icon chip — rounded square, tint fill + matching ink. */
.chip-tint {
  width: 40px;
  height: 40px;
  border-radius: var(--radius-sm);
  display: grid;
  place-items: center;
  flex: 0 0 auto;
  /* POP: a top inset highlight + a faint tint-colored drop (currentColor = the chip's ink tone). */
  box-shadow:
    inset 0 1px 0 var(--white),
    0 2px 6px -3px currentColor;
}
.chip-tint--sm {
  width: 34px;
  height: 34px;
  border-radius: var(--radius-xs);
}
/* the retired pastel category chips (sage/peach/lilac) are DELETED (HORIZON-S4) — categories signal via
   the still-life vignette now; only the azure --sky chip remains. */
.chip-tint--sky {
  background: var(--sky);
  color: var(--sky-ink);
}

/* progress ring — an SVG (two concentric circles + a centred label).
   The consumer sizes the wrapper and sets stroke-dasharray/-dashoffset
   inline on `.ring-fill` (lengths, computed in the read model); the
   stroke TONE is a `.ring--*` modifier that reads a status token. */
.ring {
  position: relative;
  display: grid;
  place-items: center;
  flex: 0 0 auto;
  width: 112px;
  height: 112px;
}
.ring--lg {
  width: 148px;
  height: 148px;
}
/* a `.ring--lg` wrapper scales the nested kit ring (the hero at-risk ring) */
.ring--lg .ring {
  width: 148px;
  height: 148px;
}
/* the Explore card panel composes `.acct.tile`; `.acct-head`/`.acct-list` own their
   inner padding, so zero the tile's own padding to avoid double-inset */
.acct.tile {
  padding: 0;
}
/* Fees & bonuses tile bodies (bento re-lay) — chip+title head, identity line, detail line. */
.ft-head {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-bottom: var(--space-2);
}
.ft-title {
  font-weight: 600;
  color: var(--ink);
}
.ft-card {
  font-size: var(--text-sm);
  color: var(--ink-2);
  margin-bottom: var(--space-1);
}
.ft-detail {
  font-size: var(--text-sm);
  color: var(--ink-2);
}
.ft-detail b.ink {
  color: var(--ink);
  font-weight: 600;
}
.ft-when {
  color: var(--ink-3);
}
.bonus-ring {
  display: grid;
  place-items: center;
  margin: var(--space-2) 0;
}
.ring-svg {
  display: block;
  width: 100%;
  height: 100%;
}
.ring-track {
  fill: none;
  stroke: var(--line);
  stroke-width: 12;
}
/* POP: on the green hero tile the cream track vanishes — use a translucent-white track instead. */
.tile--hero .ring-track {
  stroke: var(--on-dark-18);
}
.ring-fill {
  fill: none;
  stroke-width: 12;
  stroke-linecap: round;
  transform: rotate(-90deg);
  transform-origin: center;
  transition: stroke-dashoffset var(--t-3) var(--ease-out);
  /* POP: the arc eases in on mount (opacity + a short sweep from -90deg). The circumference is inline
     per-ring, so the entrance is a fade/sweep rather than a dash-draw — kept generic + tone-faithful.
     DEFAULT motion; the global reduced-motion guard collapses it. */
  animation: ring-in var(--t-4) var(--ease-out) both;
}

@keyframes ring-in {
  from {
    opacity: 0;
    transform: rotate(-140deg);
  }
  to {
    opacity: 1;
    transform: rotate(-90deg);
  }
}

@media (prefers-reduced-motion: reduce) {
  .ring-fill {
    transition: none;
  }
}
/* POP: each ring glows in its own tone (drop-shadow reads currentColor). */
.ring-fill.ring--green {
  stroke: var(--green);
  color: var(--green);
  filter: drop-shadow(0 0 6px currentColor);
}
.ring-fill.ring--amber {
  stroke: var(--amber);
  color: var(--amber);
  filter: drop-shadow(0 0 6px currentColor);
}
.ring-fill.ring--rose {
  stroke: var(--red);
  color: var(--red);
  filter: drop-shadow(0 0 6px currentColor);
}
.ring-center {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  text-align: center;
}
.ring-num {
  font-weight: 700;
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
  /* POP: the ring's focal count reads money-green with a faint halo (on a light tile). */
  color: var(--green);
  text-shadow: var(--glow-text);
}
/* POP: on the --grad-money hero tile the ring sits on green, so its number goes white instead. */
.tile--hero .ring-num {
  color: var(--white);
  text-shadow: 0 2px 12px var(--on-dark-28);
}
.tile--hero .ring-sub {
  color: var(--white);
}
.ring-sub {
  font-size: var(--text-xs);
  color: var(--ink-3);
}

/* brandmark — sidebar/login lockup: green rounded-square glyph + word. */
.brandmark {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  font-weight: 700;
  letter-spacing: -0.02em;
  color: var(--ink);
}
.brandmark-glyph {
  width: 30px;
  height: 30px;
  border-radius: var(--radius-xs);
  /* the money-green gradient tile — richer than the old flat fill; the P monogram is white on top. */
  background: var(--grad-money);
  color: var(--white);
  display: grid;
  place-items: center;
  box-shadow: 0 4px 12px -3px var(--money-glow);
  flex: 0 0 auto;
}
.brandmark-glyph svg {
  width: 60%;
  height: 60%;
}
/* the two-tone wordmark — "perk" in ink, "ival" in the brand accent (the ownable hook). */
.brandmark-word {
  letter-spacing: -0.03em;
}
.brandmark-word-accent {
  color: var(--blue);
}

@keyframes drawer-in {
  from {
    transform: translateX(100%);
  }
  to {
    transform: translateX(0);
  }
}

@keyframes fade-in {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

/* POP: the tile/card entrance — a fade-in with a ~8px rise. Applied to bento tiles (staggered) and
   the login card. Motion is DEFAULT; the global reduced-motion guard below collapses it to nothing. */
@keyframes pop-rise {
  from {
    opacity: 0;
    transform: translateY(8px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
}
.stat.hero {
  flex: 1.4;
  background: var(--grad-money);
  border: none;
  color: var(--white);
  box-shadow:
    0 14px 32px -8px var(--money-glow),
    inset 0 0 0 1px var(--on-dark-14);
  position: relative;
  overflow: hidden;
}

@media (hover: hover) and (pointer: fine) {
  .stat.hero:hover {
    box-shadow:
      0 20px 40px -10px var(--money-glow-strong),
      inset 0 0 0 1px var(--on-dark-16);
  }
}
.stat.hero::after {
  /* a soft sheen so the focal card reads as a surface, not a flat block */
  content: "";
  position: absolute;
  inset: 0;
  background: radial-gradient(120% 80% at 100% 0%, var(--on-dark-22), transparent 60%);
  pointer-events: none;
}
.stat.hero .stat-num {
  color: var(--white);
  font-size: var(--text-3xl);
  letter-spacing: -0.02em;
}
.stat.hero .stat-label,
.stat.hero .stat-sub {
  color: var(--on-dark-86);
}
.stat.warn .stat-num {
  color: var(--amber);
}

h2.section {
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--ink-3);
  margin: 26px 2px 10px;
}

.panel {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow);
  padding: 6px 8px;
  margin-bottom: 14px;
}
.card-avail .of {
  font-size: var(--text-xs);
  font-weight: 600;
  color: var(--ink-3);
  text-transform: uppercase;
  letter-spacing: 0.05em;
}
.chev {
  margin-left: 12px;
  color: var(--ink-3);
  transition: transform var(--t-2) var(--ease-out);
  display: flex;
}
.card-panel.collapsed .chev {
  transform: rotate(-90deg);
}
.card-panel.collapsed .benefits,
.card-panel.collapsed .welcome,
.card-panel.collapsed .card-empty {
  display: none;
}
.card-panel.collapsed .card-head {
  border-bottom: none;
}
/* RD-15: one neutral progress colour — the bar shows how much you've USED, not
   status. Urgency (red "Expiring") lives on the pill, not the bar. */
.b-fill {
  background: linear-gradient(90deg, var(--blue-bright), var(--blue-deep));
  transition: width var(--t-3) var(--ease-out);
}
.welcome {
  font-size: 12.5px;
  color: var(--ink-2);
  padding: 8px 18px;
  background: var(--line-2);
}

.benefits {
  list-style: none;
  margin: 0;
  padding: 4px 8px 8px;
}
/* amount over reset-date, right-aligned — long "$1,374 / $75,000" can't collide
   with the date (RD-15). */
.b-right {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 2px;
  min-width: 0;
  text-align: right;
}
.b-reset {
  font-size: 11.5px;
  color: var(--ink-3);
  white-space: nowrap;
}
.b-reset.urgent {
  color: var(--red);
  font-weight: 600;
}
.pill.available,
.pill.unlocked {
  background: var(--green-bg);
  color: var(--green);
}
.pill.partial {
  background: var(--amber-bg);
  color: var(--amber);
}
.pill.expiring {
  background: var(--red-bg);
  color: var(--red);
}
.pill.consumed {
  background: var(--grey-bg);
  color: var(--ink-3);
}
.pill.progress {
  background: var(--blue-bg-2);
  color: var(--blue-deep);
}
.pill.static,
.pill.access {
  background: var(--grey-4);
  color: var(--slate);
}
.controls {
  display: flex;
  align-items: center;
  gap: 16px;
  flex-wrap: wrap;
}
.segmented {
  display: inline-flex;
  background: var(--ink-wash-05);
  border-radius: 12px;
  padding: 4px;
  gap: 2px;
}
.seg {
  background: transparent;
  border: none;
  padding: 7px 15px;
  font-size: 13px;
  font-weight: 550;
  cursor: pointer;
  color: var(--ink-2);
  border-radius: 9px;
  transition:
    color var(--t-2) var(--ease-out),
    background var(--t-2) var(--ease-out),
    transform var(--t-1) var(--ease-out);
}

@media (hover: hover) and (pointer: fine) {
  .seg:hover {
    color: var(--ink);
  }
}
.seg.active {
  position: relative;
  background: var(--surface);
  color: var(--ink);
  box-shadow: var(--shadow-sm), var(--ring);
  font-weight: 650;
}
/* POP: a short --blue interaction underline under the active tab (on top of the white pill).
   Azure=interaction/selection, green=money only (Horizon) — matches sibling .tab.is-active::after. */
.seg.active::after {
  content: "";
  position: absolute;
  left: 15px;
  right: 15px;
  bottom: 4px;
  height: 2px;
  border-radius: 2px;
  background: var(--blue);
}
.group-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  padding: 14px 18px;
  border-bottom: 1px solid var(--line-2);
}
/* RD-40: the Expiring view at fleet scale — benefit groups, aggregated $ at risk,
   expandable to per-card instances. */
.exp-lead {
  font-size: 13px;
  color: var(--ink-2);
  font-variant-numeric: tabular-nums;
  margin: 0 0 12px 2px;
}
.exp-ghead .chev {
  margin: 0;
  transform: rotate(-90deg);
}
.exp-group.open .exp-ghead .chev {
  transform: rotate(0deg);
}
.exp-group.open .exp-instances {
  display: block;
}
/* RD-39: encoded card identity — art thumbnail + owner-initial avatar badge. */
.cidmedia {
  position: relative;
  display: inline-flex;
  flex: none;
  width: 42px;
  height: 27px;
}
.cidmedia.lg {
  width: auto;
  height: auto;
}
.cidart {
  width: 42px;
  height: 27px;
  border-radius: 5px;
  object-fit: cover;
  border: 1px solid var(--ink-wash-10);
}
/* =====================================================================
   CARD-ART (GRAPHIC-KIT §1) — the generated inline-SVG credit-card face
   that replaces the old flat placeholder rectangle. Sizing + depth live
   here; the per-product gradient, chip, texture + wordmark are drawn by
   card-art.ts. Colours are all `--card-*` tokens (raw hex only in :root).
   ===================================================================== */
.card-art {
  display: inline-flex;
  flex: none;
  border-radius: 5px;
  overflow: hidden;
  /* a real card sits above the surface with a soft warm drop-shadow + a hairline glass top edge. */
  box-shadow:
    0 2px 5px -1px var(--shadow-pop),
    var(--inset-hi);
}
.card-art--sm {
  width: 42px;
  height: 27px;
}
.card-art--lg {
  width: 220px;
  height: 139px;
  border-radius: 16px;
}
.card-art-svg {
  display: block;
  width: 100%;
  height: 100%;
}
/* the body/texture/chip fills are `url(#…)` gradient/pattern refs set INLINE on the SVG (a per-ramp
   gradient id), so they're not restated here. Only the token-driven fills/strokes live in CSS. */
/* a soft top sheen so the face reads as a lit surface, not a flat swatch. */
.card-art-sheen {
  fill: var(--card-line);
}
.card-art-chip-line {
  stroke: var(--card-foil);
  stroke-width: 0.75;
}
.card-art-wordmark {
  fill: var(--card-ink);
  font-family: var(--font-sans);
  font-size: 15px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
.card-art-last4 {
  fill: var(--card-ink-soft);
  font-family: var(--font-sans);
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.12em;
  font-variant-numeric: tabular-nums;
}
/* the sm thumbnail hides the fine text (unreadable at 42px) — chip + gradient carry the identity. */
.card-art--sm .card-art-wordmark,
.card-art--sm .card-art-last4 {
  display: none;
}
/* =====================================================================
   DATA-VIZ KIT (GRAPHIC-KIT §2) — stacked value bar, renewal timeline,
   burn sparkline. Pure SVG/flow over precomputed geometry (@bt/core
   dataviz.ts). One tone scale drives every mark via `.dv--{tone}`, so a
   green segment, an amber dot, and a rose spark all read one meaning.
   ===================================================================== */
/* the shared tone palette — reused by the bar segments, the timeline dots, and the sparkline. */
.dv--green {
  color: var(--green);
}
.dv--amber {
  color: var(--amber);
}
.dv--rose {
  color: var(--red);
}
.dv--muted {
  color: var(--ink-4);
}

/* stacked bar — a rounded track split into coloured runs, with a tone-dot legend below. */
.dv-stack {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.dv-stack-track {
  display: flex;
  height: 12px;
  border-radius: 100px;
  overflow: hidden;
  background: var(--grey-bg);
  box-shadow: var(--inset-hi);
}
.dv-stack-seg {
  height: 100%;
  background: currentColor;
  transition: width var(--t-3) var(--ease-out);
}
.dv-legend {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1) var(--space-4);
  font-size: var(--text-sm);
}
.dv-legend-item {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  color: var(--ink-2);
}
.dv-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: currentColor;
  flex: none;
}
.dv-legend-lbl {
  color: var(--ink-2);
}
.dv-legend-amt {
  color: var(--ink);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

/* renewal timeline — a full-width axis with a "today" marker + urgency dots, month ticks below. */
.dv-timeline {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  /* head-room above the axis for the two staggered rows of per-marker labels. */
  padding-top: 76px;
}
.dv-tl-axis {
  position: relative;
  height: 3px;
  border-radius: 100px;
  background: var(--line);
}
.dv-tl-now {
  position: absolute;
  top: 50%;
  width: 2px;
  height: 14px;
  border-radius: 2px;
  background: var(--ink-3);
  transform: translate(-50%, -50%);
}
.dv-tl-mark {
  position: absolute;
  top: 50%;
  width: 11px;
  height: 11px;
  border-radius: 50%;
  background: currentColor;
  border: 2px solid var(--surface);
  transform: translate(-50%, -50%);
  box-shadow: 0 1px 3px -1px var(--shadow-pop);
}
/* an event past the window end is pinned to the right edge + dimmed so it reads as "beyond". */
.dv-tl-mark--off {
  opacity: 0.5;
}
/* the visible marker label above each dot — name + detail, so the timeline reads without a hover. Two
   rows (hi/lo) staggered so close dates don't collide; edge markers anchor left/right so they can't clip. */
.dv-tl-mark-label {
  position: absolute;
  bottom: calc(100% + 8px);
  left: 50%;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1px;
  white-space: nowrap;
  line-height: 1.15;
  text-align: center;
  transform: translateX(-50%);
}
.dv-tl-mark--hi .dv-tl-mark-label {
  bottom: calc(100% + 32px);
}
.dv-tl-mark--el .dv-tl-mark-label {
  left: 0;
  align-items: flex-start;
  text-align: left;
  transform: none;
}
.dv-tl-mark--er .dv-tl-mark-label {
  right: 0;
  left: auto;
  align-items: flex-end;
  text-align: right;
  transform: none;
}
.dv-tl-mark-label b {
  color: var(--ink);
  font-size: var(--text-xs);
  font-weight: 650;
}
.dv-tl-mark-label small {
  color: var(--ink-3);
  font-size: var(--text-xs);
}
.dv-tl-ticks {
  position: relative;
  height: var(--text-xs);
}
.dv-tl-tick {
  position: absolute;
  top: 0;
  font-size: var(--text-xs);
  color: var(--ink-3);
  transform: translateX(-50%);
}

/* sparkline — a tiny burn line + soft area fill, an emphasised end dot, and an inline latest/cap label. */
.dv-spark {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
}
.dv-spark-svg {
  display: block;
  width: 72px;
  height: 24px;
  flex: none;
  overflow: visible;
}
.dv-spark-line {
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
  /* the line eases in on mount; the global reduced-motion guard collapses the duration. */
  animation: dv-spark-in var(--t-4) var(--ease-out) both;
}
.dv-spark-area {
  fill: currentColor;
  stroke: none;
  opacity: 0.12;
}
.dv-spark-dot {
  fill: currentColor;
}
.dv-spark-meta {
  font-size: var(--text-sm);
  color: var(--ink-3);
  white-space: nowrap;
}
.dv-spark-latest {
  color: var(--ink);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

@keyframes dv-spark-in {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}
/* =====================================================================
   GRAIN OVERLAY (GRAPHIC-KIT §3) — a subtle paper-grain texture over a
   flat surface. Add `.grain` to a container; the noise data-URI token is
   laid over it at low opacity via ::after. Non-interactive.
   ===================================================================== */
.grain {
  position: relative;
}
.grain::after {
  content: "";
  position: absolute;
  inset: 0;
  background-image: var(--noise);
  background-size: 140px 140px;
  opacity: 0.03;
  pointer-events: none;
  mix-blend-mode: overlay;
}
.ownav {
  position: absolute;
  right: -6px;
  bottom: -6px;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  color: var(--white);
  font-size: 10px;
  font-weight: 700;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1.5px solid var(--surface);
}
.cidmedia.lg .ownav {
  border-color: var(--surface);
}
.search {
  margin-left: auto;
  padding: 9px 15px;
  border: 1px solid var(--line);
  border-radius: var(--radius-xs);
  font-size: 13px;
  width: 230px;
  background: var(--surface);
  box-shadow: var(--shadow-sm);
  transition:
    border-color var(--t-2) var(--ease-out),
    box-shadow var(--t-2) var(--ease-out);
}
.search:focus {
  outline: none;
  border-color: var(--blue);
  box-shadow: 0 0 0 3px var(--ring-blue);
}
.syncprog {
  display: block;
  margin-top: 8px;
  height: 6px;
  border-radius: 4px;
  background: var(--blue-border-2);
  overflow: hidden;
}
.syncprog > span {
  display: block;
  height: 100%;
  border-radius: 4px;
  background: linear-gradient(90deg, var(--blue-link), var(--brand-accent));
  transition: width var(--t-3) var(--ease-out);
}
/* before the total is known, sweep an indeterminate sliver — linear so it reads as a
   smooth continuous motion (ease-in-out pauses at the edges and looks janky) */
.syncprog.indet > span {
  width: 40% !important;
  animation: indet calc(var(--t-4) * 2) linear infinite;
}

@keyframes indet {
  from {
    transform: translateX(-110%);
  }
  to {
    transform: translateX(260%);
  }
}
/* VELVET-20: the progress track stays sync-specific, but every outer status/notice band now uses `.banner`. */
.provider-link {
  color: var(--blue);
  font-weight: 700;
  text-decoration: underline;
  text-underline-offset: 2px;
}

@media (hover: hover) and (pointer: fine) {
  .provider-link:hover {
    text-decoration: none;
  }
}

/* BR-5.1 — the streaming SYNC RAIL: one row per connection while the capture agent streams SyncEvents,
   collapsing to a freshness chip on run-completion. Slides in like the live syncbar (sticky under the
   masthead) so streaming progress stays visible while cards materialize below. */
.syncrail {
  background: var(--blue-bg-2);
  color: var(--blue-deep);
  border: 1px solid var(--blue-border-2);
  border-radius: var(--radius-xs);
  padding: 12px 14px;
  margin-bottom: 16px;
  position: sticky;
  top: 64px;
  z-index: 4;
  animation: syncrail-in var(--t-3) var(--ease-out) both;
}

@keyframes syncrail-in {
  from {
    opacity: 0;
    transform: translateY(-8px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}
.syncrail-head {
  display: flex;
  align-items: center;
  gap: 9px;
  font-size: 13px;
  font-weight: 700;
}
.syncrail-spin {
  width: 14px;
  height: 14px;
  border: 2px solid var(--blue-border-2);
  border-top-color: var(--blue-link);
  border-radius: 50%;
  animation: spin calc(var(--t-4) + var(--t-2)) linear infinite;
}
.syncrail-title {
  color: var(--blue-deep);
}
.syncrail-list {
  list-style: none;
  margin: 10px 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 7px;
}
.syncrail-conn {
  display: grid;
  grid-template-columns: 12px 1fr auto;
  align-items: center;
  gap: 9px;
  font-size: 12px;
  font-weight: 600;
}
.syncrail-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--grey-3);
}
.syncrail-conn--active .syncrail-dot {
  background: var(--blue-link);
}
.syncrail-conn--done .syncrail-dot {
  background: var(--green);
}
.syncrail-conn--needs-auth .syncrail-dot {
  background: var(--amber-text);
}
.syncrail-conn--failed .syncrail-dot {
  background: var(--red);
}
.syncrail-name {
  color: var(--ink);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.syncrail-state {
  color: var(--ink-3);
  font-variant-numeric: tabular-nums;
}
.syncrail-conn--needs-auth .syncrail-state {
  color: var(--amber-text);
}
.syncrail-conn--failed .syncrail-state {
  color: var(--red);
}
.syncrail-conn--done .syncrail-state {
  color: var(--green);
}
.syncrail-bar {
  grid-column: 2 / 4;
  height: 5px;
  border-radius: 3px;
  background: var(--blue-border-2);
  overflow: hidden;
}
.syncrail-bar-fill {
  display: block;
  height: 100%;
  border-radius: 3px;
  background: linear-gradient(90deg, var(--blue-link), var(--brand-accent));
  transition: width var(--t-3) var(--ease-out);
}
/* The collapsed freshness chip — the rail's resting state after a run finishes. */
.syncrail-chip {
  display: flex;
  align-items: center;
  gap: 8px;
  border-radius: var(--radius-xs);
  padding: 9px 13px;
  margin-bottom: 16px;
  font-size: 13px;
  font-weight: 600;
  animation: syncrail-in var(--t-3) var(--ease-out) both;
}
.syncrail-chip--done {
  background: var(--green-bg);
  color: var(--green);
  border: 1px solid var(--green-border);
}
.syncrail-chip--error {
  background: var(--red-bg);
  color: var(--red);
  border: 1px solid var(--red-border-2);
}
.syncrail-chip-mark {
  font-weight: 800;
}
.syncrail-chip-warn {
  color: var(--amber-text);
  font-weight: 700;
}
/* BR-5.5 — the one-click re-auth pill on an amber (needs-auth) row. Spans the name/state columns on its own
   line so the 3-col row layout is undisturbed; amber-toned to match the needs-auth signal. */
.syncrail-reauth {
  grid-column: 2 / 4;
  justify-self: start;
  margin-top: 2px;
  padding: 3px 10px;
  border: 1px solid var(--amber-border);
  border-radius: var(--radius-xs);
  background: var(--amber-bg);
  color: var(--amber-text);
  font-size: 12px;
  font-weight: 700;
  cursor: pointer;
}

@media (hover: hover) and (pointer: fine) {
  .syncrail-reauth:hover {
    border-color: var(--amber-head);
  }
}
/* BR-5.5 — the re-auth FALLBACK dialog (absent / dead / mismatch): a centered, lifted modal over the shared
   `.scrim`. Tokens only; a per-variant top accent distinguishes the three at a glance. */
.reauth-dialog {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 380px;
  max-width: 92vw;
  z-index: 21;
  padding: var(--space-4);
  border: 1px solid var(--line);
  border-top: 3px solid var(--ink-3);
  border-radius: var(--radius-lg);
  background: var(--surface);
  box-shadow: var(--shadow-xl);
  animation: fade-in var(--t-2) var(--ease-out);
}
.reauth-dialog--absent {
  border-top-color: var(--amber-border);
}
.reauth-dialog--dead {
  border-top-color: var(--amber-head);
}
.reauth-dialog--mismatch {
  border-top-color: var(--red-border-2);
}
.reauth-dialog-title {
  margin: 0 0 8px;
  font-size: 16px;
  font-weight: 800;
  color: var(--ink);
}
.reauth-dialog-conn {
  margin: 0 0 10px;
  font-size: 12px;
  font-weight: 600;
  color: var(--ink-3);
}
.reauth-dialog-body {
  margin: 0 0 var(--space-4);
  font-size: 13px;
  line-height: 1.5;
  color: var(--ink-2);
}
.reauth-dialog-actions {
  display: flex;
  justify-content: flex-end;
}
.reauth-dialog-dismiss {
  padding: 7px 16px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--ink);
  color: var(--surface);
  font-size: 13px;
  font-weight: 700;
  cursor: pointer;
}
/* BR-5.1 — the morph marks only freshly inserted cards during a streaming run. Merely entering the
   materializing phase cannot replay the animation on survivor cards. */
.materializing .acct.card-in {
  animation: card-in var(--t-3) var(--ease-out) both;
}

@keyframes card-in {
  from {
    opacity: 0;
    transform: translateY(6px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

@keyframes spin {
  to {
    transform: rotate(360deg);
  }
}
.fee-row.cancel {
  border-left-color: var(--red);
  background: var(--red-bg-2);
}
.skeleton {
  padding-top: 4px;
}
.sk-block {
  display: block;
  border-radius: 12px;
  background: linear-gradient(90deg, var(--grey-5) 25%, var(--grey-3) 37%, var(--grey-5) 63%);
  background-size: 400% 100%;
  animation: shimmer calc(var(--t-4) * 2) var(--ease-in-out) infinite;
}
.sk-controls {
  height: 40px;
  width: 360px;
  max-width: 100%;
  margin-bottom: 18px;
  border-radius: var(--radius-xs);
}
.sk-card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 18px;
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.sk-row {
  height: 20px;
  border-radius: 7px;
}
.sk-row:nth-child(2) {
  width: 75%;
}
.sk-row:nth-child(3) {
  width: 55%;
}

@keyframes shimmer {
  0% {
    background-position: 100% 0;
  }
  100% {
    background-position: 0 0;
  }
}
.empty-state {
  text-align: center;
  padding: 80px 20px;
  color: var(--ink-3);
}
.empty-state h2 {
  color: var(--ink);
  font-size: 19px;
  margin-bottom: 8px;
}
/* AR3-E-4: the calm config-error screen, rendered from the composition root when neither a backend nor
   the demo marker is present — no MVU shell, so it carries its own centered layout. */
.config-error {
  text-align: center;
  padding: 80px 20px;
  color: var(--ink-3);
}
.config-error h1 {
  color: var(--ink);
  font-size: 19px;
  margin-bottom: 8px;
}
/* the aside voice line — the ONE serif-italic phrase per view (HORIZON §03). Instrument Serif
   (`--serif`) is reserved for exactly this: the hero `<em>` and the route-hero / caught-up aside.
   Numerals, wordmark, body and labels stay grotesque. */
.aside {
  font-family: var(--serif);
  font-style: italic;
  font-weight: 400;
  color: var(--ink-900);
}

/* route-hero — the per-route Horizon still-life band (routeHero, HORIZON-S4). The raster is weighted to
   the right; a left→right canvas scrim keeps the left ~40% calm copy-space for the kick eyebrow + the
   serif aside. It heads the route ABOVE the data masthead and carries no data (§0.2). */
.route-hero {
  position: relative;
  overflow: hidden;
  min-height: 132px;
  margin-bottom: var(--space-4);
  border: 1px solid var(--hairline);
  border-radius: var(--r-3);
  background: var(--aurora-warm);
  box-shadow: var(--lift-lo);
}
.route-hero-art {
  position: absolute;
  inset: 0 0 0 auto;
  width: 66%;
  height: 100%;
  object-fit: cover;
  object-position: center right;
}
.route-hero::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(90deg, var(--canvas) 0%, var(--canvas) 32%, transparent 74%);
  pointer-events: none;
}
.route-hero-copy {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  justify-content: center;
  max-width: 60%;
  min-height: 132px;
  padding: 0 var(--space-6);
}
.route-hero-kick {
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  /* the accent eyebrow rides the THEME-AWARE accent ink (not the fill-only --azure-deep): --accent-ink
     resolves to the same azure in light but flips to the dark theme's accent in dark, so this TEXT clears
     WCAG AA over the darkened --aurora-warm band (AR72-HZN-1; azure-on-dark was ~2.25:1). */
  color: var(--accent-ink);
}
.route-hero-aside {
  font-size: var(--text-xl);
  line-height: 1.15;
}

/* Horizon hero still-life (HORIZON-S2, RENDER-BRIEF Tier A): the rendered illustration for a full-page
   state (empty wallet / disconnected-cable error), a same-origin raster (img-src 'self') that replaces
   the old hand-drawn card-wallet SVG spot. 2:1 art, capped + centred in the calm empty-state column; the
   entrance rise rides the global reduced-motion guard. */
.hero-still {
  display: block;
  width: 100%;
  max-width: 320px;
  aspect-ratio: 2 / 1;
  margin: 0 auto var(--space-4);
  object-fit: contain;
  border-radius: var(--r-2);
  animation: pop-rise var(--t-4) var(--ease-out) both;
}

/* hero-plate (HZNF-1) — the FRAME + THEME wrapper every Horizon still-life raster sits in. The rasters are
   baked LIGHT plates: dropped straight onto the canvas they floated as bright unframed rectangles, and in
   the dark theme they read as holes punched in the night surface. No new art was commissioned (owner
   decision D-g) — the frame and the theming are CSS:
     · the PLATE is a real surface — theme-flipping --sunken fill, a --hairline edge and the Horizon
       elevation-1 lift — so the still reads as a mounted illustration, not a stray image;
     · the ::after VIGNETTE is the theme half. It washes --canvas in from the plate edges, so the SAME
       raster is matted into near-white paper in light and dissolved into the night surface in dark — the
       "baked plate seam" treatment `.lockwall-hero-art` already used, generalised to every still.
   Modifiers carry the geometry beside their component (--still here, --chip in 07, --pane in 06); the
   base owns only the frame, so a new still gets the mount for free. */
.hero-plate {
  position: relative;
  overflow: hidden;
  border: 1px solid var(--hairline);
  border-radius: var(--r-2);
  background: var(--sunken);
  box-shadow: var(--lift-lo);
}
.hero-plate::after {
  content: "";
  position: absolute;
  inset: 0;
  background: radial-gradient(ellipse at center, transparent 46%, var(--canvas) 100%);
  opacity: 0.72;
  pointer-events: none;
}
/* the full-page state mount (empty wallet / load error / caught-up): the 2:1 still, capped and centred in
   the calm empty-state column. The plate takes over the centring + the trailing gap so the framed art
   keeps the exact footprint the bare raster had. */
.hero-plate--still {
  width: 100%;
  max-width: 320px;
  margin: 0 auto var(--space-4);
}
.hero-plate--still .hero-still {
  max-width: none;
  margin: 0;
  border-radius: 0;
}
.empty-steps {
  display: inline-block;
  text-align: left;
  margin: 12px auto 0;
  padding-left: 20px;
  line-height: 1.8;
  color: var(--ink-2);
}
.empty-steps b {
  color: var(--ink);
}
.lockwall {
  display: grid;
  place-items: center;
  min-height: 100vh;
  padding: var(--space-6) var(--space-4);
  /* POP: warm sage/peach aurora backdrop instead of the flat canvas — the trust-moment surface. */
  background: var(--aurora-warm);
}
.lockwall-card {
  position: relative;
  width: 100%;
  max-width: 420px;
  padding: var(--space-6);
  border-radius: var(--radius-lg);
  overflow: hidden;
  background: var(--surface);
  /* POP: a floating card — the extra-lift float shadow + a fine top hairline. */
  box-shadow:
    var(--shadow-xl),
    0 2px 8px -4px var(--ink-wash-10);
  /* POP: a rounded --grad-money top strip via ::before (replaces the flat 4px accent border). */
  animation: pop-rise var(--t-4) var(--ease-out) both;
}
.lockwall-card::before {
  content: "";
  position: absolute;
  inset: 0 0 auto;
  height: 4px;
  border-radius: var(--radius-lg) var(--radius-lg) 0 0;
  background: var(--grad-money);
}
.lockwall-card--locked::before {
  background: var(--red);
}
/* VELVET-15 §5: the CALM sign-in card's top strip is a quiet HAIRLINE, not the money-green gradient
   (Horizon §02: green = MONEY only). The green `--grad-money` strip stays on money moments, not the
   first screen a visitor sees. The locked wall keeps its red strip (above). */
.lockwall-card--calm::before {
  background: var(--hairline);
}
/* VELVET-23: uncovered terminal flows share one editorial state-card anatomy. The object is a bare
   functional line icon on a quiet plate; tone changes the ink only, never the card structure. */
.state-card-object {
  display: grid;
  place-items: center;
  width: 48px;
  height: 48px;
  margin-bottom: var(--space-4);
  border: 1px solid var(--line);
  border-radius: 50%;
  background: var(--tile-2);
  color: var(--blue);
}
.state-card-object .icon {
  width: 24px;
  height: 24px;
}
.state-card-kicker {
  margin: 0 0 var(--space-2);
  color: var(--ink-3);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.state-screen--warn .state-card-object,
.entitlement-card--suspended .state-card-object {
  color: var(--warn);
}
.state-screen--ok .state-card-object {
  color: var(--green);
}
.entitlement-card .btn-primary {
  background: var(--accent-down);
  box-shadow: var(--shadow-sm);
}
.lockwall-export {
  border: 1px solid var(--blue);
  background: transparent;
  color: var(--blue);
}
/* VELVET-15 §4: the sign-in brandmark lockup above the title — the INTERIM single-ink glyph tile
   (ink-900 mark), NOT the money-green tile. Green saturating the brand was the loudest "green in the
   chrome" tell; azure stays reserved for interaction, green for money. Final identity is LAUNCH-BRAND-1. */
.lockwall-brand {
  margin-bottom: var(--space-4);
  font-size: var(--text-lg);
}
.lockwall-brand .brandmark-glyph {
  width: 48px;
  height: 48px;
  border-radius: var(--radius-sm);
  background: var(--ink);
  /* the glyph inverts WITH the ink tile so it stays legible in BOTH themes: `--ink` is dark-on-light
     and near-white after dark, so the "P" rides `--surface` (the opposite pole) — a white glyph on the
     dark tile in light, a dark glyph on the light tile in dark. The base `.brandmark-glyph` sets
     `--white`, which would vanish on the near-white dark-theme tile; this override is the fix. */
  color: var(--surface);
  box-shadow: var(--shadow-sm);
}
.lockwall-title {
  margin-bottom: var(--space-2);
  color: var(--ink);
  font-size: var(--text-3xl);
  font-weight: 700;
  letter-spacing: -0.03em;
}
.lockwall-lede {
  margin-bottom: var(--space-5);
  color: var(--ink-2);
  font-size: var(--text-md);
  line-height: 1.6;
}
.lockwall-cta {
  margin-bottom: var(--space-5);
}
.lockwall-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  padding-top: var(--space-4);
  border-top: 1px solid var(--bg-2);
}
/* =====================================================================
   LOGIN SPLIT (GRAPHIC-KIT / elite review) — a two-pane sign-in wall: a
   left GRAPHIC hero (aurora + guilloché motif + a fanned card-art stack +
   brand lockup) beside the right form. Below the split breakpoint the hero
   collapses out and the form stands alone (the wall degrades to the plain
   centered card). No new locators — chrome around the existing form.
   ===================================================================== */
.lockwall--split {
  /* FULL-BLEED split (owner review: the old two floating cards read as two detached widgets). Two
     edge-to-edge columns fill the viewport — a colour hero pane on the left, a clean content pane on
     the right — with NO gap and NO max-width, so it reads as ONE screen with two zones (Mercury/Linear
     style), not two panels floating on a page. */
  grid-template-columns: minmax(0, 1.05fr) minmax(0, 0.95fr);
  gap: 0;
  max-width: none;
  min-height: 100vh;
  margin: 0;
  padding: 0;
  place-items: stretch;
}
.lockwall--split .lockwall-hero {
  /* the hero becomes the left pane itself — squared off, edge-to-edge, no separate float. */
  min-height: 100vh;
  border-radius: 0;
  box-shadow: inset -1px 0 0 var(--ink-wash-05);
}
/* the right content pane — the form card + privacy band stacked and vertically centred on the page
   wash. On a narrow pane the privacy tiles stack; the pane scrolls if the content is taller than 100vh. */
.lockwall-right {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  gap: var(--space-5);
  min-height: 100vh;
  padding: clamp(var(--space-6), 6vw, 72px) clamp(var(--space-5), 5vw, 56px);
}
.lockwall--split .lockwall-card {
  max-width: 400px;
  width: 100%;
}
.lockwall--split .lockwall-privacy {
  width: 100%;
  max-width: 400px;
  margin: 0;
  padding: var(--space-5);
  border-radius: var(--radius-lg);
  background: var(--surface);
  box-shadow:
    var(--shadow-lg),
    inset 0 0 0 1px var(--ink-wash-05);
}
.lockwall-privacy .privacy h3 {
  margin-bottom: var(--space-1);
}
.lockwall-privacy .privacy-list {
  margin-top: var(--space-4);
  gap: 0;
}
/* HZNF-2 — the trust panel is TYPOGRAPHY, not tiles. VELVET-15 §5 already de-greened the two buckets
   (achromatic hairline + faint neutral fill + azure label), but each bucket was still a tinted, bordered,
   accent-striped BOX: two stacked coloured cards of near-identical copy, the generated-looking block the
   owner flagged. On the sign-in wall — the one screen whose whole job is to be believed — the chrome is
   removed and the hierarchy is carried by type and one hairline:
     · no fill, no border, no radius, no left accent strip — the panel's own --surface card is the only box;
     · the buckets are separated by a single --hairline rule, so they read as two entries in one statement
       instead of two competing tiles;
     · the bucket label becomes an editorial eyebrow (tracked small caps, the `.route-hero-kick` voice):
       QUIET --ink-700 for the on-device fact, the azure --accent-ink for the syncs-derived caveat, so the
       two still separate at a glance with ZERO green in the chrome (green = money only, Horizon §02).
   Every trust claim, both buckets, and both labels are kept verbatim — this changes only the chrome
   (§0.2). Scoped to `.lockwall-privacy` so the Settings drawer's own privacy panel is untouched. */
.lockwall-privacy .privacy-item {
  padding: 0;
  border-width: 0;
  border-radius: 0;
  background: none;
}
.lockwall-privacy .privacy-item::before {
  display: none;
}
.lockwall-privacy .privacy-item + .privacy-item {
  margin-top: var(--space-4);
  padding-top: var(--space-4);
  border-top: 1px solid var(--hairline);
}
.lockwall-privacy .privacy-tag {
  margin-bottom: var(--space-2);
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.16em;
  color: var(--ink-700);
}
.lockwall-privacy .privacy-tag--sync {
  color: var(--accent-ink);
}
.lockwall-hero {
  position: relative;
  align-self: stretch;
  display: grid;
  place-items: center;
  min-height: 380px;
  padding: var(--space-6);
  overflow: hidden;
  border-radius: var(--radius-lg);
  /* Horizon studio sweep — a soft cool vertical wash so the graphic pane reads as its own surface (the
     retired warm sage/peach mesh is gone, HORIZON-S4). The hero-auth still-life is layered over this by
     `.lockwall-hero-art`. */
  background: var(--aurora-warm);
  box-shadow:
    var(--shadow-lg),
    inset 0 0 0 1px var(--ink-wash-05);
}
/* the faint guilloché dot motif — a low-opacity radial-dot grid over the aurora. VELVET-15: the dot is
   a NEUTRAL hairline ink, not green — the hero chrome is achromatic (green = money only, Horizon §02). */
.lockwall-hero-motif {
  position: absolute;
  inset: 0;
  background-image: radial-gradient(var(--hair-strong) 1px, transparent 1.4px);
  background-size: 18px 18px;
  opacity: 0.14;
}
/* the hero-auth still-life (the glowing envelope, HORIZON-S4) — a decorative top-right accent over the
   studio sweep + dot motif, behind the sell copy (which owns z-index 1). aria-hidden in markup.
   VELVET-15 §3: a soft radial mask DISSOLVES the baked plate edges of the still-life so it blends into
   the studio sweep instead of sitting in a visible rectangle (the "baked plate seam" the owner flagged).
   HZNF-1: the mask alone still left a LIGHT-baked raster floating on a night sweep, so the still is now
   mounted in a `hero-plate` (05) — the plate owns the top-right placement, the frame, and the theme-aware
   vignette; the img keeps the dissolve so the mount reads as a matted print, not a pasted rectangle. */
.hero-plate--pane {
  position: absolute;
  top: var(--space-5);
  right: var(--space-5);
  width: 42%;
  max-width: 240px;
  pointer-events: none;
  /* HZNF-1 (second pass): the plate FRAME is deliberately dropped on this surface. Framing a light-baked
     raster over the night sweep turned it into a lit rectangle hanging above the wordmark — the exact
     "floating art" the owner flagged, made more literal. Here the still is an ATMOSPHERIC accent: no fill,
     no edge, no lift; the dissolve below is the whole treatment, so it reads as light in the sweep rather
     than a pasted object. The plate's frame still serves the empty-state + chip mounts (05 / 07), where
     the raster IS the subject and sits on a surface, not in a scene. */
  border: 0;
  background: none;
  box-shadow: none;
}
.hero-plate--pane::after {
  /* no vignette either — the mask on the image already carries the dissolve on this surface. */
  content: none;
}
.lockwall-hero-art {
  display: block;
  width: 100%;
  object-fit: contain;
  /* Knocked well back and dissolved EARLY, so neither theme shows a plate edge: in light it is a whisper
     over the studio sweep, in dark a faint glow instead of a hole punched in the night surface. */
  opacity: 0.42;
  mask-image: radial-gradient(ellipse at center, black 58%, transparent 94%);
}
.lockwall-hero-inner {
  position: relative;
  z-index: 1;
  display: grid;
  /* left-aligned editorial axis — headline, subhead, and value-props all share one left edge (the mixed
     centered-headline / left-list look read unresolved). The decorative card-fan re-centres itself. */
  justify-items: start;
  gap: var(--space-5);
  text-align: left;
}
.lockwall-hero-inner .card-fan {
  justify-self: center;
}
.lockwall-hero .lockwall-brand {
  margin-bottom: 0;
}
/* the marketing headline — the sell. Big, tight, ink-strong, left-aligned with the value-props.
   `text-wrap: balance` evens the line lengths so the last line is never a lonely orphan word. */
.lockwall-hero-head {
  max-width: 15ch;
  color: var(--ink);
  font-size: var(--text-4xl);
  font-weight: 700;
  line-height: 1.08;
  letter-spacing: -0.03em;
  text-wrap: balance;
}
/* the one serif-italic phrase in the sign-in headline (HORIZON §03) — Instrument Serif, the only serif
   on the wall; the rest of the headline stays grotesque 700. */
.lockwall-hero-head em {
  font-family: var(--serif);
  font-style: italic;
  font-weight: 400;
}
/* on the split wall the hero headline IS the one dominant line; demote the form-card heading to a quiet
   action label so two type-scale-topping headlines don't compete across the gutter (elite re-review). */
.lockwall--split .lockwall-title {
  font-size: var(--text-lg);
  font-weight: 600;
  letter-spacing: -0.01em;
}
.lockwall-hero-tag {
  max-width: 34ch;
  color: var(--ink-2);
  font-size: var(--text-md);
  line-height: 1.55;
}
/* the three product value-props — a functional line icon + a headline claim + a supporting line, stacked. */
.sell-points {
  display: grid;
  gap: var(--space-4);
  width: 100%;
  max-width: 34ch;
  margin: 0;
  padding: 0;
  list-style: none;
}
.sell-point {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--space-3);
  align-items: start;
  text-align: left;
}
/* VELVET-15 §2: the sell-point icon is a bare 24-grid line icon in ink — NOT a green icon-in-a-tinted-
   square (the literally-banned "Bootstrap icon-in-a-box", Horizon §05/§06). No tile background, no glow;
   it optically aligns with the title baseline (a small top nudge past the cap height). */
.sell-point-ic {
  display: grid;
  place-items: center;
  margin-top: 1px;
  color: var(--ink-700);
}
.sell-point-ic .icon {
  width: 22px;
  height: 22px;
}
.sell-point-title {
  display: block;
  color: var(--ink);
  font-size: var(--text-md);
  font-weight: 650;
  letter-spacing: -0.01em;
}
.sell-point-body {
  display: block;
  margin-top: 2px;
  color: var(--ink-2);
  font-size: var(--text-sm);
  line-height: 1.5;
}
/* the one-line trust footnote beneath the sign-in form (the D23 bright-line, up front). */
.lockwall-trust {
  display: flex;
  gap: var(--space-2);
  align-items: center;
  margin-top: var(--space-4);
  color: var(--ink-3);
  font-size: var(--text-sm);
  line-height: 1.4;
}
/* VELVET-15 §5: the bright-line lock glyph is a MUTED INK, not green — green is reserved for money
   figures (there are none on this screen), so no green text leaks into the sign-in chrome. */
.lockwall-trust .icon {
  flex: none;
  width: 15px;
  height: 15px;
  color: var(--ink-3);
}
/* the fanned card-art stack — three tilted `card-art--lg` faces with soft shadows, the hero moment.
   The middle card sits flat/forward; the outer two fan out and drop behind. */
.card-fan {
  position: relative;
  width: 300px;
  height: 200px;
  margin: var(--space-3) auto;
}
.card-fan-item {
  position: absolute;
  top: 50%;
  left: 50%;
  transform-origin: center bottom;
}
.card-fan-item .card-art--lg {
  /* scale the 220px face down a touch so three fan out inside the stack; each keeps its own depth.
     The entrance rise lives on the INNER face so the item's static fan tilt isn't clobbered by the
     pop-rise keyframe's transform (an animation transform wins over the CSS property). Two stacked
     shadows — a tight contact shadow + a deep ambient one — give the fan real elevation (elite review:
     the stack read flat). A hairline inset lifts the top edge. */
  width: 196px;
  height: 124px;
  box-shadow:
    0 2px 6px -2px var(--shadow-pop),
    0 22px 40px -16px var(--shadow-pop),
    var(--inset-hi);
  animation: pop-rise var(--t-4) var(--ease-out) both;
}
/* the three fan positions: left card rotates back-left + drops behind, centre sits forward + up, right
   rotates back-right. Wider offsets + deeper tilt so all three read as a real spread stack, not a near
   overlap (elite review: more depth on the fan). */
.card-fan-item--0 {
  transform: translate(-104%, -42%) rotate(-16deg);
  z-index: 1;
}
.card-fan-item--1 {
  transform: translate(-50%, -60%) rotate(0deg);
  z-index: 3;
}
.card-fan-item--2 {
  transform: translate(4%, -42%) rotate(16deg);
  z-index: 2;
}
/* below the split breakpoint: collapse to one column but KEEP the selling copy (headline + value-props)
   — only the decorative card-fan graphic drops. So mobile still gets the full pitch, not a naked form. */
@media (width <= 720px) {
  .lockwall--split {
    grid-template-columns: minmax(0, 1fr);
  }
  /* collapse the two full-height panes to natural height and stack them (hero copy, then form) — only
     the decorative card-fan drops, so mobile still gets the full pitch. */
  .lockwall--split .lockwall-hero,
  .lockwall-right {
    min-height: 0;
  }
  .lockwall-hero .card-fan {
    display: none;
  }
}
/* === BANNER === VELVET-20 — the ONE notice-band primitive. Before this, the notice archetypes shipped
   as unrelated families (`.notice-banner`, `.session-banner`, `.syncbar.stale/.demo`) with drifting
   padding/radii/borders, so a stacked band read as additive, not designed. One shared anatomy — a leading
   24-grid icon slot, a body, an optional trailing control — with the tint carried by ONE tone modifier
   (`--info/--ok/--warn/--error/--consent/--demo/--progress`) mapping to the status-tint tokens (§2b: tokens only, no
   raw hex). The trailing control (dismiss / action) inherits the band's tint (`inherit`), so a new tone
   needs no button rule. R1 holds: the band is a calm hairline-bordered tint, never alert-band chrome. */
.banner {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin-bottom: var(--space-3);
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--hair);
  border-radius: var(--radius-sm);
  font-size: var(--text-sm);
}
.banner-body {
  flex: 1;
  line-height: 1.5;
}
.banner-ic {
  flex: none;
  display: inline-flex;
}
.banner-dismiss,
.banner-action {
  flex: none;
  margin-left: auto;
  padding: var(--space-1) var(--space-2);
  border: 1px solid;
  border-color: inherit;
  border-radius: var(--radius-sm);
  background: transparent;
  color: inherit;
  font-size: var(--text-xs);
  cursor: pointer;
}
/* tone tints — the tint families the seven archetypes used, now one modifier each */
.banner--info {
  background: var(--blue-bg-2);
  color: var(--blue-deep);
  border-color: var(--blue-border-2);
}
.banner--progress {
  background: var(--blue-bg-2);
  color: var(--blue-deep);
  border-color: var(--blue-border-2);
}
.banner--ok {
  background: var(--green-bg);
  color: var(--green);
  border-color: var(--green-border);
}
.banner--warn {
  background: var(--amber-bg);
  color: var(--amber-text);
  border-color: var(--amber-border);
}
.banner--error {
  background: var(--red-bg);
  color: var(--red-text);
  border-color: var(--red-border);
}
.banner--consent {
  background: var(--surface);
  color: var(--ink-2);
  border-color: var(--hair);
}
.banner--consent .banner-body {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}
.banner-tag {
  flex: none;
  padding: 2px var(--space-2);
  border-radius: var(--radius-xs);
  background: var(--sunken);
  color: var(--ink-2);
  font-family: var(--mono);
  font-size: var(--text-xs);
  letter-spacing: 0.3px;
}
.sync-status-label {
  display: block;
  font-weight: 600;
}
/* the fabricated-data honesty bar: the amber caution palette + a heavier left accent, and it STAYS pinned
   (`--sticky`) so a "Sample data — not your account" warning never scrolls away (AR3-E-4). */
.banner--demo {
  background: var(--amber-bg);
  color: var(--amber-text);
  border-color: var(--amber-border);
  border-left: 4px solid var(--amber-text);
}
.banner--sticky {
  position: sticky;
  top: 64px;
  z-index: 4;
}
/* VELVET-11 one voice at a time: the banner PILE is collapsed to a single winning band + a subtle count
   chip for the rest of the queue. The wrapper is a positioning context so the `+N` chip can sit at the
   band's top-right corner without disturbing the winning banner's own layout; R1 (no alert-band chrome)
   holds — no tint/border/fill of its own, it only hosts the one band the read-model chose. */
.notice-stack {
  position: relative;
}
.notice-stack-count {
  position: absolute;
  top: var(--space-2);
  right: var(--space-2);
  padding: 2px var(--space-2);
  border-radius: var(--radius-sm);
  background: var(--sunken);
  color: var(--ink-3);
  font-size: var(--text-xs);
  font-variant-numeric: tabular-nums;
}
/* GTM-FRUN-1 the one-time first-run disclosure card — a calm, on-brand panel above the banners that
   states, up front, how the product handles the user's data (CWS prominent disclosure, R5). */
.first-run {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-4);
  margin-bottom: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--green-bg);
  box-shadow: var(--shadow-sm);
}
/* HZNF-1: the disclosure card's still-life is mounted in a `hero-plate` (05) instead of floating as a bare
   56px light raster on the card tint — the plate holds the square geometry, the img just fills it. */
.hero-plate--chip {
  display: block;
  width: 56px;
  height: 56px;
  flex: none;
  align-self: center;
  border-radius: var(--radius-sm);
}
.first-run-hero {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
}
.first-run-head {
  margin: 0;
  font-size: var(--text-lg);
  font-weight: 700;
  color: var(--ink);
}
.first-run-line {
  margin: var(--space-1) 0 0;
  font-size: var(--text-sm);
  line-height: 1.5;
  color: var(--ink-2);
}
.first-run-promise {
  color: var(--green);
  font-weight: 600;
}
.first-run-actions {
  display: flex;
  flex-shrink: 0;
  flex-direction: column;
  gap: var(--space-2);
}
.first-run-policy {
  /* an interaction link → the interaction accent (BROAD-V-SHELL green-sweep), not money-green. */
  color: var(--blue);
  font-size: var(--text-sm);
  text-align: center;
}
/* GTM-FRUN-3 the in-product privacy-policy link in the store-build drawer privacy section. */
.drawer-policy-link {
  display: inline-block;
  margin-top: var(--space-3);
  /* an interaction link → the interaction accent (BROAD-V-SHELL green-sweep), not money-green. */
  color: var(--blue);
  font-size: var(--text-sm);
}
.signin-form {
  display: grid;
  gap: var(--space-3);
}
/* AUTHUX-CARD-1 (D86): the password/create-account/code group is a wrapper now, not four direct grid
   children of .signin-form — without this, the wrapper collapses their gaps into one grid item and the
   revealed layout no longer matches today's spacing. Same token as .signin-form's gap, both reveal states. */
.signin-form-more {
  display: grid;
  gap: var(--space-3);
}
/* GUARD (F9) — LOAD-BEARING TODAY, not future-proofing. An author `display` declaration beats the UA
   `[hidden] { display: none }` rule regardless of specificity, and `.signin-form-more { display: grid }`
   directly above IS that competing rule: delete these three lines and the collapsed group renders
   visible in a real browser right now (verified by probe, AUTHUX-CARD-1 review of 504984c4a). Merging
   the two rules re-breaks it. jsdom does not run the cascade, so every unit test stays green either way
   — only the e2e layer can see this. Do not replace with `display: contents` (see the ticket's Known traps). */
.signin-form-more[hidden] {
  display: none;
}
/* AUTHUX-2: the form's own orientation heading ("Sign in or create your account" / "Check your email" /
   "Enter your code") — a modest label under the wall's marketing title, not a competing hero. */
.signin-form-head {
  margin: 0;
  font-size: var(--text-md);
  font-weight: 600;
  color: var(--ink);
}
.oauth {
  display: grid;
  gap: var(--space-2);
}
.oauth-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  width: 100%;
  padding: 10px var(--space-3);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  font-size: var(--text-sm);
  font-weight: 600;
  cursor: pointer;
}
.oauth-btn:disabled {
  cursor: default;
  opacity: 0.6;
}
.oauth-btn--apple {
  background: var(--oauth-apple-bg);
  border-color: var(--oauth-apple-bg);
  color: var(--oauth-apple-ink);
}
.oauth-btn--google {
  background: var(--oauth-google-bg);
  color: var(--oauth-google-ink);
}
.oauth-or {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  color: var(--ink-3);
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.06em;
}
.oauth-or::before,
.oauth-or::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--line);
}
/* ===== Primitives (REDESIGN): the real filled primary button + form field, shared by every form
   and wall. Replaces the prior anti-pattern of reusing the sidebar-nav ghost (.side-item) as a
   submit, and the bare unstyled input. ===== */
.btn-primary {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  width: 100%;
  min-height: 44px;
  padding: 0 var(--space-4);
  border: 0;
  border-radius: var(--radius-sm);
  /* POP: the primary button is the money moment — --grad-money fill + a green glow, white text. */
  background: var(--grad-money);
  color: var(--white);
  box-shadow: 0 8px 20px -8px var(--money-glow);
  font-size: var(--text-md);
  font-weight: 600;
  text-decoration: none;
  cursor: pointer;
  transition:
    box-shadow var(--t-2) var(--ease-out),
    transform var(--t-1) var(--ease-out);
}

@media (hover: hover) and (pointer: fine) {
  .btn-primary:hover {
    transform: translateY(-1px);
    box-shadow: 0 12px 26px -8px var(--money-glow-strong);
  }
}
.btn-primary:active {
  transform: translateY(1px);
}
.btn-primary:disabled {
  cursor: default;
  opacity: 0.55;
}
/* VELVET-15 §1: on the SIGN-IN wall the primary CTA is AZURE — the ONE interaction accent — not the
   money-green gradient. Signing in is an interaction, so the CTA rides `--accent-down` (azure-deep:
   white text clears WCAG AA at ~6.8:1, and it is deliberately NOT remapped after dark, so it reads
   identically in both themes, VELVET-12). Green fills stay a money moment: the entitlement upgrade/renew
   CTA lives on `.lockwall` WITHOUT `--split`, so it keeps `--grad-money` untouched. Press keeps the base
   `translateY(1px)` (VELVET-7). */
.lockwall--split .btn-primary {
  background: var(--accent-down);
  box-shadow: var(--shadow-sm);
}

@media (hover: hover) and (pointer: fine) {
  .lockwall--split .btn-primary:hover {
    box-shadow: var(--shadow-lg);
    filter: brightness(1.06);
  }
}
.field {
  width: 100%;
  min-height: 44px;
  padding: 0 var(--space-3);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--tile-2);
  color: var(--ink);
  font-family: var(--font-sans);
  font-size: var(--text-md);
}
.field::placeholder {
  color: var(--ink-3);
}
.field:focus {
  outline: none;
  border-color: var(--blue);
  /* POP: a slightly wider, tinted focus ring in the accent tone. */
  box-shadow: 0 0 0 4px var(--ring-blue);
}
.scrim {
  position: fixed;
  inset: 0;
  background: var(--scrim);
  z-index: 15;
  animation: fade-in var(--t-2) var(--ease-out);
}
.drawer {
  position: fixed;
  top: 0;
  right: 0;
  width: 420px;
  max-width: 92vw;
  height: 100vh;
  background: var(--surface);
  box-shadow: -8px 0 30px var(--shadow-drawer);
  z-index: 20;
  overflow-y: auto;
  animation: drawer-in var(--t-3) var(--ease-drawer);
}
.drawer-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 18px 24px;
  border-bottom: 1px solid var(--line);
  position: sticky;
  top: 0;
  background: var(--surface);
}
.drawer-head h2 {
  margin: 0;
  font-size: 17px;
}
.icon-btn {
  position: relative;
  background: none;
  border: none;
  font-size: 26px;
  line-height: 1;
  cursor: pointer;
  color: var(--ink-3);
}
.drawer-body {
  padding: 8px 24px 48px;
}
.drawer-body section {
  padding: 18px 0;
  border-bottom: 1px solid var(--line);
}
.drawer-body h3 {
  margin: 0 0 12px;
  font-size: 13px;
}
.drawer-body label {
  display: block;
  font-size: 13px;
  color: var(--ink-2);
  margin-bottom: 12px;
}
.drawer-body input[type="number"],
.drawer-body .rename {
  display: block;
  width: 100%;
  padding: 9px 11px;
  border: 1px solid var(--line);
  border-radius: 9px;
  font-size: 13px;
  margin-top: 5px;
}
.drawer-body .rename {
  margin-bottom: 8px;
}
.drawer-hint {
  font-size: 12px;
  color: var(--ink-3);
  margin: 6px 0 0;
}
.health-err {
  font-size: 12.5px;
  color: var(--red);
  margin: 6px 0 0;
}
.health-shape {
  color: var(--ink-3);
  font-variant-numeric: tabular-nums;
}
/* the bright-line trust surface (AUD-158) */
.privacy-list {
  list-style: none;
  margin: var(--space-3) 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.privacy-item {
  position: relative;
  overflow: hidden;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  padding: 12px 14px 12px 16px;
  /* crisp flat tint — no blurred drop-shadow (it read muddy on the pale tint); depth comes from the
     tinted fill + hairline + the solid left accent, which stays sharp at every zoom. */
}
/* a 3px solid accent strip keyed to the item's meaning — a clean edge, not a soft glow. */
.privacy-item::before {
  content: "";
  position: absolute;
  inset: 0 auto 0 0;
  width: 3px;
}
.privacy-item--local::before {
  background: var(--green);
}
.privacy-item--sync::before {
  background: var(--sky-ink);
}
.privacy-item--local {
  background: var(--green-bg);
}
.privacy-item--sync {
  /* the two buckets must read as DIFFERENT things — the "blue" tokens are green aliases in this palette,
     so the sync tile borrowed the local tile's exact green. Use the real sky tint to separate them. */
  background: var(--sky);
}
.privacy-tag {
  display: block;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  margin-bottom: 4px;
}
.privacy-tag--local {
  color: var(--green);
}
.privacy-tag--sync {
  color: var(--sky-ink);
}
.privacy-body {
  display: block;
  font-size: var(--text-sm);
  color: var(--ink-2);
  line-height: 1.45;
}
.plan-card {
  margin-top: var(--space-3);
  padding: var(--space-4);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  background: var(--surface);
}
.billing-cta {
  margin-top: 10px;
  padding: 10px var(--space-3);
  border: 1px solid var(--accent-fill);
  border-radius: var(--radius-sm);
  background: var(--accent-fill);
  color: var(--white);
  font-size: var(--text-sm);
  font-weight: 600;
  cursor: pointer;
}
.billing-cta:disabled {
  cursor: default;
  opacity: 0.6;
}
.billing-err {
  margin: 8px 0 0;
  color: var(--red);
  font-size: 12.5px;
}
/* GRW-12 growth surfaces: the live price/trial line, the apply-a-promo-code box, and the
   manage/cancel (customer portal + save-offer) surface. Tokens only; BEM-lite class names. */
.price-line {
  margin: 6px 0 0;
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--ink);
}
.promo {
  margin-top: 10px;
}
.promo-label {
  display: block;
  font-size: 12px;
  color: var(--ink-3);
  margin-bottom: 5px;
}
.promo-row {
  display: flex;
  gap: var(--space-2);
}
.promo-input {
  flex: 1;
  padding: 9px 11px;
  border: 1px solid var(--line);
  border-radius: 9px;
  font-size: 13px;
}
.promo-input::placeholder {
  color: var(--ink-3);
  opacity: 1;
}
.promo-apply {
  padding: 9px var(--space-3);
  border: 1px solid var(--accent-fill);
  border-radius: var(--radius-sm);
  background: var(--accent-fill);
  color: var(--white);
  font-size: var(--text-sm);
  font-weight: 600;
  cursor: pointer;
}
.promo-apply:disabled {
  cursor: default;
  opacity: 0.6;
}
.promo-ok {
  margin: 8px 0 0;
  color: var(--green);
  font-size: var(--text-sm);
  font-weight: 600;
}
.promo-err {
  margin: 6px 0 0;
  color: var(--red);
  font-size: 12.5px;
}
.manage-cancel {
  margin-top: 8px;
}
.cancel-offer {
  margin-top: 10px;
  padding: var(--space-3);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface);
  box-shadow: var(--shadow-sm);
}
.cancel-offer-head {
  margin: 0 0 8px;
  font-size: var(--text-sm);
  font-weight: 700;
  color: var(--ink);
}
.cancel-reason {
  display: block;
  width: 100%;
  margin-top: 5px;
  padding: 9px 11px;
  border: 1px solid var(--line);
  border-radius: 9px;
  font-size: 13px;
}
.cancel-actions {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin-top: 10px;
}
.run-log {
  margin: 8px 0 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 3px;
}
.run-log-row {
  display: flex;
  gap: 8px;
  align-items: baseline;
  font-size: 11.5px;
  color: var(--ink-3);
}
.run-log-row--error .run-sum {
  color: var(--red);
}
.run-when {
  min-width: 64px;
  font-variant-numeric: tabular-nums;
}
.run-sum {
  flex: 1;
}
.run-trig {
  color: var(--ink-4);
  text-transform: uppercase;
  font-size: 10px;
  letter-spacing: 0.04em;
}
.danger-zone {
  border: 1px solid var(--red-border-2) !important;
  background: var(--red-bg-2);
  border-radius: 12px;
  padding: 16px !important;
  margin-top: 8px;
}
.danger-zone h3 {
  color: var(--red);
}
/* Each destructive action is a scoped sub-group so a device-only wipe reads distinctly from the
   irreversible-everywhere account delete (mis-click audit finding). */
.danger-scope {
  margin-top: 12px;
}
.danger-scope-head {
  margin: 0 0 2px;
  font-size: var(--text-sm);
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--ink-2);
}
.danger-scope--account {
  margin-top: 14px;
  padding-top: 12px;
  border-top: 1px solid var(--red-border-2);
}
.danger-scope--account .danger-scope-head {
  color: var(--red);
}
.danger-btn {
  background: var(--red-bg);
  color: var(--red);
  border: none;
  padding: 9px 14px;
  border-radius: 9px;
  font-weight: 600;
  cursor: pointer;
  margin-top: 4px;
}

@media (hover: hover) and (pointer: fine) {
  .danger-btn:hover {
    background: var(--red-tint);
  }
}
.danger-btn:disabled {
  opacity: 0.5;
  cursor: default;
}
/* type-DELETE confirmation (RD-72) */
.delete-confirm {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-top: 8px;
}
.delete-confirm .danger-btn {
  background: var(--red);
  color: var(--white);
}

@media (hover: hover) and (pointer: fine) {
  .delete-confirm .danger-btn:hover {
    background: var(--red-strong);
  }
}
.delete-confirm.hidden,
.danger-btn.hidden {
  display: none;
}
.empty-state b {
  color: var(--ink);
}
/* ── ACCT-UX-3/4: account chip + sync-status surface — relocated into the shared sheet by the AUD-189 CSS extraction ── */
/* ACCT-UX-3: the persistent topbar account affordance — auth state visible on every route. */
.acct-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  max-width: 200px;
  padding: 5px 10px 5px 5px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--surface);
  color: var(--ink-2);
  font-size: 12.5px;
  font-weight: 600;
  cursor: pointer;
  /* VELVET-7b: keep the chip's own transition (it already animates background) and add
     the shared press-scale so its :active feedback eases in-family. */
  transition:
    background var(--t-1) var(--ease-out),
    transform var(--t-1) var(--ease-out);
  position: relative;
  z-index: 5; /* sit above the sticky sync/install banner (z-index 4) so it stays clickable (ACCT-UX-16) — matches .status-chip */
}

@media (hover: hover) and (pointer: fine) {
  .acct-chip:hover {
    background: var(--line-2);
  }
}
.acct-chip--out {
  /* Combined-review P2: the "Sign in" chip is an INTERACTION affordance. Its text/icon use the orange
     interaction accent (`--blue`), so its surface must be NEUTRAL — not the money-green accent-tint family
     (`--blue-bg`/`--blue-border` still hold green pending the BROAD wave-2 tint sweep). Orange-on-green read
     as incoherent; orange text on the base neutral pill is correct and decoupled from money. */
  padding: 7px 12px;
  color: var(--blue);
  border-color: var(--line);
  background: var(--surface);
}
.acct-chip--out .icon {
  width: 15px;
  height: 15px;
  color: var(--blue);
}
.acct-chip-av {
  width: 22px;
  height: 22px;
  flex: none;
  border-radius: 50%;
  display: grid;
  place-items: center;
  color: var(--white);
  font-size: 11px;
  font-weight: 700;
}
.acct-chip-email {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* the sync-status surface (ACCT-UX-4): opened from the masthead `.syncchip` (BROAD-V-SHELL) into this
   collapsible panel. The old `.status-chip` was replaced by `.syncchip` (V-SHELL section). */
.status-panel {
  position: fixed;
  top: 0;
  right: 0;
  width: 380px;
  max-width: 92vw;
  height: 100vh;
  background: var(--surface);
  box-shadow: -8px 0 30px var(--shadow-drawer);
  z-index: 20;
  overflow-y: auto;
  animation: drawer-in var(--t-3) var(--ease-drawer);
}
.status-body {
  padding: 16px 24px 48px;
}
.status-live {
  font-size: 12.5px;
  color: var(--blue);
  margin: 6px 0 0;
}
/* the Account panel (ACCT-UX-9): identity / Plan / privacy re-homed out of the settings drawer,
         mirroring the status/drawer slide-in pattern. `.devices-panel` (AR72-UI-4) aliases the same
         slide-in shell so the Connected-devices overlay is its own root (own Escape/focus spec) without
         duplicating this rule block — see overlay-runtime's OVERLAYS. */
.account-panel,
.devices-panel {
  position: fixed;
  top: 0;
  right: 0;
  width: 420px;
  max-width: 92vw;
  height: 100vh;
  background: var(--surface);
  box-shadow: -8px 0 30px var(--shadow-drawer);
  z-index: 20;
  overflow-y: auto;
  animation: drawer-in var(--t-3) var(--ease-drawer);
}

/* ── OVERLAY-EXIT (VELVET-6) ──────────────────────────────────────────────────────────────────────
   Symmetric-but-asymmetric close for every overlay that ENTERS with an animation: the slide-in panels
   enter with `drawer-in` (`--t-3` / `--ease-drawer`), the scrim + the centred reauth top-modal with
   `fade-in`. The
   RUNTIME (overlay-runtime.ts) re-homes the exact node morph detaches and adds a `--closing` modifier
   here to play the EXIT before unmounting it. The exit is deliberately FASTER than the enter
   (`--t-2` < `--t-3` — leaving needs less ceremony than arriving) and `--ease-out` to mirror the imported design law
   (velvet-target.html plays every exit on `--ease-out`). `forwards` holds the final off-screen/
   transparent frame until the runtime removes the node on `animationend`. The reauth modal is centred via
   `transform: translate(-50%,-50%)`, so it fades OUT (opacity only) — a translate keyframe would fight
   the centring — while the slide-in panels sweep off-screen on `translateX`. Placed AFTER the panel/scrim
   base rules so the equal-specificity `--closing` selector wins the `animation` shorthand. Reduced-motion
   is handled for free by the global guard above (`animation-duration: 0.001ms !important`) — the exit
   collapses to instant, no visible sweep, and `animationend` still fires so the node still fully
   unmounts. */
@keyframes drawer-out {
  from {
    transform: translateX(0);
  }
  to {
    transform: translateX(100%);
  }
}

@keyframes fade-out {
  from {
    opacity: 1;
  }
  to {
    opacity: 0;
  }
}
.drawer--closing,
.status-panel--closing,
.account-panel--closing,
.devices-panel--closing {
  animation: drawer-out var(--t-2) var(--ease-out) forwards;
}
.scrim--closing,
.reauth-dialog--closing {
  animation: fade-out var(--t-2) var(--ease-out) forwards;
}
/* ── end OVERLAY-EXIT ─────────────────────────────────────────────────────────────────────────────── */
.account-body {
  padding: 8px 24px 48px;
}
.account-body section {
  padding: 18px 0;
  border-bottom: 1px solid var(--line);
}
.account-body h3 {
  margin: 0 0 12px;
  font-size: 13px;
}
/* the account identity anchor — a tinted card leading with the deterministic avatar + email, so the
   most trust-critical panel opens with a real "this is you" block, not a bare hint line (elite review). */
.acct-identity {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin-bottom: var(--space-4);
  padding: var(--space-3);
  border-radius: var(--radius-sm);
  background: var(--bg-2);
}
.acct-identity-av {
  display: grid;
  flex: none;
  place-items: center;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  color: var(--white);
  font-size: var(--text-lg);
  font-weight: 700;
}
.acct-identity-meta {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}
.acct-identity-meta b {
  overflow: hidden;
  color: var(--ink);
  font-size: var(--text-md);
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* LIFE-4: the secondary "sign out of all devices" control sits under the primary Sign out button as a
   quiet text link; a top margin lifts it off the danger button (which has no bottom margin of its own). */
.acct-signout-all {
  margin-top: var(--space-2);
  font-size: var(--text-sm);
}

/* the Connected-devices panel (BR-2.4): the caller's linked device agents, listed with MASKED labels,
   each revocable behind an inline confirm. Reuses the account-panel slide-in shell + tokens. */
.dev-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  margin: var(--space-3) 0 0;
  padding: 0;
  list-style: none;
}
.dev-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--bg-2);
}
.dev-main {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
  margin-right: auto;
}
.dev-label {
  overflow: hidden;
  color: var(--ink);
  font-size: var(--text-md);
  font-weight: 600;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.dev-sub {
  color: var(--ink-3);
  font-size: var(--text-sm);
}
.dev-confirm {
  display: flex;
  gap: var(--space-2);
  align-items: center;
}
/* =====================================================================
   BROADSHEET PRIMITIVE KIT (BROAD-F2) — the shared editorial re-skin
   primitives every Wave-2 view lane CONSUMES (docs/BROADSHEET-PLAN.md
   §1b). Pure presentation over pre-formatted read models; the classes
   pair with the html-tagged fns in
   @bt/ui-core components/dashboard/editorial.ts. Colours / type / radii
   reference the Broadsheet token NAMES (var(--serif) / --mono / --stamp
   / --pos / --warn / --neg / --hair / --hair-strong / --sunken /
   --skeleton / --accent-ink …) which BROAD-F1 defines in tokens.css —
   they resolve once F1 merges (the existing --ink-N, --surface and
   --radius-N names are kept + re-valued by F1, so they resolve now too).
   ===================================================================== */

/* stamp — the mono punctuation chip (the only dark ink in the canvas) */
.stamp {
  display: inline-flex;
  align-items: center;
  height: 18px;
  padding: 0 6px;
  border-radius: var(--radius-xs);
  background: var(--stamp);
  color: var(--stamp-ink);
  font-family: var(--mono);
  font-size: 11px;
  line-height: 14px;
  letter-spacing: 0.4px;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  flex: none;
}

/* num — tabular lining figures (money + counts read as a ledger column) */
.num {
  font-variant-numeric: lining-nums tabular-nums;
}
.num-cents {
  color: color-mix(in srgb, currentcolor 38%, transparent);
}

/* masthead — the two-rule editorial hero (serif numeral between rules) */
.masthead {
  display: flex;
  align-items: flex-end;
  gap: 36px;
  padding: 20px 0 16px;
  border-top: 2px solid var(--ink);
  border-bottom: 1px solid var(--hair-strong);
}
.mast-label {
  margin-bottom: 6px;
  font-size: 12px;
  line-height: 16px;
  letter-spacing: 0.3px;
  color: var(--ink-3);
}
.mast-fig {
  font-family: var(--font-sans);
  font-size: 84px;
  line-height: 80px;
  letter-spacing: -0.02em;
  font-weight: 600;
  color: var(--ink);
}
.mast-fig--sub {
  font-size: 34px;
  line-height: 38px;
}
.mast-fig--pos {
  color: var(--pos);
}
.mast-note {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 10px;
  margin-top: 10px;
  font-size: 12px;
  letter-spacing: 0.3px;
  color: var(--ink-3);
}
.mast-side {
  display: flex;
  align-items: flex-end;
  gap: 28px;
  margin-left: auto;
}

/* folio — the mono dateline rule under the masthead */
.folio {
  display: flex;
  gap: 24px;
  padding: 8px 0;
  border-bottom: 1px solid var(--hair);
  font-family: var(--mono);
  font-size: 12px;
  line-height: 16px;
  letter-spacing: 0.2px;
  color: var(--ink-3);
  font-variant-numeric: tabular-nums;
}

/* band / yearband — section stat rules (compact band, bigger yearband) */
.band {
  display: flex;
  align-items: flex-end;
  gap: 28px;
  margin-bottom: 16px;
  padding: 14px 0 12px;
  border-top: 2px solid var(--ink);
  border-bottom: 1px solid var(--hair-strong);
}
.band-fig {
  font-family: var(--font-sans);
  font-size: 34px;
  line-height: 38px;
  letter-spacing: -0.015em;
  font-weight: 600;
  color: var(--pos);
}
.band-meta {
  margin-left: auto;
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: 0.2px;
  color: var(--ink-3);
  font-variant-numeric: tabular-nums;
}
.yearband {
  display: flex;
  align-items: flex-end;
  gap: 36px;
  margin-bottom: 20px;
  padding: 16px 0 14px;
  border-top: 2px solid var(--ink);
  border-bottom: 1px solid var(--hair-strong);
}
.yearfig {
  font-family: var(--font-sans);
  font-size: 40px;
  line-height: 44px;
  letter-spacing: -0.015em;
  font-weight: 600;
  color: var(--ink);
}
.yearfig--pos {
  color: var(--pos);
}

/* spark — the per-card burn sparkline (area + line + emphasised end dot).
   currentColor is driven by the tone modifier; the geometry is precomputed. */
.spark {
  width: 120px;
  height: 38px;
  overflow: visible;
  flex: none;
  color: var(--ink-2);
}
.spark-area {
  fill: currentColor;
  stroke: none;
  opacity: 0.08;
}
.spark-line {
  fill: none;
  stroke: currentColor;
  stroke-width: 1.5;
}
.spark-dot {
  fill: currentColor;
  stroke: none;
}
.spark--green {
  color: var(--pos);
}
.spark--amber {
  color: var(--warn);
}
.spark--rose {
  color: var(--neg);
}
.spark--muted {
  color: var(--ink-4);
}

/* onboard — the day-1 empty-state frame (ghost preview + fact + copy + CTA) */
.onboard {
  display: flex;
  align-items: center;
  gap: 24px;
  padding: 20px;
  border: 1px dashed var(--hair-strong);
  border-radius: var(--radius-xs);
  background: var(--surface);
}
.onboard .preview {
  position: relative;
  flex: none;
}
.onboard .preview .stamp {
  position: absolute;
  top: -8px;
  left: 50%;
  transform: translateX(-50%);
}
.fact {
  margin-bottom: 4px;
  font-size: 17px;
  line-height: 24px;
  letter-spacing: -0.01em;
  font-weight: 600;
}
.copy {
  max-width: 56ch;
  color: var(--ink-2);
}
.next {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-top: 12px;
}
.ghostrows {
  display: block;
  width: 180px;
  padding-top: 6px;
  flex: none;
}
.ghostrows i {
  display: block;
  height: 8px;
  margin: 8px 0;
  border-radius: var(--radius-xs);
  background: var(--skeleton);
}
.ghostrows i:nth-child(3) {
  width: 72%;
}
.ghostrows i:nth-child(4) {
  width: 88%;
}

/* plate — the graph-paper backdrop for the fee timeline */
.plate {
  border: 1px solid var(--hair);
  border-radius: var(--radius-xs);
  background: var(--sunken);
  background-image:
    repeating-linear-gradient(0deg, var(--hair) 0 1px, transparent 1px 8px),
    repeating-linear-gradient(90deg, var(--hair) 0 1px, transparent 1px 8px);
}

/* secthead — the section rule (serial stamp + serif h3 + a mono count) */
.secthead {
  display: flex;
  align-items: baseline;
  gap: 10px;
  margin: 28px 0 10px;
}
.secthead .stamp {
  align-self: center;
}
.secthead h3 {
  font-family: var(--font-sans);
  font-size: 24px;
  line-height: 30px;
  letter-spacing: -0.01em;
  font-weight: 600;
}
.count {
  font-size: 12px;
  letter-spacing: 0.3px;
  color: var(--ink-3);
}

/* ledger row — the editorial .row grid used by the renewal / expiring panels */
.rows {
  border-top: 1px solid var(--hair);
}
.row {
  display: grid;
  grid-template-columns: minmax(160px, 1fr) minmax(150px, auto) 44px 190px 110px;
  align-items: center;
  gap: 12px;
  height: 40px;
  padding: 0 16px;
  border-bottom: 1px solid var(--hair);
}
.row:last-child {
  border-bottom: 0;
}

@media (hover: hover) and (pointer: fine) {
  .row:hover {
    background: var(--sunken);
  }
}
.row-name {
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  color: var(--ink);
}
.row-meta {
  font-size: 12px;
  line-height: 16px;
  letter-spacing: 0.3px;
  white-space: nowrap;
  color: var(--ink-3);
}
.row-due {
  font-size: 12px;
  letter-spacing: 0.3px;
  text-align: right;
  color: var(--ink-3);
}
.row-due--warn {
  color: var(--warn);
}
.row-due--neg {
  color: var(--neg);
}
.row-money {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 2px;
}
.row-money .cap {
  color: var(--ink-3);
}
.row-action {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 8px;
}
.row.is-used .row-name {
  color: var(--ink-3);
}

/* cardline — the credit-history card row (gauge + identity + spark + value) */
.cardline {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 14px 16px;
}
.cardline + .cardline {
  border-top: 1px solid var(--hair);
}
.cardline-name {
  font-weight: 600;
  letter-spacing: -0.01em;
}
.cardline-meta {
  font-size: 12px;
  letter-spacing: 0.3px;
  color: var(--ink-3);
}
.cardline-val {
  font-size: 17px;
  font-weight: 600;
  letter-spacing: -0.01em;
}

/* serial — the mono last-5 in a card identity lockup */
.serial {
  font-family: var(--mono);
  font-size: 11px;
  line-height: 14px;
  letter-spacing: 0.4px;
  color: var(--ink-3);
  font-variant-numeric: tabular-nums;
}

/* shared editorial containers — panel, avatar, progress bar */
.panelbox {
  border: 1px solid var(--hair);
  border-radius: var(--radius-xs);
  background: var(--surface);
}
.avatar {
  width: 20px;
  height: 20px;
  border-radius: 50%;
  border: 1px solid var(--hair);
  background: var(--sunken);
  color: var(--ink-2);
  font-size: 12px;
  line-height: 18px;
  text-align: center;
  flex: none;
}
/* link — the inline text button (accent ink, underline on hover) */
.link {
  height: auto;
  padding: 0;
  background: none;
  color: var(--accent-ink);
}

@media (hover: hover) and (pointer: fine) {
  .link:hover {
    text-decoration: underline;
  }
}

/* card-art size modifiers (Broadsheet head / fee / micro) — reuse the SVG
   face from card-art.ts; these only add the Broadsheet render sizes. */
.card-art--head {
  width: 88px;
  height: 56px;
}
.card-art--fee {
  width: 120px;
  height: 76px;
}
.card-art--micro {
  width: 44px;
  height: 28px;
}
/* =====================================================================
   PER-VIEW CSS SECTION BLOCKS (BROAD-F2 scaffolding) — one banner-
   delimited block per Wave-2 view lane so each lane edits ONLY its own
   block and the parallel worktrees don't collide in components.css
   (collision matrix, docs/BROADSHEET-PLAN.md §3). Each block is empty
   until its lane lands; V-MOBILE's responsive tail stays LAST (below).
   ===================================================================== */

/* === V-ACTNOW === Act now: two-state masthead + folio + onboarding +
   expiring per-account panels (BROAD-V-ACTNOW). Consumes the F2 primitives
   (masthead/folio/onboard/stamp/serial) + the shared row/secthead
   grid; adds only the Act-now-specific note chips + per-card panel chrome.
   The panel block is namespaced `an-acct-*` (NOT the live warm-bento `.acct*`)
   so it can't collide with the still-Bento Explore panels or duplicate a
   selector. */

/* masthead note chips — the "against $Y in fees" / at-risk hints under the hero */
.an-flag {
  display: inline-flex;
  align-items: center;
  height: 20px;
  padding: 0 8px;
  border: 1px solid var(--hair-strong);
  border-radius: var(--radius-xs);
  background: var(--sunken);
  color: var(--ink-2);
  font-size: 12px;
  letter-spacing: 0.3px;
}
/* onboard "start there" hint — the single next action's pointer at the soonest credit */
.an-hint {
  color: var(--ink-3);
}

/* per-card expiring panel — the identity lockup + "$X at risk" subtotal head, ledger rows below */
.an-acct {
  margin-bottom: 16px;
}
.an-acct-head {
  display: flex;
  align-items: center;
  gap: 16px;
  width: 100%;
  min-height: 72px;
  padding: 12px 16px;
  background: none;
}

@media (hover: hover) and (pointer: fine) {
  .an-acct-head:hover {
    background: var(--sunken);
  }
}
.an-acct-id {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}
.an-acct-name {
  font-size: 17px;
  line-height: 22px;
  font-weight: 600;
  letter-spacing: -0.01em;
  white-space: nowrap;
  color: var(--ink);
}
.an-acct-owner {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  line-height: 16px;
  color: var(--ink-3);
}
.an-acct-left {
  margin-left: auto;
  font-size: 22px;
  line-height: 28px;
  font-weight: 600;
  letter-spacing: -0.01em;
  white-space: nowrap;
}
.an-acct-left--neg {
  color: var(--neg);
}
.an-acct-u {
  font-size: 12px;
  font-weight: 450;
  letter-spacing: 0.3px;
  color: var(--ink-3);
}

/* all-caught-up state (HORIZON-S4) — nothing expiring right now (the masthead still shows the YTD
   accomplishment): the celebration hero (coins + checkmark medallion) over a calm centred line with the
   one serif aside. */
.an-caughtup {
  padding: var(--space-5) 16px;
  text-align: center;
}
.an-caughtup-line {
  margin: var(--space-3) 0 0;
  color: var(--ink-3);
}
.an-flat-bucket {
  display: contents;
}
/* === V-FEES === Fees & bonuses: graph-paper timeline + fee grid +
   renewals table + credit-history gauges/sparks + signup bonuses
   (BROAD-V-FEES). Consumes the F2 primitives above (.plate/.secthead/
   .yearband/.spark/.gauge/.cardline/.row/.panelbox/.num); these
   rules add only the V-FEES-specific pieces. Tokens are the F2 Broadsheet
   vocabulary (var(--serif)/--mono/--pos/--neg/--warn/--sunken/--hair …). */

/* renewal timeline — the graph-paper centrepiece (the plate bg comes from .plate) */
.timeline {
  position: relative;
  height: 170px;
  margin: 8px 0;
}
.timeline-base {
  position: absolute;
  top: 118px;
  right: 16px;
  left: 16px;
  height: 2px;
  background: var(--hair-strong);
}
.tick {
  position: absolute;
  top: 90px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  transform: translateX(-50%);
}
.tick i {
  display: block;
  width: 3px;
  height: 28px;
  border-radius: 1px;
  background: var(--ink);
}
.tick--green i {
  background: var(--pos);
}
.tick--amber i {
  background: var(--warn);
}
.tick--rose i {
  background: var(--neg);
}
.tick--muted i {
  background: var(--ink-4);
}
.tick--today {
  top: 76px;
}
.tick--today i {
  width: 2px;
  height: 42px;
  border-radius: 0;
  background: var(--ink);
}
.tick--off {
  opacity: 0.5;
}
.tick-top {
  position: absolute;
  bottom: 64px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
}
.tick--label-high .tick-top {
  bottom: 104px;
}
.tick--label-high .tick-date {
  position: absolute;
  bottom: 84px;
}
.tick--label-hidden .tick-top,
.tick--label-hidden .tick-date {
  display: none;
}
.tick:focus-visible i {
  outline: 2px solid var(--ink);
  outline-offset: 3px;
}
.tick-amt {
  font-family: var(--font-sans);
  font-size: 15px;
  line-height: 20px;
  font-weight: 600;
  letter-spacing: -0.01em;
  white-space: nowrap;
  color: var(--ink);
}
.tick-date {
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: 0.2px;
  white-space: nowrap;
  color: var(--ink-3);
}
.tick-flag {
  padding: 1px 6px;
  border-radius: var(--radius-xs);
  font-family: var(--mono);
  font-size: 10px;
  line-height: 14px;
  letter-spacing: 0.2px;
  white-space: nowrap;
  background: var(--stamp);
  color: var(--stamp-ink);
}
.tl-months {
  position: absolute;
  top: 130px;
  right: 16px;
  left: 16px;
  height: 16px;
}
.tl-mtick {
  position: absolute;
  transform: translateX(-50%);
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.2px;
  color: var(--ink-4);
}

/* fee-decision grid + tiles */
.feegrid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 16px;
  margin: 16px 0 24px;
}
.feetile {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 16px;
}
.feetile-head {
  display: flex;
  align-items: center;
  gap: 12px;
}
.feetile-name {
  font-size: 17px;
  line-height: 22px;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--ink);
}
.feetile-fee {
  font-size: 14px;
  color: var(--ink-2);
}
.feetile-fee b {
  font-family: var(--font-sans);
  font-size: 24px;
  line-height: 28px;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--ink);
}
.feetile-copy {
  font-size: 13px;
  line-height: 18px;
  color: var(--ink-2);
}
.feepill {
  display: inline-flex;
  align-items: center;
  height: 20px;
  padding: 0 8px;
  margin-left: auto;
  border: 1px solid var(--hair);
  border-radius: var(--radius-xs);
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.2px;
  white-space: nowrap;
  background: var(--sunken);
  color: var(--ink-3);
  flex: none;
}
.feepill--neg {
  border-color: var(--neg);
  color: var(--neg);
}
.feepill--warn {
  border-color: var(--warn);
  color: var(--warn);
}
.feepill--neutral {
  color: var(--ink-3);
}

/* renewals table + the "Captured against it" cell */
.table {
  width: 100%;
  border-collapse: collapse;
}
.table th {
  padding: 8px 16px;
  border-bottom: 1px solid var(--hair-strong);
  font-size: 12px;
  line-height: 16px;
  font-weight: 400;
  letter-spacing: 0.3px;
  text-align: left;
  background: var(--sunken);
  color: var(--ink-3);
}
.table td {
  height: 40px;
  padding: 0 16px;
  border-bottom: 1px solid var(--hair);
  color: var(--ink);
}
.table th.r,
.table td.r {
  text-align: right;
}

@media (hover: hover) and (pointer: fine) {
  .table tbody tr:hover {
    background: var(--sunken);
  }
}
.table .mono {
  font-family: var(--mono);
  font-size: 12px;
  color: var(--ink-3);
}
.capfrac {
  display: inline-flex;
  align-items: center;
  justify-content: flex-end;
  gap: 8px;
}

/* credit-history: category strip + per-card period detail under the .cardline */
.catstrip {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 16px;
}
.catchip {
  display: inline-flex;
  align-items: baseline;
  gap: 8px;
  padding: 6px 12px;
  border: 1px solid var(--hair);
  border-radius: var(--radius-xs);
  font-size: 12px;
  background: var(--surface);
}
/* Horizon category vignette (HORIZON-S2) — the soft-3D still-life that re-expresses the per-category
   signal the S1 tint revalue flattened. A same-origin raster (img-src 'self'); sized small as a chip
   marker, centred against the baseline-aligned chip text. */
.vignette {
  width: 40px;
  height: 40px;
  flex: none;
  border-radius: var(--radius-sm);
  object-fit: cover;
}
.catchip .vignette {
  align-self: center;
}
.catchip-name {
  font-weight: 600;
  color: var(--ink);
}
.catchip-cap {
  color: var(--pos);
}
.catchip-for {
  color: var(--ink-3);
}
.catchip-share {
  font-family: var(--mono);
  color: var(--ink-4);
}
.histcard + .histcard {
  border-top: 1px solid var(--hair);
}
.cardline-gauge {
  flex: none;
}
.cardline-gauge .ring {
  width: 72px;
  height: 72px;
}
.cardline-id {
  flex: 1;
  min-width: 0;
}
.cardnet--pos {
  color: var(--pos);
}
.cardnet--neg {
  color: var(--neg);
}
.hpds {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 0 16px 12px 88px;
}
.hpd {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto auto auto;
  align-items: baseline;
  gap: 12px;
  font-size: 12px;
  color: var(--ink-3);
}
.hpd-name {
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  color: var(--ink-2);
}
.hpd-when {
  font-family: var(--mono);
  color: var(--ink-4);
}
.hpd-cap {
  text-align: right;
  color: var(--pos);
}
.hpd-for {
  text-align: right;
  color: var(--ink-4);
}
.hpd-cap--na {
  font-style: italic;
  color: var(--ink-4);
}

/* signup-bonus panel: figures + linear spend/pace bar + status/deadline pills */
.bonus {
  display: flex;
  align-items: center;
  gap: 20px;
  margin-bottom: 16px;
  padding: 20px;
}
.bonus-figs {
  flex: 1;
  min-width: 0;
}
.bonus-fig {
  margin-bottom: 8px;
  font-size: 15px;
}
.bonus-pills {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 8px;
  flex: none;
}

/* WB-1: signup spend uses a LINEAR rail. Fill = actual spend; thin marker = expected progress today;
   ringed endpoint = target. Geometry arrives preformatted from the read model. */
.bonus-progress {
  width: 100%;
  max-width: 520px;
  margin-top: 12px;
}
.bonus-progress-track {
  position: relative;
  height: 8px;
  border-radius: var(--radius);
  background: var(--hairline);
}
.bonus-progress-fill {
  position: absolute;
  inset: 0 auto 0 0;
  border-radius: inherit;
  transition: width var(--t-3) var(--ease-out);
}
.bonus-progress-fill--green {
  background: var(--money);
}
.bonus-progress-fill--rose {
  background: var(--red);
}
.bonus-progress-marker {
  position: absolute;
  top: -5px;
  width: 2px;
  height: 18px;
  background: var(--ink);
  transform: translateX(-1px);
}
.bonus-progress-target {
  position: absolute;
  top: -3px;
  right: -1px;
  width: 14px;
  height: 14px;
  border: 2px solid var(--ink-4);
  border-radius: 50%;
  background: var(--surface);
}
.bonus-progress-labels {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  margin-top: 8px;
  color: var(--ink-4);
  font-family: var(--mono);
  font-size: 11px;
}

/* horizon gauge (HORIZON §07) — the bespoke progress instrument (gauge.ts) that replaces the banned
   default rounded bar: a hairline baseline + quartile day-ticks, a tone light-band filled to the
   progress point, a haloed sun marker, a ringed goal flag, and a floating tabular value chip. Tone
   (band / halo / sun stroke) is set inline from a token in the component; the rest is tokened here.
   The SVG scales UNIFORMLY — width:100% + height:auto derives the height from the 440×74 viewBox
   (default preserveAspectRatio), so every circle + glyph stays true at any width, never the stretched
   ellipses of VELVET-17. max-width caps the instrument so the marker never balloons on a wide panel. */
.gauge {
  display: block;
  width: 100%;
  max-width: 520px;
}
.gauge-svg {
  display: block;
  width: 100%;
  height: auto;
}
.gauge-baseline {
  stroke: var(--hairline);
  stroke-width: 2;
}
.gauge-tick {
  stroke: var(--hair-strong);
  stroke-width: 1.5;
}
.gauge-band {
  stroke-width: 5;
  stroke-linecap: round;
  /* VELVET-8: the fill EASES to the new value. The band spans the full rail and reveals to the marker
     via stroke-dashoffset (the `.ring-fill` technique), so morph patching the offset transitions the
     length instead of the endpoint snapping — no x-stretch, the round cap stays true (VELVET-17). */
  transition: stroke-dashoffset var(--t-3) var(--ease-out);
}
.gauge-halo,
.gauge-sun {
  /* VELVET-8: the sun + its halo glide along the rail (cx) in lockstep with the fill, so the marker
     eases to the new percent rather than jumping (Horizon §08: marker eases 240ms). */
  transition: cx var(--t-3) var(--ease-out);
}
.gauge-halo {
  opacity: 0.16;
}
.gauge-sun {
  fill: var(--surface);
  stroke-width: 2.5;
}
.gauge-flag-ring {
  fill: none;
  stroke: var(--ink-4);
  stroke-width: 2;
}
.gauge-flag-dot {
  fill: var(--ink-4);
}
.gauge-chip-bg,
.gauge-chip-tip {
  fill: var(--ink);
}
.gauge-chip-num {
  fill: var(--canvas);
  font-size: 11.5px;
  font-weight: 600;
}
.gauge-edge {
  fill: var(--ink-4);
  font-size: 10.5px;
  font-weight: 600;
}
/* the COMPACT horizon gauge (gaugeCompact) — the same instrument sized for a dense inline cell (the
   act-now ledger `.row-money`, the renewals table `.capfrac`), replacing the retired 32×4 microbar. A
   fixed narrow width; height:auto (inherited from .gauge-svg) keeps it UNIFORMLY scaled — a true round
   marker on the hairline, never a stretched ellipse. It keeps the tone band + haloed sun, drops chrome. */
.gauge--compact {
  width: 88px;
}
.renewal-product {
  display: contents;
}
.bonus-product {
  display: contents;
}
/* === V-ALL === All benefits: band hero + search + collapse-by-default
   filter rail + group-by + loyalty secondary (BROAD-V-ALL). */

/* the band hero sits full-width above the base `.explore` [rail | list] flex row */
.explore-all {
  min-width: 0;
}

/* the Filters control (call 03): one button that toggles the rail. `--open` when the rail is shown;
   the accent dot marks that a facet filter is currently active. */
.filterbtn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  height: 32px;
  padding: 0 12px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--ink-2);
  font-weight: 600;
}

@media (hover: hover) and (pointer: fine) {
  .filterbtn:hover {
    border-color: var(--ink-4);
    color: var(--ink);
  }
}
.filterbtn--open {
  border-color: var(--ink);
  color: var(--ink);
}
.filterbtn-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--blue);
}

/* Desktop: the filter rail is COLLAPSED BY DEFAULT behind the Filters control (call 03). Opening it
   (toggleGroup `explore:filters` → `.explore--filters`) reveals the base sidebar. Mobile (≤560px) keeps
   its own chip-row rules (below) — scoped away here so the responsive rail is untouched. */
@media (width > 560px) {
  .explore .filterbar {
    display: none;
  }
  .explore--filters .filterbar {
    display: block;
  }
}

/* Loyalty balances: SECONDARY placement (call 10) — a quiet panel below the fleet, its point values
   labelled "est." in muted ink so a rough cash value is never mistaken for a captured-credit figure. */
.loyalty-val {
  color: var(--ink-3);
}
/* === V-SHELL === Masthead lockup (wordmark + Vol./No. dateline + tabrow)
   + freshness syncchip + first-sync banner + topbar (BROAD-V-SHELL). */

/* the masthead lockup: the serif wordmark + mono dateline between folio rules, the topbar actions on
   the right, and the tabrow of routes beneath. Broadsheet token names (--serif/--mono/--hair/--sunken)
   resolve once BROAD-F1 lands, matching the F2 primitive kit above. */
.lockup {
  position: sticky;
  top: 0;
  z-index: 10;
  padding: 14px 24px 0;
  background: var(--surface);
  border-bottom: 1px solid var(--hair);
}
.lockup-row {
  display: flex;
  align-items: baseline;
  gap: 16px;
  padding-bottom: 10px;
  border-bottom: 2px solid var(--ink);
}
/* re-skin the wordmark TYPE to the editorial serif (the wordmark TEXT/colour is the rebrand session's
   concern — brand.ts is untouched); scoped to the lockup so the sign-in wall lockup is unaffected. */
.lockup .brandmark {
  align-items: baseline;
  gap: 8px;
}
.lockup .brandmark-word {
  font-family: var(--font-sans);
  font-size: 22px;
  font-weight: 600;
  letter-spacing: 0.02em;
}
/* the mono ™ superscript on the masthead wordmark (PERK-6, matching the final Broadsheet lockup). */
.brandmark-tm {
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.3px;
  color: var(--ink-4);
  vertical-align: super;
  margin-left: 2px;
}
.lockup-edition {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.2px;
  color: var(--ink-3);
}
.lockup-right {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-left: auto;
  min-width: 0;
  max-width: 100%;
}
/* the ONE freshness chip (call 09): a calm mono line, RED-bordered when stale/failed. Opens #openStatus. */
.syncchip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 26px;
  padding: 0 9px;
  border: 1px solid transparent;
  border-radius: var(--radius-xs);
  background: none;
  font-family: var(--mono);
  font-size: 12px;
  color: var(--ink-3);
  letter-spacing: 0.2px;
  cursor: pointer;
  min-width: 0;
  max-width: 100%;
}
.syncchip-label {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.syncchip-detail {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

@media (hover: hover) and (pointer: fine) {
  .syncchip:hover {
    color: var(--ink);
  }
}
.syncchip-d {
  width: 6px;
  height: 6px;
  flex: none;
  border-radius: 50%;
  background: var(--green);
}
.syncchip--stale {
  color: var(--red);
  border-color: var(--red-bg);
  background: var(--red-bg);
}
.syncchip--stale .syncchip-d {
  background: var(--red);
}
/* while a sync is in flight the chip reflects THAT (yields to the live moment) — the accent, not red. */
.syncchip--live {
  color: var(--blue);
}
.syncchip--live .syncchip-d {
  background: var(--blue);
}
/* the topbar action buttons (Refresh / Export / Settings) — icon-only, aria-labelled for a11y + e2e. */
.iconbtn {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border: none;
  background: none;
  color: var(--ink-3);
  border-radius: var(--radius-xs);
  cursor: pointer;
  transition:
    background var(--t-1) var(--ease-out),
    transform var(--t-1) var(--ease-out);
}

@media (hover: hover) and (pointer: fine) {
  .iconbtn:hover {
    background: var(--sunken);
    color: var(--ink);
  }
}
.iconbtn .icon {
  width: 17px;
  height: 17px;
}
/* the tabrow: three route tabs with mono count badges + a hairline active underline in the accent. */
.tabrow {
  position: relative;
  display: flex;
  align-items: center;
  gap: 2px;
}
.tab {
  position: relative;
  display: flex;
  align-items: center;
  gap: 6px;
  height: 44px;
  padding: 0 12px;
  border: none;
  background: none;
  font-size: 14px;
  font-weight: 500;
  color: var(--ink-3);
  cursor: pointer;
  transition:
    color var(--t-1) var(--ease-out),
    transform var(--t-1) var(--ease-out);
}

@media (hover: hover) and (pointer: fine) {
  .tab:hover {
    color: var(--ink);
  }
}
.tab.is-active {
  color: var(--ink);
  font-weight: 600;
}
/* VELVET-8b: ONE shared ink bar under the active tab. The VIEW renders `.tab-ink` inside `.tabrow`
   (a stable node the keyed diff reuses, so a morph never wipes it); the RUNTIME positions it (transform
   + width off the active tab's box). Because it's one persistent element that only re-positions, the
   accent SLIDES between tabs through the shared motion scale instead of the old per-tab grow-in pseudo.
   The interaction accent (--blue → orange once F1 lands), never money-green. The global
   prefers-reduced-motion guard zeroes the transition. Width starts 0 so it's invisible until the runtime
   sizes it on the first render. */
.tab-ink {
  position: absolute;
  bottom: 0;
  left: 0;
  height: 2px;
  width: 0;
  background: var(--blue);
  border-radius: 2px 2px 0 0;
  transition:
    transform var(--t-3) var(--ease-in-out),
    width var(--t-3) var(--ease-in-out);
}
.tabcount {
  padding: 0 4px;
  border: 1px solid var(--line);
  border-radius: var(--radius-xs);
  background: var(--sunken);
  color: var(--ink-3);
  font-family: var(--mono);
  font-size: 10px;
  line-height: 14px;
  font-variant-numeric: tabular-nums;
}
.tab.is-active .tabcount {
  color: var(--ink-2);
  border-color: var(--hair-strong);
}
/* the sync progress hair on the bottom rule: fills as a sync runs, fades when idle. */
.syncline {
  position: absolute;
  left: 0;
  bottom: -1px;
  width: 0;
  height: 3px;
  background: var(--blue);
  opacity: 0;
  transition:
    width var(--t-3) linear,
    opacity var(--t-2) var(--ease-out);
}
.syncline.is-live {
  opacity: 1;
}
/* === V-DRAWER === Settings drawer: sync / reminders / appearance /
   behaviour / data export / privacy / danger zone (BROAD-V-DRAWER). Re-skins
   the body into editorial `.setgroup` sections (a mono label over its rows,
   divided by hairlines); the shared container (`.drawer*`), `.check-row`,
   `.privacy-*` and `.danger-*` rules re-skin via the BROAD-F1 token re-values.
   Colours/type/radii reference the Broadsheet token NAMES (var(--mono) /
   --hair / --hair-strong / --sunken …), resolved once F1 merges (§1a). */

/* an editorial settings group — a mono section label over its rows */
.setgroup {
  padding: 10px 0;
  border-bottom: 1px solid var(--hair);
}
.setgroup-label {
  padding: 6px 0 8px;
  font-family: var(--mono);
  font-size: 11px;
  line-height: 16px;
  letter-spacing: 0.4px;
  text-transform: uppercase;
  color: var(--ink-3);
}

/* a label/value row: label left, control right. The `.drawer-body .setrow`
   specificity intentionally beats the shared `.drawer-body label` block layout
   (the setrow labels wrap a single control), turning them into flex rows. */
.drawer-body .setrow {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  min-height: 44px;
  margin-bottom: 0;
  font-size: 13px;
  color: var(--ink-2);
}
.drawer-body .setrow > span {
  color: var(--ink-2);
}
/* the reminder-days row is subordinate to the master toggle (FEGAP-B1): dimmed while reminders are
   off, and its input is `disabled` so it dispatches nothing. */
.drawer-body .setrow--off {
  color: var(--ink-3);
}
.drawer-body .setrow--off > span {
  color: var(--ink-3);
}
.drawer-body .setrow--off .ctl-stepper {
  opacity: 0.5;
}
.drawer-body .setrow input[type="number"],
.drawer-body .setrow select {
  width: auto;
  min-width: 92px;
  max-width: 156px;
  margin: 0;
  padding: 6px 9px;
  border: 1px solid var(--hair-strong);
  border-radius: var(--radius-xs);
  background: var(--sunken);
  color: var(--ink);
  font-family: var(--mono);
  font-size: 12px;
  font-variant-numeric: tabular-nums;
}

/* data export — the three format buttons on one wrapping row */
.exportrow {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  padding: 4px 0 2px;
}
.export-btn {
  padding: 7px 12px;
  border: 1px solid var(--hair-strong);
  border-radius: var(--radius-xs);
  background: var(--surface);
  color: var(--ink);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.2px;
  cursor: pointer;
}

@media (hover: hover) and (pointer: fine) {
  .export-btn:hover {
    border-color: var(--ink-3);
    background: var(--sunken);
  }
}

/* a quiet mono footnote under a group (the design's `.privnote`) */
.privnote {
  margin: 6px 0 2px;
  font-size: 12px;
  line-height: 16px;
  letter-spacing: 0.2px;
  color: var(--ink-3);
}
/* ===== IOS-RESP phone layout (placed LAST, after every base component rule, so each @media override
   wins by source order — no specificity hacks). The left jobs nav becomes a fixed bottom TAB BAR, the
   Explore filter rail a horizontally-scrollable chip row, the status panel a full-width sheet, and every
   sticky edge respects env(safe-area-inset-*). Additive: nothing desktop is dropped, only reflowed. ===== */
@media (width <= 560px) {
  /* stack: the shell is no longer side-by-side — the nav drops to a fixed bottom bar (below) */
  .shell {
    display: block;
    min-height: 100vh;
  }

  /* the left rail → a fixed, safe-area-aware bottom tab bar. It keeps every nav + action it
     had on desktop (routes, refresh, export, settings) — just laid out horizontally. */
  .sidebar {
    position: fixed;
    inset: auto 0 0;
    z-index: 12;
    flex-direction: row;
    align-items: stretch;
    gap: 0;
    width: auto;
    height: auto;
    padding: 6px var(--safe-right) calc(6px + var(--safe-bottom)) var(--safe-left);
    border-top: 1px solid var(--line);
    border-right: none;
    box-shadow: var(--shadow-lg);
  }
  .side-brand {
    display: none;
  }
  .side-nav {
    flex: 1;
    flex-direction: row;
    gap: 2px;
  }
  .side-actions {
    flex-direction: row;
    gap: 2px;
    margin-top: 0;
    padding-top: 0;
    border-top: none;
  }
  /* each tab is a touch target ≥44px tall, icon-over-label, evenly distributed */
  .side-item {
    flex: 1;
    flex-direction: column;
    gap: 3px;
    min-height: 44px;
    padding: 6px 4px;
    font-size: 11px;
    text-align: center;
    white-space: nowrap;
  }
  .side-item .icon {
    width: 20px;
    height: 20px;
  }
  .export-locked {
    flex-direction: row;
  }

  /* the main column goes full-width; reserve room at the bottom for the tab bar + safe area,
     and pad the top for the notch when this is the iOS embed */
  .main {
    padding: calc(18px + var(--safe-top)) calc(16px + var(--safe-left))
      calc(var(--tabbar-h) + var(--safe-bottom) + 24px) calc(16px + var(--safe-right));
  }

  /* Explore: the filter rail stops being a 268px side column and becomes a horizontally
     scrollable chip row above the list — the standard mobile filter pattern. */
  .explore {
    display: block;
  }
  /* scoped to `.explore .filterbar` (0,2,0) so it beats the unconditional base
     `.filterbar` rule that follows all the @media blocks — a bare `.filterbar`
     here (0,1,0) loses on source order and the chip row silently dies (AUD-281). */
  .explore .filterbar {
    position: static;
    width: auto;
    max-height: none;
    flex-direction: row;
    gap: 10px;
    overflow: auto hidden;
    margin-bottom: 16px;
    padding: 12px 14px;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
  }
  .filterbar::-webkit-scrollbar {
    display: none;
  }
  .fb-head {
    flex: none;
    flex-direction: column;
    align-items: flex-start;
    gap: 4px;
    padding-bottom: 0;
    border-bottom: none;
    border-right: 1px solid var(--line);
    padding-right: 12px;
  }
  .fb-group {
    flex: none;
    width: 180px;
    padding: 0;
    border-top: none;
    border-left: 1px solid var(--line-2);
    padding-left: 12px;
  }
  .fb-group:first-of-type {
    border-left: none;
    padding-left: 0;
  }
  .fb-opts {
    max-height: 132px;
  }
  /* checkboxes get a ≥44px row hit area on touch */
  .fb-opt {
    min-height: 44px;
    padding: 8px;
  }

  /* sticky status/honesty banners must clear the notch on the iOS embed */
  .banner--sticky {
    top: calc(8px + var(--safe-top));
  }

  /* controls + cards go full-width and gain comfortable touch sizing */
  .ex-controls .search {
    margin-left: 0;
  }
  .search {
    width: 100%;
  }
  .search input {
    min-height: 44px;
  }
  .seg {
    min-height: 40px;
    padding: 9px 14px;
  }
  .btn,
  .side-item,
  .danger-btn {
    min-height: 44px;
  }

  /* the settings drawer becomes a near-full-screen sheet with safe-area gutters */
  .drawer {
    width: 100vw;
    max-width: 100vw;
    padding-bottom: var(--safe-bottom);
  }
  .drawer-head {
    padding: calc(16px + var(--safe-top)) calc(18px + var(--safe-right)) 16px
      calc(18px + var(--safe-left));
  }
  .drawer-body {
    padding-right: calc(18px + var(--safe-right));
    padding-left: calc(18px + var(--safe-left));
  }

  /* ACCT-UX top-chrome on phone (the account chip + sync-status panel master added AFTER this branch's
     IOS-RESP work): give the chip/status pills a ≥44px touch target, and let the status panel be a
     full-width, safe-area-aware sheet instead of a 380px right drawer that crowds the narrow viewport
     and overlaps the bottom tab bar. */
  .acct-chip,
  .status-chip {
    min-height: 44px;
  }
  .status-panel,
  .account-panel,
  .devices-panel {
    width: 100vw;
    max-width: 100vw;
  }
  .status-body,
  .account-body {
    padding-top: calc(16px + var(--safe-top));
    padding-bottom: calc(48px + var(--safe-bottom));
  }

  /* AR72-UI-MOBILE: the ledger/bonus/value-history rows use rigid multi-column grids sized for the desktop
     rail (the `.row` track set alone is ≈654px min) with no phone override, so they overflowed a 390px
     viewport. On phone the flexible column shrinks to 0 (minmax(0,1fr)) and the fixed columns stack into an
     explicit mobile reading order — name+amount on the first line, reset/deadline + action below — so
     `scrollWidth <= innerWidth` holds and no owner/amount relationship is lost. */
  .row {
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-areas:
      "name  money"
      "meta  action"
      "due   action";
    height: auto;
    gap: 2px 10px;
    padding: 10px 12px;
    align-items: center;
  }
  .row-name {
    grid-area: name;
  }
  .row-meta {
    grid-area: meta;
  }
  .row-due {
    grid-area: due;
    text-align: left;
  }
  .row-money {
    grid-area: money;
    justify-self: end;
  }
  .row-action {
    grid-area: action;
    place-self: center end;
  }

  /* signup-bonus panel: figures + pills stack on phone; the linear pace rail remains fluid. */
  .bonus {
    flex-direction: column;
    align-items: stretch;
    gap: 12px;
    padding: 16px;
  }
  .bonus-pills {
    flex-flow: row wrap;
    align-items: center;
  }

  /* value-history perk row: let the name column shrink so the trailing figures never force overflow. */
  .vh-prow {
    grid-template-columns: minmax(0, 1fr) auto auto auto;
  }

  /* masthead lockup: the brand + freshness + account-chip row had desktop 24px side padding and a 200px
     account chip that overflowed 390px. Drop the side padding to the safe-area, shrink the wordmark + the
     account chip (its email still ellipsis-truncates), and allow the right group to wrap as a last resort. */
  .lockup {
    padding-right: var(--safe-right);
    padding-left: var(--safe-left);
  }
  .lockup-row {
    flex-wrap: wrap;
    gap: 8px 10px;
  }
  .lockup-right {
    flex: 1 1 200px;
    flex-wrap: wrap;
    justify-content: flex-end;
  }
  .lockup .brandmark-word {
    font-size: 18px;
  }
  .acct-chip {
    max-width: 150px;
  }

  /* value-review data table: the 16px cell padding pushed the table's intrinsic width past the phone
     viewport. Tighten the horizontal cell padding so the table fits without a horizontal scroll. */
  .table th,
  .table td {
    padding: 8px 10px;
  }

  /* Act-now HERO masthead: the two-column flex (numeral | figures + a 148px at-risk ring) held the ring at
     a fixed right edge (~372px) that overflowed below that width. Let the masthead wrap so the ring group
     drops below the numeral instead of being pinned right, shrink the ring, and tighten the gaps. */
  .masthead {
    flex-wrap: wrap;
    gap: 12px 24px;
  }
  .mast-side {
    margin-left: 0;
    gap: 20px;
  }
  .ring--lg,
  .ring--lg .ring {
    width: 108px;
    height: 108px;
  }
}

/* tighter phone tier (small phones / iPhone SE-class widths) */
@media (width <= 380px) {
  .main {
    padding-right: calc(12px + var(--safe-right));
    padding-left: calc(12px + var(--safe-left));
  }
  .side-item {
    font-size: 10px;
    padding: 6px 2px;
  }
  .fb-group {
    width: 158px;
  }

  /* AR72-UI-MOBILE small tier (≤380, incl. 320/360): tighten the value-review table cells further so the
     right-aligned figures column fits without a horizontal scroll. */
  .table th,
  .table td {
    padding: 6px 8px;
    font-size: 11px;
  }
}

@media (width <= 780px) {
  .content {
    padding: 16px;
  }
}

/* Tablet-only filter stack: bounded to 561–780 so it does NOT override the ≤560 phone
   chip-row above (both queries used to match on phones and this LATER, equal-specificity
   block silently won — killing the scroll chip row). Desktop (>780) and tablet (561–780)
   are unchanged; phones (≤560) now keep their flex chip-row layout. */
@media (561px <= width <= 780px) {
  .explore .filterbar {
    width: 100%;
    position: static;
    max-height: none;
    display: block;
    overflow: visible;
  }
  .fb-group {
    display: block;
  }
  .fb-opts {
    max-height: 300px;
  }
}
/* ── BR-3.5 / BR-3.6: the site Bridge chip ──────────────────────────────────────────────────────────
   perkival.com's third "light" surface (alongside the extension toolbar icon + the popup status chip):
   an unobtrusive fixed pill stating whether a capture agent is reachable. The tone rides a
   `bridge-chip--*` modifier (dot colour only); the label always carries the state in WORDS, so meaning
   never depends on colour alone. Mounted only by the browser web build (apps/web main.ts). */
.bridge-chip-slot {
  position: fixed;
  right: var(--space-4);
  bottom: var(--space-4);
  z-index: 6;
}
.bridge-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  height: 28px;
  padding: 0 var(--space-3);
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--surface);
  box-shadow: var(--shadow-sm);
  font-family: var(--font-sans);
  font-size: var(--text-xs);
  color: var(--ink-2);
}
.bridge-chip-dot {
  width: 7px;
  height: 7px;
  flex: none;
  border-radius: 50%;
  background: var(--ink-3);
}
.bridge-chip-label {
  white-space: nowrap;
}
/* PRESENT + live-sync-capable → money green; DETECTING → the interaction accent; ABSENT/view-only → grey;
   RELOAD-NEEDED → amber warn; SYNC-FAILED → ember red. Without the warn/bad tones the reachable
   "Reload page"/"Sync failed" dots fell back to grey --ink-3, indistinguishable from idle. */
.bridge-chip--ok .bridge-chip-dot {
  background: var(--green);
}
.bridge-chip--busy .bridge-chip-dot {
  background: var(--blue);
}
.bridge-chip--idle .bridge-chip-dot {
  background: var(--ink-3);
}
.bridge-chip--warn .bridge-chip-dot {
  background: var(--warn);
}
.bridge-chip--bad .bridge-chip-dot {
  background: var(--red);
}

/* AR72-SYNC17-4: the website's authoritative Bridge action/status card. Its receipt-backed copy lives in
   ui-core; this layer only gives the semantic section a compact responsive layout and preserves the shared
   44px button target. */
.bridge-status {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  margin-bottom: var(--space-4);
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  background: var(--surface);
}
.bridge-status h2,
.bridge-status p {
  margin: 0;
}
.bridge-status h2 {
  font-size: var(--text-sm);
}
.bridge-status p {
  margin-top: var(--space-1);
  color: var(--ink-2);
  font-size: var(--text-xs);
}
.bridge-status .btn {
  min-height: 44px;
  flex: none;
}

@media (width <= 540px) {
  .bridge-status {
    align-items: stretch;
    flex-direction: column;
  }
}

/* ===== VELVET-14: a11y craft floor — semantic-element resets + 44px hit targets =====
   The a11y sweep converted styled generics into real landmarks/headings (div.main → <main>, the masthead
   wordmark span → <h1>, each route-hero kick span → <h2>, each settings-group label div → <h3>). These
   rules neutralise the UA heading defaults (margin, and the bold weight on the mono setgroup label) so the
   NEW elements render byte-identically to the styled originals — zero visual change (§0.2). Element-
   qualified so they never duplicate the base class rules (stylelint no-duplicate-selectors). */
h1.brandmark-word {
  margin: 0;
}
h2.route-hero-kick {
  margin: 0;
}
h3.setgroup-label {
  margin: 0;
  font-weight: 400;
}

/* 44px minimum pointer target (WCAG 2.5.5) for the icon-only buttons whose VISUAL size stays smaller: a
   transparent ::after enlarges what a click/tap lands on to 44×44 with NO layout shift (the button box is
   unchanged). Covers the drawer/status close `×` (.icon-btn, ~26px) and the topbar Refresh/Settings
   (.iconbtn, 32px). The pad is anchored to the button's RIGHT edge (right:0) and grows leftward, never
   past the button's right side — these buttons sit at the right of their container (the masthead action
   cluster, the drawer/status panel head), so a centered pad would push the rightmost one ~6px past the
   viewport and reintroduce the horizontal-overflow the reflow-floor e2e forbids (AR72-UI-MOBILE-ZOOM). */
.icon-btn::after,
.iconbtn::after {
  content: "";
  position: absolute;
  top: 50%;
  right: 0;
  width: 44px;
  height: 44px;
  transform: translateY(-50%);
}
/* ===== VELVET-16: one designed form-control kit =====
   Native inputs remain in the DOM for keyboard/AT behavior; appearance is replaced only in normal colour
   modes. Forced colours restores the platform rendering so a system border/check never disappears. */

.ctl-check,
.ctl-switch {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  color: var(--ink-2);
  font-size: 13px;
  cursor: pointer;
}
.drawer-body .ctl-check,
.drawer-body .ctl-switch {
  display: flex;
}
.ctl-check > input[type="checkbox"],
.ctl-switch > input[type="checkbox"] {
  appearance: none;
  position: relative;
  flex: none;
  margin: 0;
  border: 1px solid var(--hair-strong);
  background: var(--surface);
  cursor: pointer;
}
.ctl-check > input[type="checkbox"] {
  width: 15px;
  height: 15px;
  border-radius: 4px;
}
.ctl-check > input[type="checkbox"]::after {
  content: "";
  position: absolute;
  inset: 1px;
  background: var(--white);
  opacity: 0;
  mask: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='m5 12 5 5L20 7' fill='none' stroke='black' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'/></svg>")
    center / 12px 12px no-repeat;
}
.ctl-check > input[type="checkbox"]:checked {
  border-color: var(--azure);
  background: var(--azure);
}
.ctl-check > input[type="checkbox"]:checked::after {
  opacity: 1;
}

.ctl-switch > input[type="checkbox"] {
  width: 28px;
  height: 16px;
  border-radius: 999px;
  background: var(--hairline);
  transition: background var(--t-1) var(--ease-out);
}
.ctl-switch > input[type="checkbox"]::after {
  content: "";
  position: absolute;
  top: 1px;
  left: 1px;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: var(--surface);
  box-shadow: var(--shadow-sm);
  transition: transform var(--t-1) var(--ease-out);
}
.ctl-switch > input[type="checkbox"]:checked {
  border-color: var(--azure);
  background: var(--azure);
}
.ctl-switch > input[type="checkbox"]:checked::after {
  transform: translateX(12px);
}

/* Selects keep native semantics but receive the same flat instrument shell. The two gradients form a
   currentColor chevron, avoiding a hard-coded data-URI colour that would drift in dark mode. */
.ctl-select {
  appearance: none;
  padding-right: 28px;
  border: 1px solid var(--hair-strong);
  border-radius: var(--radius-xs);
  background-color: var(--surface);
  background-image:
    linear-gradient(45deg, transparent 50%, currentcolor 50%),
    linear-gradient(135deg, currentcolor 50%, transparent 50%);
  background-position:
    calc(100% - 14px) 50%,
    calc(100% - 10px) 50%;
  background-size: 4px 4px;
  background-repeat: no-repeat;
  color: var(--ink);
}

.ctl-stepper {
  display: inline-flex;
  align-items: stretch;
  overflow: hidden;
  flex: none;
  border: 1px solid var(--hair-strong);
  border-radius: var(--radius-xs);
  background: var(--surface);
}
.ctl-step {
  display: grid;
  place-items: center;
  width: 28px;
  min-height: 28px;
  padding: 0;
  border: 0;
  background: var(--sunken);
  color: var(--ink-2);
  font: 600 16px/1 var(--font-sans);
  cursor: pointer;
}
.ctl-step:first-child {
  border-right: 1px solid var(--hair);
}
.ctl-step:last-child {
  border-left: 1px solid var(--hair);
}
.drawer-body .ctl-stepper input[type="number"] {
  appearance: textfield;
  width: 46px;
  min-width: 0;
  margin: 0;
  padding: 5px 3px;
  border: 0;
  border-radius: 0;
  background: var(--surface);
  color: var(--ink);
  font-family: var(--mono);
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  text-align: center;
}
.drawer-body .ctl-stepper input[type="number"]::-webkit-inner-spin-button,
.drawer-body .ctl-stepper input[type="number"]::-webkit-outer-spin-button {
  appearance: none;
  margin: 0;
}
.ctl-step:disabled,
.ctl-stepper input:disabled {
  cursor: not-allowed;
}
.ctl-theme .seg {
  padding: 6px 9px;
  font-size: 12px;
}

@media (hover: hover) and (pointer: fine) {
  .ctl-step:hover:not(:disabled) {
    background: var(--sunken);
    color: var(--ink);
  }
}

@media (forced-colors: active) {
  .ctl-check > input[type="checkbox"],
  .ctl-switch > input[type="checkbox"] {
    appearance: auto;
    border: 1px solid ButtonText;
    /* stylelint-disable-next-line scale-unlimited/declaration-strict-value -- forced-colors system palette */
    background: Canvas;
    forced-color-adjust: auto;
  }
  .ctl-check > input[type="checkbox"]::after,
  .ctl-switch > input[type="checkbox"]::after {
    display: none;
  }
  .ctl-select {
    appearance: auto;
    /* stylelint-disable-next-line scale-unlimited/declaration-strict-value -- forced-colors system palette */
    border-color: ButtonText;
    background-image: none;
  }
}
