/* ==========================================================================
   CASCADE LAYER — issue #25.
   Everything in this file sits in @layer sfr-chrome EXCEPT the dark-mode
   legibility block below, which is deliberately left unlayered.

   WHY LAYER AT ALL. Unlayered author styles beat EVERY layer, regardless of
   specificity or source order. Consuming apps run Tailwind v4, whose utilities
   live in @layer utilities, so an app could never override this package with a
   utility class. Measured on sfr-vendors: `.mt-10` gave 40px on a plain <div>
   and 0px on an .app-h2, because `.app-h1..h4 { margin: 0 }` here outranked it.
   Every mt-* on that page was dead.

   Apps import this file BEFORE their own stylesheet, so `sfr-chrome` is the
   first layer declared and therefore the WEAKEST — which is the point. The app
   wins; the package is the floor, not the ceiling.
   WHY THE ORDER LINE BELOW IS LOAD-BEARING. Layer order beats specificity, so a
   naive `@layer sfr-chrome { … }` makes this file the WEAKEST layer — and
   Tailwind v4's preflight zeroes margin AND padding on the UNIVERSAL selector in
   @layer base. A `*` in a stronger layer then beats `.app-body` here. Measured
   on sfr-vendors: the whole app went flush-left, .app-body padding 0, max-width
   none. Declaring `base, sfr-chrome` first pins this file ABOVE base and below
   everything the app declares afterwards (theme, components, utilities), which
   is exactly the slot it wants: it beats the reset, it loses to the app.
   ========================================================================== */
@layer base, sfr-chrome;
@layer sfr-chrome {
/* ============================================================
   OWNER: Blake Brose (someone@example.com). Canonical home:
   Smart-Foundation-Repair/sfr-app-chrome. This is Blake's App Chrome
   Standard — SEPARATE from Steve's "Design System v1" governance work in
   sfr-brand-guide. Do not overwrite from that project without Blake's OK.
   ============================================================
   SFR App Core — the shared chrome standard for every SFR web app
   ------------------------------------------------------------
   The canonical chrome is the brand guide itself (brand.fixitforever.com):
   navy header with the stacked white WORDMARK logo, uppercase VV Ruby nav
   links (honey on hover), pale-yellow ground, white content sheet, navy
   footer. Drop this ONE file into any SFR app and use the .app-* classes.

   Approved standard (2026-07-02, refined from the brand guide):
     - Header  = navy bar; LEFT stacked WORDMARK logo + small app label;
                 RIGHT uppercase VV Ruby nav (honey hover) + user avatar
     - Fonts   = VV Ruby Sans (display, uppercase) + Figtree (body)
     - Ground  = Pale #F1ECBE; content on a white sheet (docs) or white
                 panels full-width (dashboards)
     - Footer  = navy, centered wordmark + line
     - Purpose-fit: field app keeps its sidebar; public forms stay simple;
       all consume these SAME tokens + logo + favicon.

   LICENSING: VV Ruby Sans is licensed to SFR — do NOT ship the woff2 in
   public repos. Public/customer surfaces fall back to Montserrat Bold
   (all-caps), already in --sfr-font-display below.
   ============================================================ */

:root {
  /* Brand palette — locked, exact-match to the brand guide */
  --sfr-navy:  #1E2A3B;
  --sfr-honey: #EAB33F;
  --sfr-pale:  #F1ECBE;
  --sfr-white: #FFFFFF;
  --sfr-black: #000000;
  --sfr-ink:   #1E2A3B;

  /* Structural navy tints */
  --sfr-navy-600: #34465f;
  --sfr-navy-line: rgba(30, 42, 59, 0.16);
  --sfr-pale-edge: #d8d2a0;
  --sfr-line: rgba(30, 42, 59, 0.14);
  --sfr-gray-ground: #F5F6F8;

  /* Semantic status — SEPARATE from the honey accent, never reused as accent.
     THE RATIFIED SET: sfr-brand-guide/decisions.json `status-palette`
     (Locked, Steven, 2026-07-09). decisions.json is upstream for brand atoms
     (Blake, 2026-07-16); this package adopts, it does not invent.
     Adopted 2026-07-16 — the decision had sat unshipped for a fortnight because
     NOTHING GRADED IT. It is graded now (conformance-check rows below).
     The outgoing set was a darker-green/amber/vermilion trio; Steven's is darker
     and clears AA on the pale ground more comfortably.
     Names stay good/warn/bad so consumers don't break; decisions.json's own
     vocabulary (success/warning/error/info) is aliased beneath. */
  --sfr-good: #166534;
  --sfr-warn: #B5651D;
  --sfr-bad:  #B4231A;
  --sfr-info: #1D5E96;  /* NEW in the ratified set — not a rename, a 4th status */

  /* decisions.json vocabulary — aliases, so either name works. */
  --sfr-success: var(--sfr-good);
  --sfr-warning: var(--sfr-warn);
  --sfr-error:   var(--sfr-bad);

  /* POINTER yellow — NOT a status, and NOT the accent (C-1 / row 43).
     Its only job is "look here", after a deep link has navigated you somewhere.
     It must not be read as a warning, so it cannot reuse --sfr-warn, and it is
     deliberately lighter and greener than honey #EAB33F so a flash is not
     mistaken for a highlighted CTA.

     AUTHORITY: sfr-app-core.css otherwise adopts brand atoms from
     decisions.json and does not invent them. This one is a UI affordance rather
     than a brand atom — Blake, 2026-07-22, asked directly. If it is ever
     ratified upstream, this becomes an alias like the three above. */
  --sfr-point-yellow: #F2C230;
  /* REQ-32 names --color-status-yellow literally; alias it rather than rename,
     so the requirement's string is satisfied without breaking the --sfr-* rule
     every other token in this file follows. */
  --color-status-yellow: var(--sfr-point-yellow);

  /* Typography */
  --sfr-font-display: "VV Ruby Sans", "Montserrat", system-ui, -apple-system, "Segoe UI", sans-serif;
  --sfr-font-body:    "Figtree", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;

  /* Metrics — brand-guide faithful */
  --sfr-header-maxw: 1180px;   /* header inner width; dashboards may set to none */
  --sfr-content-maxw: 1180px;  /* white sheet width for document apps */
  /* THE base surface radius: sfr-brand-guide/decisions.json `radius`
     (Locked, Blake, 2026-07-03). semantic.json calls it "the ONE alias to
     re-point the whole site". Adopted 2026-07-16 — it had sat unshipped for a
     fortnight because nothing graded it; graded now.
     Applied to SURFACES (panels/cards). The pill and 50% circles are EXEMPT:
     a flat 14px would square off the honey CTA pill, which row 15 locks at
     999px and which `cta-color: Honey` (Locked) depends on visually. */
  --sfr-radius: 14px;
  --sfr-radius-pill: 999px;    /* pills + chips — EXEMPT from the base radius */
  --sfr-shadow: 0 10px 30px rgba(30, 42, 59, 0.10);
  --sfr-sheet-shadow: 0 0 60px rgba(30, 42, 59, 0.12);
}

/* ==========================================================================
   STATUS PALETTE — DARK
   decisions.json `status-palette-dark`, tier Locked, ratified by Blake
   2026-07-18 (CR-01 Decision 2).

   It was ratified and never shipped here, so every consuming app rendered the
   LIGHT status colours on a dark ground. Measured on the sfr-vendors features
   page 2026-07-21: --sfr-bad #B4231A gave 2.58:1 for a 30px bold count, under
   the 3:1 large-text floor — the most urgent thing on the page was the least
   legible one.

   Same failure the .app-card radius comment records: a governed value sitting
   unshipped because the CSS hardcoded the light one. Tokens are the mechanism
   that is supposed to prevent exactly this, so they have to carry BOTH themes.
   ========================================================================== */
.dark {
  --sfr-good: #3FB768;
  --sfr-warn: #D98A2B;
  --sfr-bad:  #E5645A;
  --sfr-info: #4C93CF;
  /* Lighter in dark for the same reason the four above are: the light-mode hex
     is tuned against a pale ground and goes muddy on navy. The alias follows it
     automatically — it points at this token, not at a hex. */
  --sfr-point-yellow: #F7DC6A;
}


/* ---- MINIMUM FONT SIZE — LOCKED (Blake, 2026-07-04) ----
   Nothing below 8pt anywhere. On the web that means:
     - 11px absolute floor for ALL text (0.6875rem)
     - 12px floor (0.75rem) for uppercase VV Ruby Sans — headers, table headers,
       letterspaced caps — because the condensed all-caps face reads smaller
       than its nominal size.
   No font-size in this file or any consuming app may go below these floors.
   Trigger case: the leverage-table header on opord.fixitforever.com. */

/* ---- Type discipline (uppercase baked into the display face) ----
   VV Ruby Sans is a CONDENSED face — it needs generous tracking or the letters
   "smash" together and read small. Default tracking is deliberately open. */
.sfr-display,
.app-label,
.app-nav a,
.app-footer__meta {
  font-family: var(--sfr-font-display);
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

/* ==========================================================================
   HEADER — navy bar, wordmark logo + app label, uppercase nav (brand guide)
   ========================================================================== */
.app-header {
  position: sticky;
  top: 0;
  z-index: 50;
  background: var(--sfr-navy);
  color: var(--sfr-white);
  box-shadow: 0 2px 14px rgba(0, 0, 0, 0.18);
}
.app-header__inner {
  max-width: var(--sfr-header-maxw);
  margin: 0 auto;
  padding: 12px 24px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 18px;
}
/* Dashboards that run full-width tables set this on the inner to align the
   header edges with the content. */
.app-header__inner--full { max-width: none; }

/* LEFT: stacked wordmark logo + small app label */
.app-header__brand {
  display: flex;
  align-items: center;
  gap: 14px;
  text-decoration: none;
  color: inherit;
  flex: none;
  min-width: 0;
}
.app-wordmark { height: 30px; width: auto; display: block; flex: none; }
/* App/sheet NAME — WHITE and slightly LARGER than the menu, so it reads as the
   title of the page, not a nav item. Honey stays reserved for nav hover/active. */
.app-label {
  font-size: 1.08rem;
  font-weight: 700;
  color: var(--sfr-white);
  letter-spacing: 0.05em;
  padding-left: 16px;
  border-left: 1px solid rgba(255, 255, 255, 0.28);
  white-space: nowrap;
}

/* RIGHT: nav links + user avatar */
.app-header__right {
  display: flex;
  align-items: center;
  gap: 22px;
  min-width: 0;
}
.app-nav { display: flex; align-items: center; gap: 28px; flex-wrap: wrap; }
.app-nav a {
  color: rgba(255, 255, 255, 0.80);
  font-size: 0.98rem;
  font-weight: 600;
  letter-spacing: 0.06em;   /* open the condensed face so the menu reads fast */
  text-decoration: none;
  transition: color 0.12s;
}
.app-nav a:hover { color: var(--sfr-honey); }
.app-nav a.is-active { color: var(--sfr-honey); }

/* Grouped / collapsible menu — when an app has many destinations, group them
   under a SUBJECT with a caret trigger + dropdown. Apps toggle .is-open (and it
   also opens on hover). Keeps the top bar clean and readable. */
.app-nav__group { position: relative; display: inline-flex; }
.app-nav__trigger {
  display: inline-flex; align-items: center; gap: 7px;
  font-family: var(--sfr-font-display); text-transform: uppercase;
  font-size: 0.98rem; font-weight: 600; letter-spacing: 0.06em;
  color: rgba(255, 255, 255, 0.80); background: none; border: 0;
  /* min-height 44 = row 20. `padding: 0` rendered these 82x19 — under even
     WCAG 2.2 AA's 24px floor. Row 20 measures at the 390px render, where the nav
     is a hidden drawer, so the ENTIRE desktop nav was never measured by anything.
     render-check now runs the tap-target probe at desktop too. */
  cursor: pointer; padding: 0 4px; min-height: 44px;
}
.app-nav__trigger::after { content: "▾"; font-size: 0.72em; opacity: 0.8; }
.app-nav__group:hover .app-nav__trigger,
.app-nav__group.is-open .app-nav__trigger { color: var(--sfr-honey); }
.app-nav__menu {
  position: absolute; top: calc(100% + 10px); left: 0; min-width: 240px;
  display: none; flex-direction: column; gap: 2px;
  background: var(--sfr-navy); border: 1px solid rgba(255, 255, 255, 0.14);
  border-radius: 10px; padding: 8px; box-shadow: 0 14px 34px rgba(0, 0, 0, 0.34);
  z-index: 60;
}
.app-nav__group:hover .app-nav__menu,
.app-nav__group.is-open .app-nav__menu { display: flex; }
/* Menu items stack an optional 1-line purpose description under the label. */
.app-nav__menu a {
  display: flex; flex-direction: column; align-items: flex-start; gap: 2px;
  padding: 9px 12px; border-radius: 7px; font-size: 0.94rem; letter-spacing: 0.05em;
  color: rgba(255, 255, 255, 0.82);
}
.app-nav__menu a:hover { background: rgba(255, 255, 255, 0.07); color: var(--sfr-honey); }
/* Active dropdown child — persistent honey state (JS + React emit a.active). */
.app-nav__menu a.active { color: var(--sfr-honey); background: rgba(255, 255, 255, 0.06); }
/* Active-group marker on the trigger (JS + React emit .is-current). */
.app-nav__trigger.is-current { color: var(--sfr-honey); }
/* Per-item 1-line purpose description. STANDARD: every nav tab and every
   dropdown item carries a concise 1-line purpose. Rendered as a muted subtitle
   inside dropdown menus; flat top-level tabs surface it as a title tooltip. */
.app-nav__desc {
  font-family: var(--sfr-font-body); text-transform: none;
  letter-spacing: 0.01em; font-weight: 400; font-size: 12px; line-height: 1.3;
  color: rgba(255, 255, 255, 0.58); max-width: 260px; white-space: normal;
}
.app-nav__menu a:hover .app-nav__desc,
.app-nav__menu a.active .app-nav__desc { color: rgba(255, 255, 255, 0.72); }

/* .app-user — LEGACY placeholder. A bare 28px circle with no content or behaviour.
   It shipped empty on every backoffice page: an avatar-shaped disc that looked
   clickable, wasn't, and told you nothing (Blake asked "what is this?" 2026-07-17).
   Kept only so apps that still ship <span class="app-user"> don't break. New work
   uses .app-account below. */
.app-user {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: var(--sfr-navy-600);
  border: 1px solid rgba(255, 255, 255, 0.28);
  flex: none;
}

/* .app-account — the real account control (2026-07-17). Replaces the dead
   .app-user placeholder. Shows the signed-in user's initials in the brand circle;
   click opens a menu with their email + Sign out. The app's own auth layer
   populates it (only that layer knows who is signed in) — the markup contract is:

     <div class="app-account">
       <button class="app-account__btn" aria-haspopup="true" aria-expanded="false" aria-label="Account">
         <span class="app-account__avatar">BB</span>   // initials, or an <img>
       </button>
       <div class="app-account__menu" role="menu" hidden>
         <div class="app-account__email">someone@example.com</div>
         <button class="app-account__signout">…Sign out</button>
       </div>
     </div>

   Opens on CLICK, not hover: an account menu is a deliberate action, unlike the
   nav dropdowns (row 39). Toggle .is-open; the app wires outside-click + Esc. */
.app-account { position: relative; flex: none; display: inline-flex; }
.app-account__btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 44px; height: 44px;            /* row 20 tap target — the visible circle is smaller */
  padding: 0; border: 0; background: none; cursor: pointer; border-radius: 50%;
}
.app-account__avatar {
  width: 32px; height: 32px; border-radius: 50%;
  background: var(--sfr-navy-600); border: 1px solid rgba(255, 255, 255, 0.28);
  color: var(--sfr-white); display: inline-flex; align-items: center; justify-content: center;
  font-family: var(--sfr-font-display); font-size: 0.8rem; font-weight: 700; letter-spacing: 0.02em;
  overflow: hidden;
}
.app-account__avatar img { width: 100%; height: 100%; object-fit: cover; }
.app-account__btn:hover .app-account__avatar,
.app-account__btn[aria-expanded="true"] .app-account__avatar {
  border-color: var(--sfr-honey); color: var(--sfr-honey);
}
.app-account__menu {
  position: absolute; top: calc(100% + 8px); right: 0; min-width: 220px;
  display: none; flex-direction: column;
  background: var(--sfr-navy); border: 1px solid rgba(255, 255, 255, 0.14);
  border-radius: 10px; padding: 10px; box-shadow: 0 14px 34px rgba(0, 0, 0, 0.34);
  z-index: 60;
}
.app-account.is-open .app-account__menu { display: flex; }
.app-account__email {
  color: rgba(255, 255, 255, 0.72); font-size: 0.82rem; line-height: 1.3;
  padding: 4px 8px 10px; margin-bottom: 6px; word-break: break-all;
  border-bottom: 1px solid rgba(255, 255, 255, 0.12);
}
.app-account__signout {
  display: flex; align-items: center; gap: 9px; width: 100%;
  min-height: 44px; padding: 0 8px;     /* row 20 */
  background: none; border: 0; cursor: pointer; border-radius: 7px;
  color: rgba(255, 255, 255, 0.82); text-align: left;
  font-family: var(--sfr-font-body); font-size: 0.9rem; font-weight: 600;
}
.app-account__signout:hover { background: rgba(255, 255, 255, 0.08); color: var(--sfr-honey); }
.app-account__signout svg { width: 17px; height: 17px; flex: none; }

@media (max-width: 760px) {
  .app-header__inner { flex-wrap: wrap; padding: 10px 16px; gap: 10px; }
  .app-wordmark { height: 24px; }
  .app-header__right { width: 100%; gap: 14px; }
  .app-nav { gap: 14px; font-size: 0.78rem; overflow-x: auto; }
}

/* ==========================================================================
   PAGE GROUND + CONTENT
   ========================================================================== */
.app-ground-pale { background: var(--sfr-pale); }
.app-ground-gray { background: var(--sfr-gray-ground); }
/* The ground MUST flip in dark. Without this the page keeps its light ground while
   text flips light — near-white on pale, ~1.1:1. Found live on
   backoffice.fixitforever.com 2026-07-16: <body class="app-ground-pale"> painted
   #F1ECBE while the app's own [data-theme=dark] flipped --text to #f2f0e8. Row 9's
   stated failure mode ("a single uncovered selector renders dark-on-dark"),
   inverted to light-on-light.
   #0e141d is not a new colour — it is the dark ground this file already assumes
   (see the .app-acc dark block below) and what scorecard/jobcosting already paint.
   NOTE: conformance-check could not catch this. Row 7's `flipsSurface` suppressor
   switches off EVERY light-surface violation as soon as any .dark rule redefines
   --sfr-pale/--bg/--surface/--card — the same over-broad exemption as the
   var(--sfr-white) gap in LIGHT_BG. Both are tracked in .planning/STATE.md. */
.dark .app-ground-pale,
.dark .app-ground-gray { background: #0e141d; }

/* .app-body — the content column. FULL-WIDTH with 24px gutters, matching
   studio.fixitforever.com (Blake's width standard, 2026-07-17: "closer to hr, or
   even better studio"). NOT a narrow centred column — backoffice was capped at
   800px and read as cramped. Data consoles want the width; prose blocks cap their
   OWN measure (.app-lede is 62ch; a reading page uses .app-sheet below). 16px
   gutters on mobile. This is canonical here so every app inherits one width, not a
   per-page copy. */
.app-body { width: 100%; margin: 0 auto; padding: 0 24px 64px; }
@media (max-width: 767px) { .app-body { padding: 0 16px 48px; } }

/* ── WIDTH BY CONTENT TYPE (row 41, Blake 2026-07-17) ──────────────────────────
   The page (.app-body) is full-width, but a full-width page is not a licence for
   full-width TEXT — comms prose ran 183 chars/line before this. Width follows what
   the content IS:
     • Data — tables, card grids, dashboards, kanban, media galleries → FULL width
       (they earn the screen). Just use .app-body; tables are width:100%.
     • Prose — rules, docs, ledes, running paragraphs → a ~66ch MEASURE (.app-prose).
       Nobody reads a 180-char line. .app-lede is the tighter 62ch title lede.
     • Long-form reading — an article/newsletter body → ~72ch (.app-reading).
     • Forms — one readable column, NOT stretched → ~34rem (.app-form). A field
       spanning 1400px is unusable; the eye loses the label.
     • Reference "sheet" documents → centered 1180px sheet (.app-sheet, below).
   Measures are in ch so they track the font, and left-aligned inside the full-
   width page. */
.app-prose   { max-width: 66ch; }
.app-reading { max-width: 72ch; }
.app-form    { max-width: 34rem; }   /* ~544px — a single readable form column */

/* Document / reference apps — centered white sheet on the pale ground */
.app-sheet {
  max-width: var(--sfr-content-maxw);
  margin: 0 auto;
  background: var(--sfr-white);
  box-shadow: var(--sfr-sheet-shadow);
}

/* Dashboards — full-width content region with white panels/cards */
.app-shell { width: 100%; margin: 0 auto; padding: 24px 24px 64px; }
.app-panel,
.app-card {
  background: var(--sfr-white);
  border: 1px solid var(--sfr-line);
  /* Was a hardcoded 20px. Now THE base surface radius (decisions.json: 14px,
     Locked, Blake 2026-07-03) — the card is the surface that decision is about.
     Hardcoding it here is what let the ratified value sit unshipped. */
  border-radius: var(--sfr-radius);
  box-shadow: var(--sfr-shadow);
}
/* Dark surface for the panel/card. WITHOUT this the panel stays WHITE on the dark
   ground — a blinding near-white surface (rubric row 9) that also renders any
   dark-flipped ink on top of it (chart axes, chart lines) as light-on-light.
   Found 2026-07-16 by the <SFRChart> render proof.
   #141c28 is not a new colour: it is what .dark .app-acc__item already uses here,
   and it matches scorecard.fixitforever.com's live dark card exactly
   (measured rgb(20,28,40) / border rgba(255,255,255,0.12)).
   NOTE: conformance-check.mjs's LIGHT_BG regex does NOT match var(--sfr-white)
   (only #fff / var(--pale) / #f1ecbe), which is why row 7 never flagged this. */
.dark .app-panel,
.dark .app-card {
  background: #141c28;
  border-color: rgba(255, 255, 255, 0.12);
  box-shadow: none;
}

/* Honey uppercase EYEBROW above a navy headline — the website's signature
   section-header pattern (honey eyebrow → big navy VV Ruby title). */
.app-eyebrow {
  font-family: var(--sfr-font-display);
  text-transform: uppercase;
  font-weight: 800;
  font-size: 0.75rem; /* 12px caps floor */
  letter-spacing: 0.16em;
  color: var(--sfr-honey);
  display: block;
}

/* ==========================================================================
   HEADINGS — generous VV Ruby scale. The condensed display face reads small,
   so headings run LARGER and tracking looser than a normal sans would.
   ========================================================================== */
.app-h1, .app-h2, .app-h3, .app-h4 {
  font-family: var(--sfr-font-display);
  text-transform: uppercase;
  color: var(--sfr-navy);
  letter-spacing: 0.05em;
  line-height: 1.1;
  font-weight: 700;
  margin: 0;
}
.app-h1 { font-size: clamp(1.9rem, 3.4vw, 2.7rem); }
/* Caps BELOW .app-title's 1.85rem. It used to cap at 1.95rem, so from ~750px up
   a section heading rendered LARGER than the page title it sat under — measured
   on vendors at 1280px: .app-title 29.6px, .app-h2 31.2px. An inverted scale
   reads as "no hierarchy", and the app that hit it was the one FOLLOWING the
   standard; the app that ignored these classes and hand-rolled its own type
   looked correct. That is the standard's bug, not the app's. */
.app-h2 { font-size: clamp(1.3rem, 2.2vw, 1.6rem); }
.app-h3 { font-size: 1.3rem; letter-spacing: 0.055em; }
.app-h4 { font-size: 1.05rem; letter-spacing: 0.07em; }

/* ==========================================================================
   BUTTONS + CONTROLS
   ========================================================================== */
.app-btn {
  font-family: var(--sfr-font-display);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  font-weight: 800;
  font-size: 13px;
  border-radius: var(--sfr-radius-pill); /* website uses full-radius PILLS */
  padding: 11px 22px;
  border: 2px solid transparent;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  line-height: 1;
  transition: filter 0.12s, background 0.12s, color 0.12s;
}
.app-btn--primary   { background: var(--sfr-honey); color: var(--sfr-navy); border-color: var(--sfr-honey); }
.app-btn--primary:hover { filter: brightness(0.95); }
/* Secondary = OUTLINE pill: honey border, navy text (matches the site). */
.app-btn--secondary { background: var(--sfr-white); color: var(--sfr-navy); border-color: var(--sfr-honey); }
.app-btn--secondary:hover { background: var(--sfr-pale); }
.app-btn--ghost     { background: transparent; color: var(--sfr-navy); border-color: var(--sfr-line); }
.app-btn--ghost:hover { border-color: var(--sfr-navy); }

.app-pill {
  font-family: var(--sfr-font-display);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  font-weight: 700;
  font-size: 12px; /* 12px caps floor */
  padding: 5px 12px;
  border-radius: var(--sfr-radius-pill);
  border: 1px solid var(--sfr-navy);
  color: var(--sfr-navy);
  background: transparent;
  cursor: pointer;
}
.app-pill.is-active { background: var(--sfr-honey); border-color: var(--sfr-honey); color: var(--sfr-navy); }

.app-header a:focus-visible,
.app-btn:focus-visible,
.app-pill:focus-visible { outline: 3px solid var(--sfr-honey); outline-offset: 2px; }

/* ==========================================================================
   SEGMENTED TOGGLE — selected-state DOT standard.
   RULE: DESIGN-RUBRIC row 37 (Blake, 2026-07-10). The rubric is the single
   statement of the rule and the gate grades it; this comment is not a second
   copy. <SegmentedToggle> (segmented-toggle.jsx) renders these classes;
   hand-roll the same markup in apps that can't use the shared component.
   ========================================================================== */
.app-toggle {
  display: inline-flex;
  align-items: center;
  border: 1px solid var(--sfr-navy-line);
  border-radius: var(--sfr-radius);
  overflow: hidden;
}
.app-toggle__option {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-family: var(--sfr-font-body);
  font-weight: 500;
  font-size: 0.875rem; /* 14px */
  color: var(--sfr-navy);
  background: var(--sfr-white);
  border: 0;
  padding: 6px 12px;
  cursor: pointer;
  transition: background 0.12s, color 0.12s;
}
.app-toggle__option:hover { background: var(--sfr-pale); }
.app-toggle__option[aria-pressed="true"] { background: var(--sfr-navy); color: var(--sfr-white); }
.app-toggle__option[aria-pressed="true"]:hover { background: var(--sfr-navy); }
.app-toggle__option:focus-visible { outline: 3px solid var(--sfr-honey); outline-offset: -2px; }
/* The dot — ALWAYS rendered (transparent when unselected) so swapping the
   active option never reflows the label text. Never omit it on the
   unselected buttons; that's what causes the horizontal shift this rule
   exists to prevent. */
.app-toggle__dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: transparent;
  flex: none;
}
.app-toggle__option[aria-pressed="true"] .app-toggle__dot { background: var(--sfr-white); }

/* ==========================================================================
   CALLOUT + FLOW — the two components the /features page is built from.
   RULES: DESIGN-RUBRIC row 42. The rubric is the single statement of the rule;
   this comment is the markup contract only.

   .app-callout — ONE outstanding thing to do. Not a status badge and not a
   banner: a callout always names an ACTION and links to where it is done. It
   carries a count, an instruction, and why it matters. Severity uses the status
   tokens, never honey — honey is the brand accent and must not come to mean
   "urgent".
     <a class="app-callout app-callout--bad" href="/approvals">
       <span class="app-callout__count">3</span>
       <span class="app-callout__body">
         <span class="app-callout__do">Decide on 3 requests</span>
         <span class="app-callout__why">Nothing can be ordered until you do.</span>
       </span>
       <span class="app-chip">Open</span>
     </a>

   .app-flow — an EXPLANATORY sequence: how work moves through the app, who does
   each step, and where. Numbered, because order is real here. (Numbering is
   otherwise discouraged: a list of capabilities is not a sequence and must not
   look like one.)

   WHY THIS IS NOT .app-acc--stepper (row 35): a stepper is something the viewer
   COMPLETES — locked later steps, mark-complete, resume state. A flow describes
   a process that usually spans several PEOPLE and several days; the reader is
   not working through it. Dressing it as a stepper promises interaction the page
   does not have. Use the stepper when one person completes the steps; use the
   flow when you are explaining how work travels.
     <section class="app-flow">
       <h3 class="app-h3">Getting something bought</h3>
       <p class="app-meta">When this applies…</p>
       <ol class="app-flow__steps">
         <li><span class="app-flow__n" aria-hidden="true">1</span>
             <span><a href="/requests/new">Someone raises a request</a>
               <span class="app-flow__who">Anyone · Start a new request</span></span></li>
         <li class="app-flow__step--elsewhere">
             <span class="app-flow__n" aria-hidden="true">5</span>
             <span>The bill is approved or pushed to a later date
               <span class="app-flow__who">Office · in QuickBooks</span></span></li>
       </ol>

   aria-hidden ON THE NUMBER is required, not optional: the <ol> already carries
   the order, so a visible number span is read out TWICE by a screen reader
   ("list item 1… one"). It is decoration reinforcing a semantic that already
   exists.

   .app-flow__step--elsewhere marks a step that does NOT happen in this app. Real
   processes cross systems — a purchase is approved here and PAID in QuickBooks —
   and a flow that silently drops those steps teaches the process wrong, then
   sends people hunting for a button that was never here. Show the step, mark
   where it happens, and do not link it.
     </section>
   ========================================================================== */
.app-callout {
  display: flex;
  align-items: center;
  gap: 14px;
  background: var(--sfr-white);
  border: 1px solid var(--sfr-line);
  border-left-width: 4px;
  border-radius: var(--sfr-radius);
  padding: 14px 18px;
  margin-bottom: 10px;
  text-decoration: none;
  color: inherit;
}
/* Set the three NON-left borders individually. The `border-color` shorthand here
   is specificity 0,2,0 and was silently overriding .app-callout--bad/--warn's
   border-left-color (0,1,0), so in dark mode every callout drew the same neutral
   stripe and severity was invisible — the one thing the component exists to
   convey. Caught rendering the sfr-vendors features page, 2026-07-21. */
.dark .app-callout {
  background: rgba(255,255,255,0.04);
  border-top-color: rgba(255,255,255,0.13);
  border-right-color: rgba(255,255,255,0.13);
  border-bottom-color: rgba(255,255,255,0.13);
}
.app-callout--bad  { border-left-color: var(--sfr-bad); }
.app-callout--warn { border-left-color: var(--sfr-warn); }
.app-callout--good { border-left-color: var(--sfr-good); }
.app-callout__count {
  font-family: var(--sfr-font-display);
  font-size: 30px;
  font-weight: 800;
  line-height: 1;
  font-variant-numeric: tabular-nums;
  min-width: 1.6ch;
  text-align: right;
}
.app-callout--bad  .app-callout__count { color: var(--sfr-bad); }
.app-callout--warn .app-callout__count { color: var(--sfr-warn); }
.app-callout--good .app-callout__count { color: var(--sfr-good); }
.app-callout__body { flex: 1; min-width: 0; }
.app-callout__do {
  display: block;
  font-family: var(--sfr-font-display);
  font-weight: 800;
  font-size: 15px;
  letter-spacing: 0.01em;
}
.app-callout__why { display: block; font-size: 13px; opacity: 0.8; margin-top: 2px; }

.app-flow {
  background: var(--sfr-white);
  border: 1px solid var(--sfr-line);
  border-radius: var(--sfr-radius);
  padding: 18px 20px;
  margin-bottom: 12px;
}
.dark .app-flow { background: rgba(255,255,255,0.04); border-color: rgba(255,255,255,0.13); }
.app-flow__steps { list-style: none; margin: 12px 0 0; padding: 0; }
.app-flow__steps > li {
  display: grid;
  grid-template-columns: 26px 1fr;
  gap: 12px;
  position: relative;
  padding-bottom: 14px;
}
.app-flow__steps > li:last-child { padding-bottom: 0; }
/* the connector makes the sequence readable without arrows */
.app-flow__steps > li:not(:last-child)::before {
  content: "";
  position: absolute;
  left: 12px;
  top: 24px;
  bottom: 0;
  width: 1px;
  background: var(--sfr-line);
}
.app-flow__n {
  width: 24px;
  height: 24px;
  border-radius: 50%;
  background: rgba(30,42,59,0.06);
  font-family: var(--sfr-font-display);
  font-size: 12px;
  font-weight: 800;
  display: grid;
  place-items: center;
  position: relative;
  z-index: 1;
}
.dark .app-flow__n { background: rgba(255,255,255,0.09); }
.app-flow__who { display: block; font-size: 12.5px; opacity: 0.65; margin-top: 1px; }
/* A step that happens in another system. Muted and unlinked, but never omitted —
   dropping it teaches the process wrong. */
.app-flow__step--elsewhere { opacity: 0.72; }
.app-flow__step--elsewhere .app-flow__n {
  background: transparent;
  border: 1px dashed var(--sfr-line);
}

/* ==========================================================================
   ACCORDION + WORKFLOW STEPPER — the collapse standard.
   RULES: DESIGN-RUBRIC rows 34 (accordion), 35 (workflow stepper) and 36
   (carousel ban) — Blake, 2026-07-10. The rubric is the single statement of
   each rule and the gate grades them; this comment is not a second copy.
     .app-acc                      reference accordion
     .app-acc.app-acc--stepper     workflow stepper
   Behavior auto-wires from sfr-chrome.js (also window.SFRAcc.init() after any
   dynamic render). Markup contract — see README "Accordion / Workflow Stepper":
     <div class="app-acc[ app-acc--stepper]" [data-app-workflow="unique-id"]>
       <div class="app-acc__done">…all-steps-complete banner (stepper only)…</div>
       <div class="app-acc__progress"><span class="app-acc__bar"><span></span></span>
            <span class="app-acc__count"></span></div>          (stepper only)
       <div class="app-acc__item">
         <button class="app-acc__head" type="button">
           <span class="app-acc__marker">1</span>
           <span class="app-acc__title">Step title</span>
           <span class="app-acc__status">Step 1</span>
           <span class="app-acc__chevron" aria-hidden="true"></span>
         </button>
         <div class="app-acc__panelwrap"><div class="app-acc__panel">
           <div class="app-acc__body"> …content…
             <div class="app-acc__cta">
               <button class="app-btn app-btn--primary" type="button" data-app-acc-complete>Mark step complete</button>
             </div>
           </div>
         </div></div>
       </div>
     </div>
   ========================================================================== */
.app-acc { display: flex; flex-direction: column; gap: 10px; }
.app-acc__item {
  border: 1px solid var(--sfr-navy-line);
  border-radius: var(--sfr-radius);
  background: var(--sfr-white);
  overflow: hidden;
}
.app-acc__item.is-open { border-color: var(--sfr-honey); }
.app-acc__head {
  display: flex; align-items: center; gap: 12px; width: 100%;
  text-align: left; cursor: pointer; background: none; border: 0;
  padding: 14px 16px; font-family: var(--sfr-font-body); color: var(--sfr-navy);
}
.app-acc__head:hover { background: var(--sfr-pale); }
.app-acc__head:disabled { cursor: not-allowed; opacity: 0.55; }
.app-acc__head:focus-visible { outline: 3px solid var(--sfr-honey); outline-offset: -2px; }
.app-acc__marker {
  flex: none; width: 24px; height: 24px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  font-family: var(--sfr-font-display); font-weight: 700; font-size: 0.75rem;
  background: var(--sfr-gray-ground); color: var(--sfr-navy-600);
  border: 1.5px solid var(--sfr-navy-line);
}
.app-acc__item.is-open .app-acc__marker,
.app-acc__item.is-done .app-acc__marker {
  background: var(--sfr-honey); color: var(--sfr-navy); border-color: var(--sfr-honey);
}
.app-acc__title {
  flex: 1; min-width: 0;
  font-family: var(--sfr-font-display); text-transform: uppercase; letter-spacing: 0.02em;
  font-size: 0.9375rem; font-weight: 600; line-height: 1.3; color: var(--sfr-navy);
}
.app-acc__status {
  flex: none; white-space: nowrap;
  font-family: var(--sfr-font-display); font-size: 0.6875rem; font-weight: 700;
  letter-spacing: 0.06em; text-transform: uppercase; color: var(--sfr-navy-600);
}
.app-acc__chevron {
  flex: none; width: 16px; height: 16px; transition: transform 0.22s ease;
  color: var(--sfr-navy-600);
  background: no-repeat center / 12px url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%2334465f' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
}
.app-acc__item.is-open .app-acc__chevron { transform: rotate(180deg); }
.app-acc__panelwrap { display: grid; grid-template-rows: 0fr; transition: grid-template-rows 0.24s ease; }
.app-acc__item.is-open .app-acc__panelwrap { grid-template-rows: 1fr; }
.app-acc__panel { overflow: hidden; }
.app-acc__body {
  padding: 2px 16px 16px 52px;
  font-family: var(--sfr-font-body); font-size: 0.84375rem; color: var(--sfr-navy); line-height: 1.6;
}
.app-acc__cta { margin-top: 14px; display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
.app-acc__hint { font-family: var(--sfr-font-body); font-size: 0.75rem; color: var(--sfr-navy-600); }
/* stepper progress + completion banner */
.app-acc__progress { display: flex; align-items: center; gap: 12px; margin: 0 0 14px; }
.app-acc__bar { flex: 1; height: 7px; border-radius: var(--sfr-radius-pill); background: var(--sfr-gray-ground); overflow: hidden; }
.app-acc__bar > span { display: block; height: 100%; width: 0; background: var(--sfr-honey); transition: width 0.3s ease; }
.app-acc__count { flex: none; white-space: nowrap; font-family: var(--sfr-font-display); font-size: 0.6875rem; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; color: var(--sfr-navy-600); }
.app-acc__done { display: none; align-items: center; gap: 10px; background: var(--sfr-pale); border: 1px solid var(--sfr-honey); border-radius: var(--sfr-radius); padding: 12px 16px; margin-bottom: 14px; font-family: var(--sfr-font-body); font-size: 0.84375rem; color: var(--sfr-navy); }
.app-acc.is-complete .app-acc__done { display: flex; }
/* dark-mode coverage (chrome content sits on the #0e141d ground) */
.dark .app-acc__item { background: #141c28; border-color: rgba(255, 255, 255, 0.10); }
.dark .app-acc__item.is-open { border-color: var(--sfr-honey); }
.dark .app-acc__head { color: #e8eaed; }
.dark .app-acc__head:hover { background: rgba(255, 255, 255, 0.05); }
.dark .app-acc__marker { background: #0e141d; color: #c3ccd6; border-color: rgba(255, 255, 255, 0.18); }
.dark .app-acc__item.is-open .app-acc__marker,
.dark .app-acc__item.is-done .app-acc__marker { background: var(--sfr-honey); color: var(--sfr-navy); border-color: var(--sfr-honey); }
.dark .app-acc__title { color: #e8eaed; }
.dark .app-acc__status { color: #c3ccd6; }
.dark .app-acc__chevron { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23c3ccd6' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E"); }
.dark .app-acc__body { color: #c3ccd6; }
.dark .app-acc__hint { color: #c3ccd6; }
.dark .app-acc__bar { background: rgba(255, 255, 255, 0.08); }
.dark .app-acc__count { color: #c3ccd6; }
.dark .app-acc__done { background: rgba(255, 255, 255, 0.05); border-color: var(--sfr-honey); color: #e8eaed; }
@media (prefers-reduced-motion: reduce) {
  .app-acc__panelwrap, .app-acc__chevron, .app-acc__bar > span { transition: none; }
}

/* ==========================================================================
   FOOTER — navy, centered wordmark + line (brand guide)
   ========================================================================== */
.app-footer {
  background: var(--sfr-navy);
  color: rgba(255, 255, 255, 0.8);
  text-align: center;
  padding: 40px 24px;
}
.app-footer__mark { height: 26px; width: auto; margin: 0 auto 14px; display: block; }
.app-footer__meta {
  font-size: 0.75rem; /* 12px caps floor */
  font-weight: 700;
  letter-spacing: 0.08em;
  color: rgba(255, 255, 255, 0.55);
}

@media (prefers-reduced-motion: reduce) {
  .app-nav a, .app-btn { transition: none; }
}

/* ==========================================================================
   BRAND GRAPHICS — use the GraphicKit art the way the website does: in
   NON-DATA zones only (loading, empty states, auth, whisper-light watermark).
   Assets live in this repo's assets/ (sfr-mascot.svg, sfr-anvil.svg,
   sfr-pattern.svg, sfr-badge.svg) — copy into each app's /public.
   ========================================================================== */

/* Loading — the running-mascot loader (Blake's /sfr-loading.gif) on a theme-
   stable CREAM "stage" so the navy line-art mascot stays legible in BOTH light
   and dark mode. The GIF takes over automatically once dropped in /public; the
   static mascot SVG is the fallback. Used for EVERY loading scenario. */
.app-loading { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 18px; padding: 64px 0; }
/* Route-level loader — fixed, full-viewport, centered on the SCREEN regardless of
   top/side layout (Blake, 2026-07-03: the mascot must be screen-centered, not
   shifted right by the sidebar). Inline loaders keep the base .app-loading. */
.app-loading--full { position: fixed; inset: 0; z-index: 30; min-height: 0; padding: 0; }
.app-loading__stage, .app-empty__stage, .app-error__stage {
  display: inline-flex; align-items: center; justify-content: center;
  background: transparent; border: 0; box-shadow: none; padding: 0;
}
.app-loading__stage img { height: 132px; width: auto; display: block; }
.app-loading .app-loading__label { font-family: var(--sfr-font-display); text-transform: uppercase; font-weight: 800; font-size: 0.75rem; letter-spacing: 0.16em; color: var(--sfr-honey); }
@keyframes sfr-run { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-7px); } }
.sfr-run { animation: sfr-run 0.9s ease-in-out infinite; }

/* Empty state — a kit graphic on the same cream stage + message, not dead space. */
.app-empty { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 16px; padding: 56px 24px; text-align: center; }
.app-empty__stage img { height: 128px; width: auto; display: block; }
.app-empty__title { font-family: var(--sfr-font-display); text-transform: uppercase; font-weight: 800; letter-spacing: 0.04em; color: var(--sfr-navy); font-size: 1.15rem; }
.app-empty__body { color: var(--sfr-navy); opacity: 0.72; max-width: 42ch; }

/* Error / 404 — the same cream mascot stage, a calm headline, and a honey action
   button. NEVER a raw stack trace (UI-SPEC States / Copywriting Contract). */
.app-error { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 16px; padding: 64px 24px; text-align: center; min-height: 62vh; }
.app-error__stage img { height: 128px; width: auto; display: block; }
.app-error__title { font-family: var(--sfr-font-display); text-transform: uppercase; font-weight: 800; letter-spacing: 0.03em; color: var(--sfr-navy); font-size: 1.4rem; }
.app-error__body { color: var(--sfr-navy); opacity: 0.72; max-width: 46ch; }
.app-error__actions { display: flex; gap: 10px; flex-wrap: wrap; justify-content: center; margin-top: 4px; }
/* The Next.js error `digest` — a SUPPORT HANDLE, not a stack trace. Safe to show:
   it lets someone quote a reference without the user ever seeing an exception.
   Row 11 bans the stack trace, not the means to report it. */
.app-error__digest { font-size: 0.6875rem; color: var(--sfr-navy); opacity: 0.5; font-variant-numeric: tabular-nums; }

/* mascot stage transparent in both themes */
.dark .app-empty__title, .dark .app-error__title { color: #eef1f5; }
.dark .app-empty__body, .dark .app-error__body { color: #c3ccd6; }
.dark .app-error__digest { color: #c3ccd6; opacity: 0.55; }
/* Chrome content components sit on the dark `main` ground (#0e141d) in dark mode,
   so their default navy ink is illegible — flip them light. Honey/active pills
   keep navy text (navy-on-honey stays correct). Surfaced by the dark-mode
   legibility conformance check, 2026-07-07. */
.dark .app-h1, .dark .app-h2, .dark .app-h3, .dark .app-h4 { color: #e8eaed; }
.dark .app-btn--secondary { background: #141c28; color: #e8eaed; }
.dark .app-btn--secondary:hover { background: #1a2434; }
.dark .app-btn--ghost { color: #e8eaed; border-color: rgba(255, 255, 255, 0.2); }
.dark .app-btn--ghost:hover { border-color: rgba(255, 255, 255, 0.42); }
.dark .app-pill { color: #e8eaed; border-color: rgba(255, 255, 255, 0.28); }
.dark .app-pill.is-active { color: var(--sfr-navy); border-color: var(--sfr-honey); }
.dark .app-toggle { border-color: rgba(255, 255, 255, 0.1); }
.dark .app-toggle__option { background: #141c28; color: #e8eaed; }
.dark .app-toggle__option:hover { background: rgba(255, 255, 255, 0.05); }
.dark .app-toggle__option[aria-pressed="true"] { background: var(--sfr-navy); color: #e8eaed; }
.dark .app-toggle__option[aria-pressed="true"]:hover { background: var(--sfr-navy); }
.dark .app-toggle__option[aria-pressed="true"] .app-toggle__dot { background: #e8eaed; }

/* Watermark — whisper-light brand pattern/mascot behind content (never over data). */
.app-watermark { position: relative; }
.app-watermark::after {
  content: ""; position: absolute; right: 18px; bottom: 14px; width: 180px; height: 180px;
  background: no-repeat center / contain; opacity: 0.05; pointer-events: none;
}
.app-watermark--mascot::after { background-image: url("assets/sfr-mascot.svg"); }
.app-watermark--pattern::after { width: 100%; height: 100%; right: 0; bottom: 0; opacity: 0.035; background-image: url("assets/sfr-pattern.svg"); background-size: 420px; }

@media (prefers-reduced-motion: reduce) { .sfr-run { animation: none; } }

}
/* ── END @layer sfr-chrome — the block below is UNLAYERED ON PURPOSE ───────── */
/* ==========================================================================
   DARK-MODE LEGIBILITY OVERRIDES — the robust way (use INSTEAD of a fragile
   per-element dark: sweep). Static hubs get this via importing sfr-app-core.css;
   Tailwind/Next apps copy this block into their own globals.css. Flips hardcoded
   content utilities in dark: navy/black text -> light, white panels -> dark,
   dark borders -> light. EXCEPTION: navy text on SOLID honey (pills) stays navy.
   ========================================================================== */
.dark .text-brand-navy { color: #e8eaed; }
.dark .text-brand-navy\/90 { color: rgba(232, 234, 237, 0.90); }
.dark .text-brand-navy\/80 { color: rgba(232, 234, 237, 0.80); }
.dark .text-brand-navy\/70 { color: rgba(232, 234, 237, 0.72); }
.dark .text-brand-navy\/60 { color: rgba(232, 234, 237, 0.64); }
.dark .text-brand-navy\/50 { color: rgba(232, 234, 237, 0.56); }
.dark .text-brand-navy\/40 { color: rgba(232, 234, 237, 0.50); }
.dark .text-brand-navy\/30 { color: rgba(232, 234, 237, 0.42); }
.dark .text-brand-navy\/20 { color: rgba(232, 234, 237, 0.34); }
.dark .text-brand-black { color: #e8eaed; }
.dark .text-brand-black\/70 { color: rgba(232, 234, 237, 0.72); }
.dark .text-brand-black\/60 { color: rgba(232, 234, 237, 0.64); }
.dark .text-brand-black\/50 { color: rgba(232, 234, 237, 0.56); }
.dark .bg-white, .dark .bg-brand-white { background-color: #141c28; }
.dark .border-brand-navy\/5, .dark .border-brand-navy\/10, .dark .border-brand-navy\/15, .dark .border-brand-navy\/20,
.dark .border-brand-black\/5, .dark .border-brand-black\/10, .dark .border-brand-black\/15, .dark .border-brand-black\/20 { border-color: rgba(255, 255, 255, 0.12); }
.dark .bg-brand-honey.text-brand-navy { color: var(--color-brand-navy); }

/* Pale-yellow used as a CONTENT highlight fill -> dark tint in dark mode; the
   page <main> ground stays the dark ground (forced, beats this override). Add
   this alongside the dark-legibility overrides above. */
.dark .bg-brand-pale-yellow,
.dark .bg-brand-pale-yellow\/40,
.dark .bg-brand-pale-yellow\/30,
.dark .bg-brand-pale-yellow\/25,
.dark .bg-brand-pale-yellow\/20 { background-color: rgba(255, 255, 255, 0.05); }
.dark main { background-color: #0e141d !important; }

/* ── resume @layer sfr-chrome ─────────────────────────────────────────────── */
@layer sfr-chrome {
/* ==========================================================================
   UNIFIED CHROME v1 (2026-07-03) — the flex-bar header the shared <AppChrome>
   component renders, its honey uppercase TEXT-link nav, the right-side tools
   cluster (layout + theme toggles + user), and the top↔side layout morph. This
   is THE chrome every app consumes via @sfr/app-chrome. (The
   .app-header__inner/__brand/__right block far above is the LEGACY static-hub
   structure — migrate hubs onto this.) Fonts resolve through --sfr-font-display
   / --sfr-font-body (set them from your next/font vars for hashed faces; else
   they fall back to VV Ruby / Figtree from :root).
   ========================================================================== */
.app-header {
  display: flex; align-items: center; gap: 18px; flex-wrap: wrap;
  min-height: 56px; padding: 0 24px;
  /* STICKY on desktop in the TOP layout (M7) — the navy bar stays pinned as the page
     scrolls, so the wordmark + nav + tools are always reachable. `sticky` (not `relative`)
     still forms the containing block the absolutely-centered nav below needs, and the navy
     background + z-index:50 come from the .app-header rule above. The SIDE layout overrides
     this to `position: fixed` (the 244px rail, ≥768px), and MOBILE overrides it back to
     `position: relative` (≤767px, so the nav drawer still anchors under the bar) — so this
     only pins the desktop top bar. */
  position: sticky; top: 0; justify-content: space-between;
}
/* min-height 44 = row 20. The brand is a LINK (it goes home) and rendered
   183x26 — a real destination under the touch floor on every page. */
.app-brand { display: flex; align-items: center; gap: 14px; text-decoration: none; color: inherit; flex-shrink: 0; min-height: 44px; }
/* .app-wordmark (30px) + .app-label (white, divider) are defined above. */
.app-nav { display: flex; align-items: center; gap: 2px; flex-wrap: wrap; }
/* CENTER THE MENU (Blake, 2026-07-17: "menu should be in the center, not off to
   the left"). On desktop the nav is absolutely centered in the full-bleed header,
   so it reads as centered on screen regardless of the brand/tools widths. Below
   768px it becomes the hamburger drawer (the max-width rule wins) — untouched. */
@media (min-width: 768px) {
  .app-nav { position: absolute; left: 50%; transform: translateX(-50%); margin: 0; }
}
.app-nav__link {
  font-family: var(--sfr-font-display); text-transform: uppercase; letter-spacing: 0.06em;
  font-weight: 700; font-size: 0.82rem; color: rgba(255, 255, 255, 0.82);
  background: transparent; border: 0; cursor: pointer; padding: 8px 12px;
  border-radius: 8px; text-decoration: none; line-height: 1;
  min-height: 44px;   /* row 20 — rendered 49x32; the top nav is desktop-only, so
                         the 390px probe never saw it */
  display: inline-flex; align-items: center; gap: 4px; transition: color 0.15s, background 0.15s;
}
.app-nav__link:hover { color: var(--sfr-honey); background: rgba(255, 255, 255, 0.06); }
.app-nav__link.active { color: var(--sfr-honey); background: transparent; }
/* margin-left:auto is LOAD-BEARING, not cosmetic. The header is
   justify-content:space-between and the nav is absolutely centered (out of flow),
   so the in-flow children are brand + aux + tools. With three of them
   space-between parks .app-aux in the MIDDLE of the bar — directly underneath the
   centered nav. Measured on hr-hub 2026-07-21: nav spanned x 423-856 and the aux
   link sat at x 577, overlapping. Pushing aux right collapses it back onto the
   tools cluster and restores the two-cluster layout the comment above assumes.
   Apps with a narrow nav never saw this; a wide nav exposes it. */
.app-aux { display: flex; align-items: center; gap: 12px; margin-left: auto; }
.app-tools { display: flex; align-items: center; gap: 6px; flex-shrink: 0; }
/* Mobile hamburger — hidden on desktop; the @media (max-width:767px) block below
   reveals it and turns .app-nav into a drawer. First child of .app-tools. */
/* ROW 20 — every chrome control is a 44x44 tap target (2026-07-16).
   These were 36px (hamburger) and 32px (theme/layout/collapse). WCAG 2.2 AA's
   floor is 24px, so they "passed" — and SFR holds 44 because field crews use
   gloves in sunlight. Measured live on backoffice: 25 of 25 controls under 44,
   smallest 30px.

   44 at EVERY width, not just under 768px. Row 20 is specified at the 390px
   render, but viewport width is a poor proxy for a thumb: a tablet at 800px is a
   touch device and would have kept 32px targets. The icon inside stays its
   original size — only the hit area grows, so nothing looks chunkier. */
.app-hamburger {
  display: none; align-items: center; justify-content: center;
  width: 44px; height: 44px; border: 0; background: transparent;
  color: var(--sfr-white); border-radius: 8px; cursor: pointer; flex-shrink: 0;
}
.app-hamburger:hover { background: rgba(255, 255, 255, 0.08); color: var(--sfr-honey); }
.themebtn, .layoutbtn {
  width: 44px; height: 44px; display: inline-flex; align-items: center; justify-content: center;
  border-radius: 7px; background: transparent; border: 0; color: rgba(255, 255, 255, 0.8); cursor: pointer;
}
.themebtn:hover, .layoutbtn:hover { background: rgba(255, 255, 255, 0.1); color: var(--sfr-honey); }
.layoutbtn { display: none; }             /* desktop-only */
@media (min-width: 768px) { .layoutbtn { display: inline-flex; } }
/* Theme toggle is MOBILE-ONLY (M10): internal apps are dark-only on desktop, so the toggle
   is hidden on desktop and shown on mobile. ThemeToggle also enforces dark on desktop at runtime. */
@media (min-width: 768px) { .themebtn { display: none; } }

/* Top ↔ side morph (desktop): the whole header becomes a fixed left sidebar and
   the content shifts right. Put the app's content root under .app-body (or apply
   the padding on your own wrapper). Mobile always keeps the top bar. */
@media (min-width: 768px) {
  .layout-side .app-header {
    position: fixed; left: 0; top: 0; height: 100vh; width: 244px;
    flex-direction: column; align-items: stretch; flex-wrap: nowrap;
    justify-content: flex-start; gap: 18px; padding: 20px 16px; overflow-y: auto; z-index: 60;
  }
  .layout-side .app-brand { flex-direction: column; align-items: center; text-align: center; gap: 10px; }
  .layout-side .app-label { border-left: 0; padding-left: 0; }
  .layout-side .app-nav { flex-direction: column; align-items: stretch; width: 100%; margin-right: 0; gap: 4px; }
  .layout-side .app-nav__link { width: 100%; justify-content: flex-start; }
  .layout-side .app-aux { flex-direction: column; align-items: stretch; width: 100%; gap: 8px; }
  .layout-side .app-tools { margin-top: auto; width: 100%; }
  .layout-side .app-body { padding-left: 244px; }
}

/* ==========================================================================
   .app-page-header — the canonical page masthead. 2026-07-17.
   ==========================================================================
   Every page opens with a real header, not a thin subtitle line. Modelled on
   opord.fixitforever.com's masthead (Blake's reference, 2026-07-17: "opord does
   a WAY better job on fonts, titles, headers"). backoffice pages showed a single
   13px <p class="subtitle"> — no title presence at all.

   Contract:
     <div class="app-page-header">
       <p class="app-eyebrow">Smart Foundation Repair · Backoffice</p>
       <h1 class="app-title">Tech Stack<span class="app-title__accent"> line two</span></h1>
       <p class="app-lede">One-line what-and-why, short measure.</p>
       <div class="app-meta"><span><b>Label</b> value</span></div>   (optional)
     </div>

   COMPACT BY DEFAULT (Blake, 2026-07-17: "a ton of space taken up — we don't want
   to scroll so far down every time"). The first cut was opord's HERO masthead
   (40px pad + 43px title + lede + eyebrow) and pushed content to 295px — 37% of
   the screen gone before any data. backoffice pages are UTILITY: you are there to
   find a tool or a person, so the header is tight and the content is near the top.
   The eyebrow is OMITTED on these pages — "Smart Foundation Repair · Backoffice"
   just repeats what the chrome header's wordmark + label already say; a page
   eyebrow should add context (opord's "· The 10-Year OPORD"), never duplicate the
   header. For a genuine landing/hero page, add .app-page-header--hero for the
   bigger opord treatment.

   Values: title VV Ruby 700 uppercase, clamps ~27->34px (still display-sized,
   passes row 40's >=26px), navy on pale / near-white on dark; lede Figtree ~0.98rem
   muted. */
.app-page-header { padding: 10px 0 2px; }
.app-eyebrow { font-family: var(--sfr-font-display); text-transform: uppercase;
  font-size: 0.78rem; font-weight: 800; color: var(--sfr-navy-600);
  letter-spacing: 0.16em; line-height: 1.4; margin: 0 0 5px; }
.dark .app-eyebrow { color: var(--sfr-honey); }
.app-title { font-family: var(--sfr-font-display); text-transform: uppercase;
  font-weight: 700; font-size: clamp(1.65rem, 3vw, 1.85rem); line-height: 1.08;
  letter-spacing: 0.03em; color: var(--sfr-navy); margin: 0; }
.dark .app-title { color: #e8eaed; }
.app-title__accent { color: var(--sfr-honey); }
.app-lede { font-family: var(--sfr-font-body); font-size: 0.9rem; line-height: 1.45;
  color: var(--sfr-navy-600); max-width: 62ch; margin: 4px 0 0; }
.dark .app-lede { color: #c3ccd6; }
.app-meta { display: flex; flex-wrap: wrap; gap: 22px; margin: 12px 0 0;
  font-size: 0.84rem; color: var(--sfr-navy-600); }
.app-meta b { color: var(--sfr-navy); font-weight: 700; }
.dark .app-meta { color: #c3ccd6; } .dark .app-meta b { color: #e8eaed; }
/* The hero treatment — opord-sized. For landing pages only, never utility pages. */
.app-page-header--hero { padding: 40px 0 6px; }
.app-page-header--hero .app-eyebrow { font-size: 0.82rem; margin: 0 0 12px; }
.app-page-header--hero .app-title { font-size: clamp(1.9rem, 4.6vw, 2.7rem); line-height: 1.08; }
.app-page-header--hero .app-lede { font-size: 1.05rem; line-height: 1.6; margin: 16px 0 0; }
.app-page-header.has-rule { border-bottom: 2px solid var(--sfr-honey); padding-bottom: 20px; margin-bottom: 8px; }

/* ==========================================================================
   DOCUMENT SECTIONS — rhythm + labels for long-form pages (the features page,
   rubric row 42; any guide or explainer).

   WHY THIS EXISTS. A page like this needs a QUIET section label, not a display
   heading: .app-h2 is a section *heading* and shouts next to a lede. The package
   never shipped a label idiom, so the two apps that built a features page each
   solved it differently — one used .app-h2 (too loud, and it out-sized the page
   title), the other hand-rolled Tailwind and ignored the chrome type classes
   entirely. Neither is the standard's fault to leave unowned.

   WHY THE SPACING LIVES HERE AND NOT IN THE APP. `.app-h1,.app-h2,.app-h3,
   .app-h4 { margin: 0 }` above is UNLAYERED, and unlayered CSS beats @layer
   utilities — so a Tailwind margin utility on a heading silently does nothing.
   Proven in a browser: `mt-10` computes to 40px on a plain div and 0px on an
   .app-h2. An app therefore CANNOT space its own headings with utilities, which
   is why the rhythm has to be owned here. (The layering itself is a wider issue
   — see the repo issue; changing it moves the cascade for every app.)
   ========================================================================== */
.app-section { margin-top: 44px; }
.app-section:first-of-type { margin-top: 28px; }
.app-section__label {
  font-family: var(--sfr-font-display); text-transform: uppercase;
  font-size: 0.82rem;          /* 13.1px — above the 12px caps floor, row 16 */
  font-weight: 700; letter-spacing: 0.1em; line-height: 1.4;
  color: var(--sfr-navy); margin: 0;
}
.dark .app-section__label { color: var(--sfr-white); }
/* One quiet line under the label saying what the section is for. */
.app-section__note { max-width: 66ch; margin: 4px 0 0; font-size: 0.85rem; line-height: 1.55; color: var(--sfr-navy-600); }
.dark .app-section__note { color: rgba(255, 255, 255, 0.62); }
/* Body of a section — separated from its label without per-child margins. */
.app-section__body { margin-top: 14px; display: flex; flex-direction: column; gap: 14px; }

/* ==========================================================================
   .app-table — the canonical data table (row 14 / 30 / 31 / 32). 2026-07-17.
   ==========================================================================
   Tables, not card grids, for any list of like records. This is the pattern
   sfr-backoffice's hub proved (a 17-launcher card grid became one table); it is
   promoted here so every app uses ONE table, not a per-page copy — the same
   duplication the whole standard exists to kill. Tech Stack shipped 23 tool
   records as a card grid because there was no shared table to reach for.

   Contract:
     <table class="app-table">
       <thead><tr><th>Col</th>…</tr></thead>
       <tbody>
         <tr class="app-table__group"><th colspan="N" scope="colgroup">Section</th></tr>
         <tr><th scope="row" class="app-table__key">
               <a class="app-table__link" href="…">icon + label</a></th>
             <td>…</td></tr>
       </tbody>
     </table>

   Row 30: sticky navy header. Row 31: the row link is one ≥44px tap target and
   secondary columns drop under 768px rather than squeezing. Row 32: exactly one
   action per row (the row link OR one button, never both). */
.app-table { width: 100%; border-collapse: collapse; margin: 0 0 24px;
  background: var(--sfr-white); border: 1px solid var(--sfr-line);
  border-radius: var(--sfr-radius); overflow: hidden; }
.dark .app-table { background: #141c28; border-color: rgba(255,255,255,0.12); }
.app-table thead th { position: sticky; top: 0; z-index: 1;
  background: var(--sfr-navy); color: var(--sfr-white);
  font-family: var(--sfr-font-display); text-transform: uppercase; letter-spacing: 0.04em;
  font-size: 0.75rem; text-align: left; padding: 9px 14px; font-weight: 700; } /* 12px = uppercase VV Ruby floor (row 16) */
.app-table tbody tr { border-top: 1px solid var(--sfr-line); }
.dark .app-table tbody tr { border-top-color: rgba(255,255,255,0.10); }
.app-table tbody tr:hover { background: rgba(234,179,63,0.10); }
.app-table__group th { font-family: var(--sfr-font-display); font-size: 0.75rem;
  font-weight: 700; color: var(--sfr-navy-600); text-transform: uppercase;
  letter-spacing: 0.1em; text-align: left; padding: 14px 14px 6px;
  background: transparent; border-bottom: 1px solid var(--sfr-honey); } /* 12px = uppercase VV Ruby floor (row 16) */
.dark .app-table__group th { color: rgba(255,255,255,0.66); }
.app-table__group:hover { background: transparent; }
.app-table__key { padding: 0; font-weight: 400; text-align: left; }
/* The link fills the cell so the whole row height is the tap area (row 31). */
.app-table__link { display: flex; align-items: center; gap: 10px; min-height: 44px;
  padding: 8px 14px; color: var(--sfr-ink); text-decoration: none;
  font-family: var(--sfr-font-display); font-weight: 700; font-size: 0.88rem;
  text-transform: uppercase; letter-spacing: 0.02em; white-space: nowrap; }
.dark .app-table__link { color: #e8eaed; }
.app-table__link:hover { color: var(--sfr-honey); }
.app-table__link .ico, .app-table__link svg { flex: none; color: var(--sfr-honey); width: 18px; height: 18px; }
.app-table td { font-size: 0.82rem; color: var(--sfr-navy-600); padding: 8px 14px; line-height: 1.5; }
.dark .app-table td { color: #a9b2bf; }
/* Row 31 — under 768px, drop the columns tagged .app-table__sub rather than
   squeezing; the row stays one ≥44px target with its key column. */
@media (max-width: 767px) {
  .app-table thead th.app-table__sub, .app-table td.app-table__sub { display: none; }
  .app-table__link { min-height: 48px; white-space: normal; }
}

/* Screen-reader-only (used by <LoadingMascot> for role=status). */
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}

/* Charts — let axis tick labels render into the card padding instead of being
   clipped by the SVG/container box (fixes cut-off week + y-axis labels, scorecard
   /trends 2026-07-03). Pair with adequate chart margins (bottom/left ≥ 4). */
.recharts-wrapper, .recharts-surface { overflow: visible; }

/* ==========================================================================
   TOOL ICON STATE SWAP — for STATIC sites (2026-07-16)
   ==========================================================================
   The React components pick their icon in JS (`isDark ? <Moon/> : <Sun/>`).
   A static page has no JS state to branch on, so it ships BOTH icons and lets
   the cascade choose, keyed off the same `.dark` / `.layout-side` classes on
   <html> that everything else already keys off. No script, no flash.

   This exists because sfr-backoffice shipped its theme/layout/collapse controls
   as the HTML entities ☰ ◫ ◑ — bare glyphs, against the rubric's canonical icon
   rule ("No ad-hoc glyphs or HTML entities") — on all 5 pages, in production.
   render-check has asserted `tool icons are not SVG` since the day it was
   written; it had simply never been pointed at the app. Static sites had no
   sanctioned way to do the swap, so they improvised. Now they have one.
   ========================================================================== */
.themebtn .ticon-moon,
.layoutbtn .ticon-paneltop { display: none; }
.dark .themebtn .ticon-sun { display: none; }
.dark .themebtn .ticon-moon { display: block; }
.layout-side .layoutbtn .ticon-panelleft { display: none; }
.layout-side .layoutbtn .ticon-paneltop { display: block; }

/* ==========================================================================
   COLLAPSE THE MENU (nav-collapsed) — reclaim screen space (Blake, 2026-07-03:
   a collapse option on every menu/page). Top bar → hide the nav links. Sidebar →
   slide the whole sidebar away + reclaim the width; a fixed reopen button (PORTAL
   it to <body> — the sidebar transform is a containing block for fixed children)
   brings it back. Desktop-only, like the layout selector.
   ========================================================================== */
.collapsebtn { width: 44px; height: 44px; display: none; align-items: center; justify-content: center; border-radius: 7px; background: transparent; border: 0; color: rgba(255, 255, 255, 0.8); cursor: pointer; }  /* 44 = row 20 */
.collapsebtn:hover { background: rgba(255, 255, 255, 0.1); color: var(--sfr-honey); }
@media (min-width: 768px) { .collapsebtn { display: inline-flex; } }
.app-header { transition: transform 0.2s ease; }
.menu-reopen{position:fixed;left:0;top:0;z-index:45;display:none;align-items:flex-start;justify-content:center;padding-top:14px;width:44px;height:100vh;background:var(--sfr-navy);color:#fff;border:0;cursor:pointer}
.menu-reopen:hover { background: #2a3a52; }
.nav-collapsed:not(.layout-side) .app-nav { display: none; }
@media (min-width: 768px) {
  .layout-side.nav-collapsed .app-header { transform: translateX(-102%); }
  .layout-side.nav-collapsed .app-body { padding-left: 44px; }
  .layout-side.nav-collapsed .menu-reopen { display: inline-flex; }
}

/* ==========================================================================
   MOBILE HAMBURGER DRAWER (2026-07-07) — below 768px the inline nav collapses
   behind a hamburger (first child of .app-tools). Tapping it toggles `nav-open`
   on the header, which drops the nav as a full-width drawer under the bar. This
   block sits LAST so it overrides the legacy @media (max-width:760px) overflow-x
   rule on .app-nav. Desktop (≥768px) is untouched — the hamburger stays
   display:none and the nav stays inline.
   ========================================================================== */
@media (max-width: 767px) {
  .app-header { position: relative; }
  .app-hamburger { display: inline-flex; }
  /* Nav becomes a hidden drawer anchored under the header bar. */
  .app-nav {
    display: none; position: absolute; top: 100%; left: 0; right: 0;
    /* flex-wrap:nowrap is LOAD-BEARING. The base .app-nav is `flex-wrap: wrap`,
       which on a COLUMN container with a max-height does not wrap onto new rows —
       it wraps into new COLUMNS, off to the right, where overflow-x:hidden then
       clips them. Measured on hr-hub at 375px: "People" and "Emma" laid out at
       x=365 and "My Tasks" at x=718, all invisible and unreachable with the
       drawer open. nowrap sends the overflow down instead, into the
       overflow-y:auto scroll this rule already sets up. */
    flex-direction: column; flex-wrap: nowrap; align-items: stretch; gap: 2px; margin: 0;
    background: var(--sfr-navy); padding: 10px 12px 14px;
    box-shadow: 0 10px 26px rgba(0, 0, 0, 0.32);
    border-top: 1px solid rgba(255, 255, 255, 0.12);
    max-height: 76vh; overflow-x: hidden; overflow-y: auto; z-index: 55;
  }
  .nav-open .app-nav, .app-header.nav-open .app-nav { display: flex; }
  /* Full-width tap targets. */
  .app-nav__link { width: 100%; justify-content: flex-start; padding: 12px; }
  /* Dropdown groups list inline in the drawer instead of floating.
     flex-direction:column is LOAD-BEARING. The base rule is
     `.app-nav__group { display: inline-flex }` — a ROW — so without this the
     trigger and its menu sit side by side, and each group is as wide as its
     widest description. Measured on hr-hub at 375px the first time anything ever
     rendered the drawer: groups were 351px wide, "People" and "Emma" laid out at
     x=365 and were clipped by this block's own overflow-x:hidden. Two of three
     groups were unreachable WITH the drawer open. This CSS shipped 2026-07-07 and
     was never exercised, because nothing rendered the trigger until #22. */
  .app-nav__group { width: 100%; flex-direction: column; align-items: stretch; }
  /* Same full-width tap target as .app-nav__link above — a group trigger is a
     destination-opener, not a label. */
  .app-nav__trigger { width: 100%; justify-content: flex-start; padding: 12px; }
  .app-nav__menu {
    position: static; display: block; box-shadow: none; background: transparent;
    border: 0; padding-left: 12px; min-width: 0; width: 100%;
  }
  .app-nav__menu a { width: 100%; padding: 10px 12px; }
}

/* ==========================================================================
   CHARTS — <SFRChart> / <SFRChartMini> / <SFRSpark> (@sfr/app-chrome/chart).
   RULE: DESIGN-RUBRIC row 29. Contract:
   docs/superpowers/specs/2026-07-16-sfrchart-design.md
   Codifies sfr-scorecard's chart pattern (Blake, 2026-07-16).

   These classes exist so the components don't depend on the consuming app having
   shadcn/Tailwind tokens (bg-popover, text-muted-foreground). scorecard styled its
   tooltips with those; a shared package cannot assume them.
   ========================================================================== */
.app-chart { display: block; }

.app-chart__empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 0 24px;
  text-align: center;
}
.app-chart__empty-head {
  font-family: var(--sfr-font-display);
  font-size: 20px;
  font-weight: 600;
  color: var(--sfr-navy);
  margin: 0;
}
.app-chart__empty-body {
  max-width: 28rem;
  font-size: 14px;
  color: var(--sfr-muted, #5b6675);
  margin: 0;
}

/* Mini tier, 0 points — a flat muted baseline. Never a collapsed card. */
.app-chart__flat {
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 4px;
  background: rgba(156, 163, 175, 0.3);
  font-size: 14px;
  color: var(--sfr-muted, #5b6675);
}

/* Spark tier, <2 points — a dash. aria-hidden in the component: no trend to announce. */
.app-chart__spark-empty {
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 14px;
  color: var(--sfr-muted, #5b6675);
}

.app-chart__note {
  margin: 4px 0 0;
  padding: 0 8px;
  font-size: 14px;
  color: var(--sfr-muted, #5b6675);
}

/* Tooltip — always custom, never Recharts' default. */
.app-chart__tip {
  border-radius: 6px;
  border: 1px solid rgba(30, 42, 59, 0.1);
  background: var(--sfr-white);
  padding: 8px 12px;
  font-family: var(--sfr-font-body);
  font-size: 14px;
  color: var(--sfr-navy);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
}
.app-chart__tip-label { font-weight: 600; }
.app-chart__tip-value { font-variant-numeric: tabular-nums; }
.app-chart__tip-meta { color: var(--sfr-muted, #5b6675); }

/* Inline key — icon+text, never colour-only (WCAG 1.4.1). Recharts' <Legend> is
   deliberately unused: it is colour-only by default. */
.app-chart__legend {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 16px;
  margin-top: 8px;
  padding: 0 8px;
  font-size: 14px;
  color: var(--sfr-muted, #5b6675);
}
.app-chart__key { display: flex; align-items: center; gap: 6px; }
.app-chart__key-dot {
  display: inline-block;
  width: 10px;
  height: 10px;
  border-radius: 50%;
}
.app-chart__key-goal {
  display: inline-block;
  width: 20px;
  height: 0;
  border-top: 2px dashed var(--sfr-navy);
}

/* ── Charts in dark mode ──
   Moved into this package from sfr-scorecard/app/globals.css (2026-07-16) so every
   app gets it by importing the CSS instead of hand-writing it. Rubric row 17 now
   means "did you import the package CSS", not "did you write these rules".

   SVG uses fill/stroke, NOT CSS `color`, so the HTML dark overrides can't reach
   chart ink. Recharts paints navy axis text + navy data lines + faint black axes,
   which all VANISH on the dark ground. Flip them.

   THE KEY INSIGHT (scorecard's): chrome ink flips, inline STATUS fills SURVIVE.
   Status dots keep their own inline fills, so a dot means the same thing in both
   themes. Leave dots alone. */
.dark svg text,
.dark .recharts-text { fill: #c3ccd6; }
.dark .recharts-curve.recharts-line-curve { stroke: #cdd6e2; }
.dark .recharts-cartesian-axis-line,
.dark .recharts-cartesian-axis-tick line { stroke: rgba(255, 255, 255, 0.18); }
.dark .recharts-cartesian-grid line { stroke: rgba(255, 255, 255, 0.1); }
.dark .recharts-reference-line line { stroke: rgba(255, 255, 255, 0.24); }

.dark .app-chart__empty-head { color: var(--sfr-dark-text, #f4f5f7); }
.dark .app-chart__empty-body,
.dark .app-chart__note,
.dark .app-chart__flat,
.dark .app-chart__spark-empty,
.dark .app-chart__legend,
.dark .app-chart__tip-meta { color: #aab4c0; }
.dark .app-chart__tip {
  background: var(--sfr-dark-overlay, #26344a);
  border-color: rgba(255, 255, 255, 0.1);
  color: var(--sfr-dark-text, #f4f5f7);
}
.dark .app-chart__key-goal { border-top-color: #cdd6e2; }

/* Let axis tick labels render into the card padding instead of being clipped by
   the SVG/container box (fixes cut-off week + y-axis labels). */
.recharts-wrapper,
.recharts-surface { overflow: visible; }

/* ==========================================================================
   TREND CARD — <SFRTrendCard> (@sfr/app-chrome/chart).
   RULE: DESIGN-RUBRIC row 29. The composition Blake ratified 2026-07-16,
   extracted from sfr-scorecard's trend-card.tsx: title + headline chart with a
   status chip, then every market side by side on a SHARED y-domain.
   Sits on .app-panel (row 32 owns the surface), so it darkens with it.
   ========================================================================== */
.app-trend {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 16px;
}
@media (min-width: 768px) {
  .app-trend { padding: 24px; }
}

.app-trend__head {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
}
.app-trend__title {
  font-family: var(--sfr-font-display);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  font-size: 16px;
  font-weight: 600;
  color: var(--sfr-navy);
  text-decoration: none;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
a.app-trend__title:hover { text-decoration: underline; }

.app-trend__headline { display: flex; flex-direction: column; gap: 4px; }
.app-trend__headline-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}
.app-trend__chips { display: inline-flex; align-items: center; gap: 4px; }

.app-trend__market-label,
.app-trend__market-link {
  font-size: 14px;
  color: var(--sfr-muted, #5b6675);
  text-decoration: none;
}
.app-trend__market-link:hover { color: var(--sfr-navy); text-decoration: underline; }

.app-trend__sparse {
  margin: 0;
  font-size: 14px;
  color: var(--sfr-muted, #5b6675);
}

/* Markets side by side — KCI left, DFW right. They MUST sit on one shared
   y-domain (the component computes it); side-by-side without a shared domain is
   a lie, because a smaller number can draw a taller line. */
.app-trend__markets { display: flex; gap: 12px; }
.app-trend__market { display: flex; flex-direction: column; min-width: 0; flex: 1; }
/* Under 768px a two-up pair is unreadable — stack (rubric row 31's spirit). */
@media (max-width: 767px) {
  .app-trend__markets { flex-direction: column; }
}

/* Status chip — icon+text, never colour-only (WCAG 1.4.1). */
.app-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  border-radius: var(--sfr-radius-pill);
  border: 1px solid var(--sfr-line);
  padding: 2px 10px;
  font-size: 12px;
  font-weight: 600;
  color: var(--sfr-navy);
  background: var(--sfr-white);
  white-space: nowrap;
}
.app-chip__dot {
  display: inline-block;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  flex: none;
}

/* A qualifier, NOT a status: "Partial" means not finished, which must never read
   as "off goal". Deliberately outline-only so it cannot be mistaken for a chip. */
.app-pill-note {
  display: inline-flex;
  align-items: center;
  border-radius: var(--sfr-radius-pill);
  border: 1px dashed var(--sfr-line);
  padding: 2px 8px;
  font-size: 12px;
  color: var(--sfr-muted, #5b6675);
  background: transparent;
  white-space: nowrap;
}

.dark .app-trend__title { color: #e8eaed; }
.dark .app-trend__market-label,
.dark .app-trend__market-link,
.dark .app-trend__sparse,
.dark .app-pill-note { color: #c3ccd6; }
.dark .app-trend__market-link:hover { color: #f4f5f7; }
.dark .app-chip {
  background: #0e141d;
  border-color: rgba(255, 255, 255, 0.14);
  color: #e8eaed;
}
.dark .app-pill-note { border-color: rgba(255, 255, 255, 0.18); }

/* ==========================================================================
   ALERTS (candidate C-1 / proposed row 43) — the bell, its panel, and the
   "look here" flash a deep link leaves on its target.

   NO `display` ON .app-alerts__panel. AppAlerts hides the closed panel with the
   `hidden` ATTRIBUTE, not a class, so it works with no stylesheet at all. An
   unconditional author `display` here would BEAT [hidden] and expose the closed
   panel in a styled build, while every jsdom test stayed green. Style the open
   state only.
   ========================================================================== */
.app-alerts { position: relative; display: inline-flex; align-items: center; }

.app-alerts__bell {
  position: relative;
  width: 44px; height: 44px;              /* row 20 — gloved hands, sunlight */
  display: inline-flex; align-items: center; justify-content: center;
  border: 0; background: transparent; border-radius: 8px; cursor: pointer;
  color: rgba(255, 255, 255, 0.8);
}
.app-alerts__bell:hover { background: rgba(255, 255, 255, 0.1); color: var(--sfr-honey); }

.app-alerts__badge {
  position: absolute; top: 5px; right: 5px;
  min-width: 17px; height: 17px; padding: 0 4px;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 999px;
  background: var(--sfr-bad); color: var(--sfr-white);
  font-family: var(--sfr-font-body);
  font-size: 11px;                        /* row 16 floor — not below 11 */
  font-weight: 700; line-height: 1;
}

/* The aria-live region. Announced, never seen — without this it renders as a
   stray line of text under the bell in any styled build. */
.app-alerts__live {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}

.app-alerts__panel:not([hidden]) {
  position: absolute; top: calc(100% + 6px); right: 0; z-index: 60;
  display: block;
  width: min(360px, calc(100vw - 32px));
  max-height: 70vh; overflow-y: auto;
  background: var(--sfr-white); color: var(--sfr-navy);
  border: 1px solid rgba(30, 42, 59, 0.14);
  border-radius: var(--sfr-radius, 14px);
  box-shadow: 0 14px 34px rgba(0, 0, 0, 0.22);
  padding: 6px;
}
.dark .app-alerts__panel:not([hidden]) {
  background: var(--sfr-navy-600, #24314a); color: var(--sfr-white);
  border-color: rgba(255, 255, 255, 0.14);
}
.app-alerts__list { list-style: none; margin: 0; padding: 0; }
.app-alerts__empty { margin: 0; padding: 14px; font-size: 0.9rem; opacity: 0.75; }

.app-alerts__item {
  display: flex; align-items: flex-start; gap: 8px;
  padding: 4px; border-radius: 10px;
}
.app-alerts__item + .app-alerts__item { border-top: 1px solid rgba(30, 42, 59, 0.08); }
.dark .app-alerts__item + .app-alerts__item { border-top-color: rgba(255, 255, 255, 0.1); }

.app-alerts__link {
  flex: 1; display: flex; flex-direction: column; gap: 2px;
  min-height: 44px; justify-content: center;    /* row 20 */
  padding: 8px; border: 0; background: none; cursor: pointer;
  text-align: left; text-decoration: none; color: inherit; font: inherit;
  border-radius: 8px;
}
.app-alerts__link:hover { background: rgba(30, 42, 59, 0.06); }
.dark .app-alerts__link:hover { background: rgba(255, 255, 255, 0.08); }
.app-alerts__title { font-weight: 650; font-size: 0.92rem; }
.app-alerts__body { font-size: 0.82rem; opacity: 0.72; }

/* Unread marker — the honey dot is the accent doing accent work, which is
   allowed; the POINTER yellow below is a different job. */
.app-alerts__item.is-unread .app-alerts__title::before {
  content: ""; display: inline-block; vertical-align: middle;
  width: 6px; height: 6px; margin-right: 7px; border-radius: 50%;
  background: var(--sfr-honey);
}

.app-alerts__toggle {
  flex: none; align-self: center;
  min-height: 44px; padding: 0 10px;             /* row 20 */
  border: 0; background: none; cursor: pointer;
  color: inherit; opacity: 0.66;
  font-family: var(--sfr-font-body); font-size: 12px;
  border-radius: 8px; white-space: nowrap;
}
.app-alerts__toggle:hover { opacity: 1; background: rgba(30, 42, 59, 0.06); }
.dark .app-alerts__toggle:hover { background: rgba(255, 255, 255, 0.08); }

/* ── The flash ──────────────────────────────────────────────────────────────
   THREE times, then stop — `animation-iteration-count: 3`. The count lives in
   CSS on purpose: sfr-jobcosting re-implements the same effect with a JS
   interval, and its own comment records that as the worse of the two.

   scroll-margin-top keeps the target clear of the sticky header once
   flashTarget() has scrolled with block:"start". Without it the element lands
   underneath the chrome and the flash is invisible. */
.app-flash, .app-flash--held { scroll-margin-top: 84px; border-radius: 10px; }

@keyframes sfr-flash {
  0%, 100% { box-shadow: 0 0 0 0 transparent; }
  50%      { box-shadow: 0 0 0 4px color-mix(in srgb, var(--color-status-yellow) 55%, transparent); }
}
.app-flash { animation: sfr-flash 500ms ease-in-out 3; }

/* Reduced motion: HELD, not absent. The user still has to find what they
   clicked — what they must not get is 1.5s of flashing yellow, which is a
   vestibular and photosensitivity concern. flashTarget() applies this class
   instead and removes it on interaction, so it is not on a timer either.
   No existing implementation in the fleet guards this at all. */
.app-flash--held {
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--color-status-yellow) 55%, transparent);
  transition: box-shadow 200ms ease-out;
}
@media (prefers-reduced-motion: reduce) {
  /* Belt and braces: even if an app adds .app-flash directly, it must not animate. */
  .app-flash {
    animation: none;
    box-shadow: 0 0 0 4px color-mix(in srgb, var(--color-status-yellow) 55%, transparent);
  }
}

/* ── <Traceable> — the verify-this affordance (DESIGN-RUBRIC row 44, M6) ───────────
   The marker is a dotted underline + a small magnifying glyph: a TEXTURE cue, never
   colour-only (WCAG 1.4.1). The popover mirrors .app-chart__tip. */
.app-src {
  position: relative;
  text-decoration: underline dotted;
  text-underline-offset: 2px;
  cursor: help;
}
.app-src--link { cursor: pointer; color: inherit; }
.app-src:focus-visible {
  outline: 2px solid var(--color-status-info, #1D5E96);
  outline-offset: 2px;
  border-radius: 2px;
}
.app-src__glyph { display: inline-block; margin-inline-start: 0.15em; opacity: 0.55; }
.app-src__pop {
  position: absolute;
  left: 0;
  top: calc(100% + 6px);
  z-index: 2147482500;
  display: block;
  min-width: 200px;
  max-width: 360px;
  white-space: normal;
  background: var(--sfr-white, #fff);
  color: #1e2a3b;
  border: 1px solid rgba(30, 42, 59, 0.14);
  border-radius: var(--sfr-radius, 14px);
  box-shadow: 0 12px 32px rgba(0, 0, 0, 0.18);
  padding: 10px 12px;
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  cursor: default;
}
.app-src__pop-head { display: block; font-weight: 600; opacity: 0.7; margin-bottom: 6px; }
.app-src__formula {
  display: block;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  background: rgba(30, 42, 59, 0.05);
  border-radius: 6px;
  padding: 4px 6px;
  margin-bottom: 6px;
}
.app-src__rows {
  display: grid;
  grid-template-columns: repeat(var(--app-src-cols, 2), auto);
  gap: 2px 10px;
  margin-bottom: 6px;
}
.app-src__th { font-weight: 600; opacity: 0.6; font-size: 11px; }
.app-src__td { font-size: 12px; }
.app-src__more { display: block; opacity: 0.55; font-size: 11px; margin-bottom: 6px; }
.app-src__link { color: var(--color-status-info, #1D5E96); font-weight: 600; text-decoration: none; }
.app-src__link:hover { text-decoration: underline; }

.dark .app-src__pop {
  background: #141c28;
  color: #e8eaed;
  border-color: rgba(255, 255, 255, 0.14);
  box-shadow: 0 12px 32px rgba(0, 0, 0, 0.5);
}
.dark .app-src__formula { background: rgba(255, 255, 255, 0.06); }
.dark .app-src__link { color: #7fb3e0; }

/* ── RecordDrawer — the deep tier of <Traceable> (DESIGN-RUBRIC row 44, M6) ────────
   A right slide-out record dialog: for a recordset too large for the popover. Portaled
   to <body>, so the scrim + panel are positioned against the viewport. The panel slides
   in from the right, but only when motion is welcome (WCAG 2.3.3). */
.app-src--drawer { cursor: pointer; }
.app-drawer__scrim {
  position: fixed;
  inset: 0;
  z-index: 2147482600;
  display: flex;
  justify-content: flex-end;
  background: rgba(15, 22, 33, 0.42);
}
.app-drawer {
  display: flex;
  flex-direction: column;
  width: min(440px, 92vw);
  max-width: 100%;
  height: 100%;
  background: var(--sfr-white, #fff);
  color: #1e2a3b;
  border-inline-start: 1px solid rgba(30, 42, 59, 0.14);
  box-shadow: -16px 0 40px rgba(0, 0, 0, 0.22);
  outline: none;
}
@media (prefers-reduced-motion: no-preference) {
  .app-drawer { animation: app-drawer-in 180ms ease-out; }
  @keyframes app-drawer-in {
    from { transform: translateX(100%); }
    to { transform: translateX(0); }
  }
}
.app-drawer__bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 14px 16px;
  border-bottom: 1px solid rgba(30, 42, 59, 0.1);
}
.app-drawer__title {
  font-weight: 700;
  font-size: 14px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.app-drawer__close {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border: none;
  background: transparent;
  color: inherit;
  border-radius: 8px;
  cursor: pointer;
}
.app-drawer__close:hover { background: rgba(30, 42, 59, 0.08); }
.app-drawer__close:focus-visible {
  outline: 2px solid var(--color-status-info, #1D5E96);
  outline-offset: 2px;
}
.app-drawer__body {
  flex: 1;
  min-height: 0;
  overflow: auto;
  padding: 16px;
  font-size: 13px;
  font-variant-numeric: tabular-nums;
}
.app-drawer__head { display: block; font-weight: 600; opacity: 0.7; margin-bottom: 8px; }
.app-drawer__note {
  display: block;
  opacity: 0.7;
  font-style: italic;
  margin-bottom: 8px;
}
.app-drawer__formula {
  display: block;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  background: rgba(30, 42, 59, 0.05);
  border-radius: 6px;
  padding: 6px 8px;
  margin-bottom: 12px;
}
.app-drawer__table {
  width: 100%;
  border-collapse: collapse;
  margin-bottom: 12px;
}
.app-drawer__table th {
  position: sticky;
  top: 0;
  text-align: start;
  font-weight: 600;
  font-size: 11px;
  opacity: 0.6;
  padding: 4px 8px;
  background: var(--sfr-white, #fff);
  border-bottom: 1px solid rgba(30, 42, 59, 0.12);
}
.app-drawer__table td {
  padding: 4px 8px;
  border-bottom: 1px solid rgba(30, 42, 59, 0.06);
}
.app-drawer__link { color: var(--color-status-info, #1D5E96); font-weight: 600; text-decoration: none; }
.app-drawer__link:hover { text-decoration: underline; }

.dark .app-drawer {
  background: #141c28;
  color: #e8eaed;
  border-inline-start-color: rgba(255, 255, 255, 0.14);
  box-shadow: -16px 0 40px rgba(0, 0, 0, 0.5);
}
.dark .app-drawer__bar { border-bottom-color: rgba(255, 255, 255, 0.1); }
.dark .app-drawer__close:hover { background: rgba(255, 255, 255, 0.08); }
.dark .app-drawer__formula { background: rgba(255, 255, 255, 0.06); }
.dark .app-drawer__table th {
  background: #141c28;
  border-bottom-color: rgba(255, 255, 255, 0.14);
}
.dark .app-drawer__table td { border-bottom-color: rgba(255, 255, 255, 0.07); }
.dark .app-drawer__link { color: #7fb3e0; }

/* ── <SFRStat> — the KPI / stat tile, traceable by construction (row 44, M6) ───────
   The value is rendered inside a <Traceable> (.app-src), so the verify-marker sits on
   the number itself. The trend is a secondary read: a direction glyph (never colour-
   only) whose tone the APP decides. */
.app-stat { display: flex; flex-direction: column; gap: 4px; }
.app-stat__label {
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  opacity: 0.6;
}
.app-stat__value {
  font-size: 28px;
  font-weight: 700;
  line-height: 1.1;
  font-variant-numeric: tabular-nums;
}
.app-stat__trend {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: 12px;
  font-weight: 600;
  opacity: 0.75;
}
.app-stat__trend-glyph { flex: none; }
.app-stat__trend--good { color: var(--color-status-success, #166534); opacity: 1; }
.app-stat__trend--bad { color: var(--color-status-error, #B4231A); opacity: 1; }

/* ── chart provenance (row 44, M6) ────────────────────────────────────────────────
   A chart-level source affordance below the chart, and a per-point source line inside
   the tooltip (.app-chart__tip already ships). */
.app-chart__source { margin-top: 6px; font-size: 12px; opacity: 0.75; }
.app-chart__tip-src {
  margin-top: 4px;
  padding-top: 4px;
  border-top: 1px solid rgba(30, 42, 59, 0.12);
}
.app-chart__tip-link {
  display: inline-block;
  margin-top: 2px;
  color: var(--color-status-info, #1D5E96);
  font-weight: 600;
  text-decoration: none;
}
.app-chart__tip-link:hover { text-decoration: underline; }

.dark .app-stat__trend--good { color: #4ea56e; }
.dark .app-stat__trend--bad { color: #e0796f; }
.dark .app-chart__tip-src { border-top-color: rgba(255, 255, 255, 0.14); }
.dark .app-chart__tip-link { color: #7fb3e0; }

/* ── <AppFeatures> — the /features page as a component (DESIGN-RUBRIC row 42, M8) ──────
   Website-style, icon-based, scannable full-bleed banner bands. NOT cards. Dark is the
   PRIMARY theme (M10: desktop is dark) — every rule carries a .dark counterpart. */
.app-feat-band { padding: 52px 24px; }
.app-feat-band__inner { max-width: 1120px; margin: 0 auto; }
.app-feat-band--sheet { background: var(--sfr-white, #fff); }
.dark .app-feat-band--sheet { background: #141c28; }
.app-feat-band--tint { background: rgba(234, 179, 63, 0.16); padding-top: 30px; padding-bottom: 30px; }
.dark .app-feat-band--tint { background: rgba(234, 179, 63, 0.12); }
.app-feat-band__title { font-family: var(--sfr-font-display); text-transform: uppercase; letter-spacing: 0.02em; font-size: 1.5rem; font-weight: 700; margin: 0 0 6px; color: var(--sfr-navy); }
.dark .app-feat-band__title { color: #e8eaed; }
.app-feat-band__intro { font-size: 1.02rem; color: var(--sfr-navy-600, #41506a); margin: 0 0 26px; max-width: 60ch; }
.dark .app-feat-band__intro { color: #aeb8c6; }

.app-feat-hero { padding: 26px 24px 30px; border-bottom: 3px solid var(--sfr-honey); }
.app-feat-hero .app-eyebrow { margin: 0 0 8px; }
.app-feat-title { font-size: clamp(2.1rem, 5vw, 3rem); line-height: 1.02; margin: 0; color: var(--sfr-navy); }
.dark .app-feat-title { color: #e8eaed; }
.app-feat-lede { font-size: 1.2rem; line-height: 1.45; max-width: 56ch; margin: 14px 0 0; }

.app-feat-need { display: flex; align-items: center; gap: 13px; padding: 9px 0; border-bottom: 1px solid rgba(30, 42, 59, 0.14); }
.app-feat-need:last-child { border-bottom: 0; }
.dark .app-feat-need { border-bottom-color: rgba(255, 255, 255, 0.14); }
.app-feat-need__ico { flex: none; display: inline-flex; color: var(--sfr-bad); }
.app-feat-need--warn .app-feat-need__ico { color: var(--sfr-warn); }
.app-feat-need--good .app-feat-need__ico { color: var(--sfr-good); }
.app-feat-need__do { font-family: var(--sfr-font-display); text-transform: uppercase; letter-spacing: 0.01em; font-weight: 700; font-size: 0.98rem; }
.app-feat-need__do a { text-decoration: none; color: inherit; }
.app-feat-need__why { font-size: 0.92rem; color: var(--sfr-navy-600, #41506a); margin-top: 1px; }
.dark .app-feat-need__why { color: #aeb8c6; }
.app-feat-need__count { flex: none; margin-left: auto; font-family: var(--sfr-font-display); font-size: 1.35rem; font-weight: 800; color: var(--sfr-bad); }
.app-feat-need--warn .app-feat-need__count { color: var(--sfr-warn); }
.app-feat-need--good .app-feat-need__count { color: var(--sfr-good); }

.app-feat-steps { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(5, 1fr); gap: 16px; }
.app-feat-step { text-align: center; position: relative; }
.app-feat-step__ico { display: inline-flex; align-items: center; justify-content: center; width: 60px; height: 60px; border-radius: 50%; background: rgba(234, 179, 63, 0.22); color: var(--sfr-navy); }
.dark .app-feat-step__ico { background: rgba(234, 179, 63, 0.18); color: #e8eaed; }
.app-feat-step--elsewhere .app-feat-step__ico { background: transparent; border: 2px dashed rgba(30, 42, 59, 0.3); color: var(--sfr-navy-600, #41506a); }
.dark .app-feat-step--elsewhere .app-feat-step__ico { border-color: rgba(255, 255, 255, 0.3); color: #aeb8c6; }
.app-feat-step__n { display: block; font-family: var(--sfr-font-display); font-size: 0.72rem; font-weight: 700; color: var(--sfr-navy-600, #41506a); margin: 12px 0 3px; letter-spacing: 0.12em; }
.dark .app-feat-step__n { color: #aeb8c6; }
.app-feat-step__label { display: block; font-weight: 700; font-size: 1rem; line-height: 1.25; }
.app-feat-step__label a { color: inherit; text-decoration: none; }
.app-feat-step__label a:hover { text-decoration: underline; }
.app-feat-step__who { display: block; font-size: 0.9rem; color: var(--sfr-navy-600, #41506a); margin-top: 4px; }
.dark .app-feat-step__who { color: #aeb8c6; }
.app-feat-step:not(:last-child)::after { content: ""; position: absolute; top: 30px; left: calc(50% + 38px); right: calc(-50% + 38px); height: 2px; background: rgba(30, 42, 59, 0.16); }
.dark .app-feat-step:not(:last-child)::after { background: rgba(255, 255, 255, 0.16); }

.app-feat-list { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 34px 40px; }
.app-feat-feature__ico { display: inline-flex; align-items: center; justify-content: center; width: 54px; height: 54px; border-radius: 15px; background: rgba(234, 179, 63, 0.22); color: var(--sfr-navy); margin-bottom: 14px; }
.dark .app-feat-feature__ico { background: rgba(234, 179, 63, 0.18); color: #e8eaed; }
.app-feat-feature__name { display: inline-flex; align-items: center; gap: 7px; font-family: var(--sfr-font-display); text-transform: uppercase; letter-spacing: 0.02em; font-size: 1.25rem; font-weight: 700; margin: 0; text-decoration: none; color: var(--sfr-ink); }
.dark .app-feat-feature__name { color: #e8eaed; }
.app-feat-feature__name:hover { color: var(--sfr-honey); }
.app-feat-feature__name svg { opacity: 0.5; }
.app-feat-feature__what { font-size: 1.02rem; line-height: 1.5; margin: 8px 0 12px; }
.dark .app-feat-feature__what { color: #e8eaed; }
.app-feat-feature__cans { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.app-feat-feature__can { display: flex; align-items: baseline; gap: 9px; font-size: 1rem; }
.app-feat-feature__can > svg { flex: none; position: relative; top: 3px; color: var(--sfr-honey); }
.app-feat-feature__can a { color: var(--color-status-info, #1D5E96); font-weight: 600; text-decoration: none; }
.app-feat-feature__can a:hover { text-decoration: underline; }
.dark .app-feat-feature__can a { color: #7fb3e0; }
.app-feat-feature__where { color: var(--sfr-navy-600, #41506a); }
.dark .app-feat-feature__where { color: #aeb8c6; }

.app-feat-also { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
.app-feat-also__ico { flex: none; color: var(--sfr-navy-600, #41506a); }
.dark .app-feat-also__ico { color: #aeb8c6; }
.app-feat-also__list { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: 8px 24px; }
.app-feat-also__list li { font-size: 1.02rem; }
.app-feat-also__tag { color: var(--sfr-navy-600, #41506a); font-size: 0.88rem; margin-left: 6px; }
.dark .app-feat-also__tag { color: #aeb8c6; }

/* Elements that INHERIT color (no base class with a dark override) — flip them light in dark, or
   they render navy-on-navy. Row 9. */
.dark .app-feat-need__do,
.dark .app-feat-step__label,
.dark .app-feat-also__list li { color: #e8eaed; }

@media (max-width: 820px) {
  .app-feat-steps { grid-template-columns: 1fr; gap: 0; }
  .app-feat-step { text-align: left; display: grid; grid-template-columns: 60px 1fr; grid-template-rows: auto auto auto; column-gap: 16px; padding: 2px 0 20px; }
  .app-feat-step__ico { grid-row: 1 / span 3; align-self: start; }
  .app-feat-step__n { margin: 0 0 2px; }
  .app-feat-step:not(:last-child)::after { top: 60px; bottom: 0; left: 29px; right: auto; width: 2px; height: auto; }
}
@media (max-width: 600px) {
  .app-feat-band { padding: 34px 18px; }
  .app-feat-hero { padding: 20px 18px 26px; }
  .app-feat-list { grid-template-columns: 1fr; gap: 26px; }
  .app-feat-need { gap: 12px; }
  .app-feat-need__count { font-size: 1.6rem; }
}

}
