/* =============================================================================
 * responsive-shell.css — shared PHONE-ONLY shell contract (≤767px).
 *
 * Loaded from base.html AFTER every other stylesheet (including per-page
 * CSS from {% block extra_css %}), so equal-specificity rules here win the
 * cascade. Desktop (≥768px) must stay pixel-identical: every rule that can
 * affect server-rendered desktop markup lives inside @media (max-width:767px).
 *
 * Owns (page agents must NOT restyle these):
 *   - .cd-toolbar / .tb-search / .tb-menu phone treatment (ONE 52px row)
 *   - .tb-back      — icon-only back chevron; .tb-full/.tb-short label swap
 *   - .rt-chipbar / .rt-chip — THE filter/sort chip row (page agents apply
 *                   these classes and DELETE their bespoke chip styles)
 *   - .rt-btn / .rt-btn-primary / .rt-btn-quiet / .rt-btn-danger /
 *     .rt-btn-icon — THE button system + the .btn normalization net and
 *                   the :focus-visible ring (round 4; page agents adopt
 *                   the classes and DELETE their bespoke button styles)
 *   - .rt-seg      — THE segmented control (view/period toggles)
 *   - .rt-sheet    — bottom-sheet vocabulary + anatomy (.rt-sheet-title/
 *                   -close/-content/-foot/-count/-actions, .no-anim)
 *   - .rt-actionbar — fixed bottom selection action bar vocabulary
 *   - toast fit, .content-wrapper phone padding, 16px input floor
 *
 * GRID & RHYTHM (the 12px grid — every page sits on it):
 *   - Outer page gutter: 12px (set on .content-wrapper below; redesigned
 *     pages own their body padding and must use the same 12px side gutter).
 *   - Card lists: cards full-bleed to the 12px gutter, 12px between cards,
 *     14px internal padding.
 *   - Vertical rhythm inside a card: 6px between text lines, 10px before a
 *     meta row; hairline (0.5px var(--line)) separators ONLY between
 *     semantic groups.
 *   - Caption labels: 10.5px uppercase letter-spaced, color var(--ink-3);
 *     values share one left edge per card; numerals right-aligned in mono.
 * ========================================================================== */

/* The .tb-menu hamburger and .tb-search-close button exist ONLY via JS
   injection (sidebar.js / search-bar.js) — they are never in server HTML,
   so hiding them by default keeps desktop rendered output byte-identical.
   The phone media block below reveals them. */
.tb-menu { display: none; }
.tb-search-close { display: none; }

/* Mobile Safari inflates text on orientation change without this guard.
   No effect on desktop browsers. */
html { -webkit-text-size-adjust: 100%; }

@media (max-width: 767px) {

  /* ── Toolbar — ONE 52px row, everywhere ──────────────────────────────
     Desktop gives .tb-search a fixed 540px slot which inflates the phone
     layout viewport to ~1200px; it collapses to a 44px icon button below.
     The row NEVER wraps: the title truncates with ellipsis, buttons keep
     shrink-to-fit content width. Page agents make their labels fit via
     the .tb-full/.tb-short span swap and the .tb-back chevron below —
     if a page STILL can't fit one row, its ⋯-menu items move into the
     primary sheet. Never a second row. */
  .cd-page .cd-toolbar {
    height: auto;
    min-height: 52px;
    flex-wrap: nowrap;
    gap: 6px;
    padding: 4px max(10px, env(safe-area-inset-right, 0px))
             4px max(6px,  env(safe-area-inset-left,  0px));
  }
  .cd-page .cd-toolbar .left {
    flex: 1 1 auto;
    min-width: 0;
    gap: 4px;
  }
  .cd-page .cd-toolbar .right {
    flex: 0 0 auto;
    min-width: 0;
    gap: 6px;
  }
  /* Title truncates instead of clipping under the search slot. !important
     beats the inline font-size some templates put on the toolbar h1. */
  .cd-page .cd-toolbar .left h1 {
    font-size: 16px !important;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    min-width: 0;
    flex: 0 1 auto;
  }
  /* Counters / dividers / subtitle spans in the toolbar are desktop
     niceties — .pos pager position, .div hairlines, "N total · N in
     repair" subtotals. Phones drop them; the data lives on the page. */
  .cd-page .cd-toolbar .left > span,
  .cd-page .cd-toolbar .pos,
  .cd-page .cd-toolbar .div {
    display: none;
  }
  /* Keyboard-shortcut chips mean nothing on touch. */
  .cd-page .kbd,
  .tb-search .kbd,
  kbd {
    display: none !important;
  }
  /* The problem-status hint is nowrap italic prose — it can't fit. The
     status is visible on the page itself. (!important outranks the
     [data-status="problem"] display:inline-block rule.) */
  .cd-page .cd-toolbar .cd-toolbar-hint { display: none !important; }

  /* Toolbar interactive elements get a ≥44px hit area and never squash:
     buttons are shrink-to-fit (content width, no flex squeeze) — the
     TITLE is the only element that gives way on tight rows. */
  .cd-page .cd-toolbar .btn,
  .cd-page .cd-toolbar .overflow-btn {
    min-height: 40px;
    min-width: 40px;
    flex-shrink: 0;
    white-space: nowrap;
  }
  .cd-page .cd-toolbar .btn-icon { min-width: 44px; min-height: 44px; }
  .cd-page .cd-toolbar .left > a {
    min-height: 44px;
    display: inline-flex;
    align-items: center;
  }

  /* ── .tb-full / .tb-short — toolbar label swap ───────────────────────
     Page agents wrap a button's desktop label in <span class="tb-full">
     and add a sibling <span class="tb-short" hidden> with the short
     phone label ("+ Add computer" → "+ Add", "Export CSV" → "CSV"…):
       <button class="btn btn-primary">
         <span class="tb-full">+ Add computer</span>
         <span class="tb-short" hidden>+ Add</span>
       </button>
     Desktop needs no CSS: .tb-full renders, [hidden] hides .tb-short.
     On phones the pair flips below. !important + the [hidden] variant
     are REQUIRED: Bootstrap's reboot ships [hidden]{display:none
     !important} at (0,1,0), so the reveal must match it with !important
     and outrank it with .tb-short[hidden] at (0,2,0). */
  .tb-full { display: none !important; }
  .tb-short,
  .tb-short[hidden] { display: inline !important; }

  /* ── .tb-back — icon-only back chevron ───────────────────────────────
     Page agents ADD this class to their existing back button/anchor
     (keep href/data-cd-back handlers; put aria-label="Back" on the
     element since the text label is hidden here). Any bare glyph text
     ("← ") collapses via font-size:0; the label span is hidden; the
     ::before chevron is the only visible content. Desktop unchanged —
     these rules exist only in this phone block. */
  .cd-page .cd-toolbar .tb-back {
    flex: 0 0 44px;
    width: 44px;
    min-width: 44px;
    height: 44px;
    min-height: 44px;
    padding: 0;
    gap: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 0 !important;      /* hides stray text nodes (e.g. "← ") */
    line-height: 0;
  }
  .cd-page .cd-toolbar .tb-back > span { display: none !important; }
  .cd-page .cd-toolbar .tb-back::before {
    content: '‹';
    font-size: 24px;
    line-height: 1;
    font-weight: 400;
    color: var(--ink-2, #3A3832);
    transform: translateY(-2px);  /* optical centring of the glyph */
  }

  /* ── Hamburger (injected by sidebar.js as first child of .left) ────── */
  .cd-page .cd-toolbar .tb-menu {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    flex: 0 0 44px;
    margin-left: -6px;           /* optically align the icon with the edge */
    padding: 0;
    border: none;
    background: transparent;
    border-radius: 8px;
    color: var(--ink-2, #3A3832);
    font-size: 17px;
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
  }
  .cd-page .cd-toolbar .tb-menu:active { background: var(--surface-2, #F1EFE8); }

  /* ── Search: collapse to a 44px icon button ────────────────────────── */
  .cd-page .cd-toolbar > .tb-search {
    flex: 0 0 44px;
    width: 44px;
    min-width: 44px;
    max-width: 44px;
    height: 44px;
    padding: 0;
    justify-content: center;
    background: transparent;
    border: none;
    border-radius: 8px;
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
  }
  /* The ⌕ glyph is an inline-styled span (13px) — bump it to icon size. */
  .cd-page .cd-toolbar > .tb-search > span:first-child {
    font-size: 19px !important;
    line-height: 1;
  }
  .cd-page .cd-toolbar > .tb-search:not(.is-open) input,
  .cd-page .cd-toolbar > .tb-search:not(.is-open) .tb-search-chip,
  .cd-page .cd-toolbar > .tb-search:not(.is-open) .tb-search-count,
  .cd-page .cd-toolbar > .tb-search:not(.is-open) .cd-search-dropdown {
    display: none;
  }

  /* ── Search: expanded overlay state (.is-open, toggled by search-bar.js).
     The SAME node becomes a fixed full-width bar over the toolbar; the
     existing dropdown/suggest anchor inside it and go full-width. ─────── */
  .cd-page .cd-toolbar > .tb-search.is-open {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    z-index: 300;                 /* above the sticky toolbar (250) */
    width: auto;
    max-width: none;
    height: 52px;
    display: flex;
    align-items: center;
    justify-content: flex-start;
    gap: 8px;
    padding: 0 4px 0 max(14px, env(safe-area-inset-left, 0px));
    background: var(--surface, #FFFFFF);
    border: none;
    border-bottom: 0.5px solid var(--line, #E0DDD2);
    border-radius: 0;
    cursor: default;
  }
  .cd-page .cd-toolbar > .tb-search.is-open > span:first-child {
    font-size: 15px !important;
  }
  .cd-page .cd-toolbar > .tb-search.is-open input {
    display: block;
    flex: 1 1 auto;
    min-width: 0;
  }
  .cd-page .cd-toolbar > .tb-search.is-open .tb-search-close {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    flex: 0 0 44px;
    border: none;
    background: transparent;
    color: var(--ink-3, #605D54);
    font-size: 22px;
    line-height: 1;
    cursor: pointer;
    border-radius: 8px;
    -webkit-tap-highlight-color: transparent;
  }
  /* Scope-chip ✕ ("in parcels" ×): 16px visual stays, but the effective
     touch target grows to ≥44px via an invisible hit extension — a
     hover-sized close affordance is untappable on phones. */
  .tb-search-chip-x { position: relative; }
  .tb-search-chip-x::after {
    content: '';
    position: absolute;
    left: -14px;
    right: -14px;
    top: -14px;
    bottom: -14px;
  }

  /* Results / slash-suggest dropdown: pin under the fixed bar, full width,
     leave room for the on-screen keyboard. */
  .cd-page .cd-toolbar > .tb-search.is-open .cd-search-dropdown {
    position: fixed;
    top: 56px;
    left: 8px;
    right: 8px;
    max-height: min(55vh, 480px);
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
  }
  .cd-page .cd-toolbar > .tb-search.is-open .cd-search-row { padding: 11px 12px; }

  /* ── 16px input floor: stops iOS focus auto-zoom app-wide ──────────── */
  input[type="text"],
  input[type="search"],
  input[type="password"],
  input[type="email"],
  input[type="number"],
  input[type="tel"],
  input[type="url"],
  input[type="date"],
  input:not([type]),
  select,
  textarea {
    font-size: 16px !important;
  }

  /* ── .rt-chipbar / .rt-chip — THE filter/sort control style ──────────
     One horizontally scrolling row (never wraps), directly under the
     toolbar (or under the segmented toggle when both exist). Sort lives
     here too, as a chip ("Sort · Newest") opening its bottom sheet —
     never a <select> on phones. Page agents apply these classes to
     their own markup and DELETE their bespoke chip styles. Markup:
       <div class="rt-chipbar" hidden>          ← [hidden] keeps it off
         <button class="rt-chip is-active">        desktop; this block
           Model <span class="rt-chip-count">3</span>   reveals it ≤767px
         </button>
         <button class="rt-chip">Sort · Newest</button>
       </div>
     - .is-active = engaged facet: ink border + ink text + inverted count
       badge. NO weight change (chips must not shift width) and chips
       NEVER reorder on selection.
     - Tapping a chip opens ITS bottom sheet (.rt-sheet).
     - To hide a chipbar on phones too, use a page class/inline style —
       [hidden] alone is claimed by this desktop-off reveal. */
  .rt-chipbar,
  .rt-chipbar[hidden] {
    display: flex !important;      /* reveal past Bootstrap's [hidden] */
    flex-wrap: nowrap;
    align-items: center;
    gap: 8px;
    padding: 8px 12px;             /* 12px grid side gutter */
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
  }
  .rt-chipbar::-webkit-scrollbar { display: none; }
  .rt-chip {
    appearance: none;
    -webkit-appearance: none;
    flex: 0 0 auto;
    position: relative;            /* anchors the ::after hit extension */
    display: inline-flex;
    align-items: center;
    gap: 6px;
    min-height: 40px;
    padding: 8px 14px;
    background: var(--surface, #FFFFFF);
    border: 0.5px solid var(--line-2, #C7C3B5);
    border-radius: 999px;
    font: inherit;
    font-size: 13px;               /* brief, sentence-case labels */
    color: var(--ink-2, #3A3832);
    white-space: nowrap;
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
  }
  /* 44px effective hit area — visual height stays 40px. */
  .rt-chip::after {
    content: '';
    position: absolute;
    left: 0;
    right: 0;
    top: -2px;
    bottom: -2px;
  }
  .rt-chip.is-active {
    border-color: var(--ink, #1F1E1A);
    color: var(--ink, #1F1E1A);
  }
  /* .rt-chip-n: legacy alias emitted by the Parts lane switcher. */
  .rt-chip .rt-chip-count,
  .rt-chip .rt-chip-n {
    font-size: 11px;
    line-height: 1.5;
    font-variant-numeric: tabular-nums;
    background: var(--surface-2, #F1EFE8);
    color: var(--ink-2, #3A3832);
    border-radius: 999px;
    min-width: 18px;
    padding: 1px 6px;
    text-align: center;
  }
  .rt-chip.is-active .rt-chip-count,
  .rt-chip.is-active .rt-chip-n {
    background: var(--ink, #1F1E1A);
    color: var(--surface, #FFFFFF);
  }

  /* ── .rt-seg — THE segmented control (view/period toggles) ───────────
     Used by Board|Shelf, Awaiting|Parcels, M/Q/Y(/All) period switchers.
     NOT for page-level sections — those stay underline tabs (44px, 2px
     ink underline; existing per-page styles). Don't mix the two roles.
     Markup: segments are the DIRECT children (button/a); the active one
     carries .is-active:
       <div class="rt-seg" hidden>
         <button class="is-active">Board</button>
         <button>Shelf</button>
       </div>
     Inactive segments keep a transparent 0.5px border so geometry never
     shifts when .is-active moves. */
  .rt-seg,
  .rt-seg[hidden] {
    display: flex !important;      /* reveal past Bootstrap's [hidden] */
    align-items: stretch;
    background: var(--surface-2, #F1EFE8);
    border-radius: 10px;
    padding: 4px;
  }
  .rt-seg > * {
    appearance: none;
    -webkit-appearance: none;
    flex: 1 1 0;
    min-width: 0;
    min-height: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    padding: 0 10px;
    background: transparent;
    border: 0.5px solid transparent;
    border-radius: 8px;
    font: inherit;
    font-size: 13px;
    font-weight: 500;
    color: var(--ink-2, #3A3832);
    white-space: nowrap;
    text-decoration: none;
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
  }
  .rt-seg > .is-active {
    background: var(--surface, #FFFFFF);
    border-color: var(--line, #E0DDD2);
    color: var(--ink, #1F1E1A);
    font-weight: 600;
    box-shadow: 0 1px 2px rgba(31, 30, 26, 0.05);
  }

  /* ════════════════════════════════════════════════════════════════════
     THE CONTROL SYSTEM (round 4, Part B — names and metrics are LOCKED)

     One button language at ≤767px. Tokens:
       - type:    13px labels — 600 primary / 500 secondary / 400 quiet
       - radius:  8px (999px chips, 12px sheets, 7px thumbs, 6px mini-chips
                  are the ONLY other radii in the app)
       - height:  min 44px every control; 40px VISUAL is allowed only
                  inside the 52px toolbar row (the toolbar block above
                  keeps that at .cd-page .cd-toolbar specificity)
       - borders: 0.5px var(--line-2) on the secondary look
       - color:   primary = ink fill + white · secondary = surface +
                  line-2 border + ink-2 · quiet = transparent + ink-3 ·
                  danger = #8E2424 fill + white (destructive confirms
                  ONLY). #8E2424 is written literally because sheets
                  appended to <body> resolve var(--danger) to main.css's
                  legacy #ef4444, not the warm-paper danger.
       - ink-4 (#8E8B80) is BANNED for readable text (≈2.9:1 on white,
         fails WCAG AA) — decorative use only. Page agents must lift any
         ink-4 TEXT in their own files to var(--ink-3).

     SPECIFICITY STRATEGY — audited against the read-only page sheets
     (computer-detail.css, computers-list.css, parcels-board.css,
     financials2.css), all of which load BEFORE this file:

       Competing base rules (all ≤ (0,2,0)):
         .cd-page .btn            (0,2,0) 13.3333px/400, 28px, r6  [detail]
         .cd-page .btn-primary    (0,2,0) ink fill                 [detail]
         .cd-page .btn-ghost/.btn-icon/.btn-back  (0,2,0)          [detail]
         .btn / .btn.primary/.sm/.ghost  (0,1–2,0) 12.5px, r7      [parcels]
         .cl-dlg-actions .btn     (0,2,0) 12px, r6                 [computers]
         .btn (Bootstrap/main.css)(0,1,0) 13.3333px UA fallback

       1. The NORMALIZATION NET below is written at exactly (0,2,0)
          (`.cd-page .btn`, `.rt-sheet .btn`, …; :where() keeps extra
          qualifiers at zero weight). It BEATS every rule listed above:
          higher-than-(0,1,0) rules by specificity or, at (0,2,0), by
          source order — this file loads after all page CSS. The net sets
          TYPE + GEOMETRY ONLY (size/weight/height/padding/radius), never
          color/background/border-color, so page-level danger/ghost fills
          (.btn.cm-danger, .btn.ef-delete, disabled states) survive it.
       2. The canonical classes (.rt-btn…) are written DOUBLED —
          `.rt-btn-primary.rt-btn-primary` = (0,2,0) — and placed after
          the net, so an explicit .rt-btn-* class always beats the net
          and every (0,≤2,0) page rule by order, while staying low enough
          that page agents never need !important to compose with it.
       3. Page rules at (0,3,0)+ (e.g. `.cl-dlg-actions .btn.btn-primary`,
          `#pcl-drawer … .btn`, `.fin-page .c-tdetail .btns .btn`) still
          outrank both layers — those are the bespoke variants the page
          agents DELETE this round (they only carry color or already-
          conformant phone metrics today, so nothing regresses meanwhile).
       4. Buttons in containers appended to <body> (outside .cd-page —
          parcels drawer/confirm, computers dialogs) are reached via
          `.rt-sheet .btn` / `.rt-actionbar .btn` once the page agent adds
          the .rt-sheet class those containers already adopt on phones.
     ════════════════════════════════════════════════════════════════════ */

  /* ── Normalization net — an unstyled/bespoke .btn can never render at
     browser-default 13.3333px (or off-token 11.5/12/12.5px, radius
     4/6/7/11px, height 28/34/36px) again. Metrics only; looks below. */
  .cd-page .btn,
  .cd-page :where(.cd-toolbar) .overflow-btn,
  .rt-sheet .btn,
  .rt-actionbar .btn {
    /* `font: inherit` fully resets Chrome's `font: -webkit-small-control`
       system shorthand (sub-properties no longhand reaches), exactly like
       computer-detail.css does; the longhands then pin the token type. */
    font: inherit;
    font-family: inherit;
    font-size: 13px;
    font-weight: 500;
    line-height: 1.2;
    height: auto;
    min-height: 44px;              /* toolbar row overrides to 40 visual */
    min-width: 44px;
    padding: 0 14px;
    border-radius: 8px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    text-decoration: none;
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
  }

  /* Map the pages' primary spellings to THE primary look (ink fill +
     white, 600). Bare `.btn-primary` (0,1,0) also catches legacy /
     Bootstrap-styled buttons outside .cd-page and re-inks them. */
  .cd-page .btn:where(.primary, .btn-primary),
  .rt-sheet .btn:where(.primary, .btn-primary),
  .rt-actionbar .btn:where(.primary, .btn-primary),
  .btn-primary {
    background: var(--ink, #1F1E1A);
    border-color: var(--ink, #1F1E1A);
    color: #FFFFFF;
    font-weight: 600;
  }
  .cd-page .btn:where(.primary, .btn-primary):hover,
  .rt-sheet .btn:where(.primary, .btn-primary):hover,
  .btn-primary:hover {
    background: #1a1a18;
    border-color: #1a1a18;
    color: #FFFFFF;
  }
  /* Ghost spellings → THE quiet look (transparent, ink-3, 400). */
  .cd-page .btn:where(.ghost, .btn-ghost),
  .rt-sheet .btn:where(.ghost, .btn-ghost) {
    background: transparent;
    border-color: transparent;
    color: var(--ink-3, #605D54);
    font-weight: 400;
  }
  .cd-page .btn:where(.ghost, .btn-ghost):hover,
  .rt-sheet .btn:where(.ghost, .btn-ghost):hover {
    background: var(--surface-2, #F1EFE8);
  }
  /* Legacy Bootstrap danger → the warm-paper destructive fill. */
  .btn-danger {
    background: #8E2424;
    border-color: #8E2424;
    color: #FFFFFF;
    font-weight: 600;
  }
  .btn-danger:hover { background: #7a1f1f; border-color: #7a1f1f; }

  /* ── .rt-btn — THE canonical button classes (adopt these; the net
     above is only the safety floor). Doubled selectors = (0,2,0), after
     the net, so the explicit class always wins. .rt-btn alone IS the
     secondary look. */
  .rt-btn.rt-btn,
  .rt-btn-primary.rt-btn-primary,
  .rt-btn-quiet.rt-btn-quiet,
  .rt-btn-danger.rt-btn-danger,
  .rt-btn-icon.rt-btn-icon {
    appearance: none;
    -webkit-appearance: none;
    font: inherit;
    font-family: inherit;
    font-size: 13px;
    font-weight: 500;
    line-height: 1.2;
    height: auto;
    min-height: 44px;
    min-width: 44px;
    padding: 0 14px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    background: var(--surface, #FFFFFF);
    border: 0.5px solid var(--line-2, #C7C3B5);
    border-radius: 8px;
    color: var(--ink-2, #3A3832);
    white-space: nowrap;
    text-decoration: none;
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
    transition: background 0.15s ease, border-color 0.15s ease,
                opacity 0.15s ease;
  }
  .rt-btn.rt-btn:hover,
  .rt-btn.rt-btn:active { background: var(--surface-2, #F1EFE8); }

  .rt-btn-primary.rt-btn-primary {
    background: var(--ink, #1F1E1A);
    border-color: var(--ink, #1F1E1A);
    color: #FFFFFF;
    font-weight: 600;
  }
  .rt-btn-primary.rt-btn-primary:hover,
  .rt-btn-primary.rt-btn-primary:active {
    background: #1a1a18;
    border-color: #1a1a18;
  }

  .rt-btn-quiet.rt-btn-quiet {
    background: transparent;
    border-color: transparent;
    color: var(--ink-3, #605D54);
    font-weight: 400;
  }
  .rt-btn-quiet.rt-btn-quiet:hover,
  .rt-btn-quiet.rt-btn-quiet:active {
    background: var(--surface-2, #F1EFE8);
    color: var(--ink-2, #3A3832);
  }

  /* Destructive confirms ONLY — never a merely-negative action. */
  .rt-btn-danger.rt-btn-danger {
    background: #8E2424;
    border-color: #8E2424;
    color: #FFFFFF;
    font-weight: 600;
  }
  .rt-btn-danger.rt-btn-danger:hover,
  .rt-btn-danger.rt-btn-danger:active {
    background: #7a1f1f;
    border-color: #7a1f1f;
  }

  /* Icon-only: 44×44 quiet square, 17px glyph, ink-2. REQUIRES an
     aria-label (the accessible name is otherwise empty). */
  .rt-btn-icon.rt-btn-icon {
    flex: 0 0 44px;
    width: 44px;
    min-width: 44px;
    padding: 0;
    font-size: 17px;
    background: transparent;
    border-color: transparent;
    color: var(--ink-2, #3A3832);
  }
  .rt-btn-icon.rt-btn-icon:hover,
  .rt-btn-icon.rt-btn-icon:active { background: var(--surface-2, #F1EFE8); }

  /* Shared disabled treatment (mirrors the detail page's muted chip). */
  .rt-btn.rt-btn:disabled,
  .rt-btn.rt-btn.disabled,
  .rt-btn-primary.rt-btn-primary:disabled,
  .rt-btn-primary.rt-btn-primary.disabled,
  .rt-btn-quiet.rt-btn-quiet:disabled,
  .rt-btn-danger.rt-btn-danger:disabled,
  .rt-btn-icon.rt-btn-icon:disabled {
    opacity: 0.45;
    cursor: not-allowed;
  }

  /* ── Bottom-sheet vocabulary (.rt-sheet) ─────────────────────────────
     Page agents ADD this class to an existing drawer/dialog. Positional +
     sizing properties are forced so it wins over the element's desktop
     drawer geometry; display/visibility/transform are left alone — they
     remain the page's own open/close mechanism. */
  .rt-sheet {
    position: fixed !important;
    top: auto !important;
    left: 0 !important;
    right: 0 !important;
    bottom: 0 !important;
    width: 100% !important;
    max-width: none !important;
    min-width: 0 !important;
    height: auto !important;
    max-height: 85vh !important;
    margin: 0 !important;
    border: none !important;
    border-top: 0.5px solid var(--line, #E0DDD2) !important;
    border-radius: 12px 12px 0 0 !important;
    background: var(--surface, #FFFFFF);
    box-shadow: 0 -16px 48px rgba(31, 30, 26, 0.18) !important;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    overscroll-behavior: contain;
    padding-bottom: max(12px, env(safe-area-inset-bottom, 0px));
    z-index: 1300;
    transition: transform 220ms cubic-bezier(0.2, 0, 0, 1),
                opacity   220ms cubic-bezier(0.2, 0, 0, 1);
  }
  /* Drag-handle bar at the top of every sheet. */
  .rt-sheet::before {
    content: '';
    display: block;
    width: 36px;
    height: 4px;
    margin: 8px auto 6px;
    border-radius: 999px;
    background: var(--line-2, #C7C3B5);
  }

  /* ── Sheet anatomy — ONE implementation app-wide ─────────────────────
     All parts optional, but when a sheet has them it uses THESE classes
     (page agents delete their bespoke title/footer variants):
       <aside class="rt-sheet">
         (drag handle — automatic ::before above)
         <div class="rt-sheet-title">Filter · Model
           <button class="rt-sheet-close" aria-label="Close">✕</button>
         </div>
         <div class="rt-sheet-content"> …rows… </div>
         <div class="rt-sheet-foot">
           <span class="rt-sheet-count"><strong>298</strong> matches</span>
           <span class="rt-sheet-actions">
             <button>Clear</button> <button>Cancel</button> …primary…
           </span>
         </div>
       </aside>
     One sheet at a time; only a red confirm may stack once above its
     parent sheet. Always closable: ✕ + scrim tap + Esc (page-owned). */
  .rt-sheet .rt-sheet-title {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    min-height: 44px;
    padding: 0 6px 0 16px;
    font-size: 13.5px;
    font-weight: 600;
    color: var(--ink, #1F1E1A);
  }
  .rt-sheet .rt-sheet-close {
    appearance: none;
    -webkit-appearance: none;
    flex: 0 0 44px;
    width: 44px;
    height: 44px;
    margin-left: auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: none;
    background: transparent;
    border-radius: 8px;
    font-size: 16px;
    line-height: 1;
    color: var(--ink-3, #605D54);
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
  }
  .rt-sheet .rt-sheet-close:active { background: var(--surface-2, #F1EFE8); }
  /* Content region: 16px side padding per the contract. */
  .rt-sheet .rt-sheet-content { padding: 0 16px; }
  /* Footer — THE sheet-footer contract (round 4, Part B; every sheet on
     every page uses this):
       - count/info LEFT (.rt-sheet-count), actions RIGHT
         (.rt-sheet-actions), every action ≥44px tall;
       - action ORDER inside .rt-sheet-actions: quiet/secondary first,
         PRIMARY RIGHTMOST (markup order — CSS cannot enforce it);
       - single-action FORM sheets only: the one primary button sits as
         the ONLY child of .rt-sheet-foot (or of a lone .rt-sheet-actions)
         and goes full-width via the :only-child rules below.
     Sticky so it stays visible while content scrolls. */
  .rt-sheet .rt-sheet-foot {
    position: sticky;
    bottom: 0;
    z-index: 1;                    /* paints above scrolled content */
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    margin-top: 6px;
    /* Safe-area floor matters while pinned mid-scroll (the foot sits at
       the physical screen bottom); at rest the sheet's own bottom
       padding adds a touch more air below — intended. */
    padding: 6px 16px max(2px, env(safe-area-inset-bottom, 0px));
    border-top: 0.5px solid var(--line, #E0DDD2);
    background: var(--surface, #FFFFFF);
  }
  .rt-sheet .rt-sheet-count {
    font-size: 13px;               /* token ramp — 12.5px is off-scale */
    color: var(--ink-3, #605D54);
    font-variant-numeric: tabular-nums;
  }
  .rt-sheet .rt-sheet-count strong { color: var(--ink, #1F1E1A); }
  .rt-sheet .rt-sheet-actions {
    display: inline-flex;
    align-items: center;
    gap: 8px;                      /* proximity: related actions, 8px */
    margin-left: auto;             /* actions stay RIGHT even with no count */
  }
  /* Footer actions inherit the .rt-btn metrics even before a page agent
     classes them — metrics only, looks come from the button's class. */
  .rt-sheet .rt-sheet-actions button,
  .rt-sheet .rt-sheet-actions .btn { min-height: 44px; }
  /* Type metrics at (0,0,1) via :where() — a floor for BARE <button>s
     only, so any explicit class (.rt-btn-*, page classes, the .btn net)
     outranks it instead of being re-padded by this later-in-file rule. */
  :where(.rt-sheet .rt-sheet-actions) :where(button, .btn) {
    font-size: 13px;
    line-height: 1.2;
    border-radius: 8px;
    padding: 0 14px;
  }
  /* Single-action FORM sheets: the lone primary goes full-width. */
  .rt-sheet .rt-sheet-foot > :where(button, .btn, a):only-child,
  .rt-sheet .rt-sheet-foot > .rt-sheet-actions:only-child
    > :where(button, .btn, a):only-child {
    flex: 1 1 100%;
    width: 100%;
    justify-content: center;
  }
  .rt-sheet .rt-sheet-foot > .rt-sheet-actions:only-child { flex: 1 1 100%; }

  /* ── .rt-sheet.no-anim — programmatic re-open, NO entry replay ───────
     CONTRACT NAME — page agents rely on `.no-anim` exactly. The 220ms
     entry slide plays ONLY on a user-initiated open. Whenever a page
     re-opens or re-renders a sheet programmatically (live-apply boost
     swap, SSE/facet refresh, restoring an open sheet after a DOM
     rebuild), it must suppress every transition/animation for that
     incarnation:
       sheetEl.classList.add('no-anim');
       …re-open / swap content in place…
       void sheetEl.offsetHeight;   // flush styles before re-enabling
       requestAnimationFrame(() => sheetEl.classList.remove('no-anim'));
     Scrim/backdrop fades must be suppressed by the page for the same
     incarnation (no scrim flicker on refresh). */
  .rt-sheet.no-anim,
  .rt-sheet.no-anim::before,
  .rt-sheet.no-anim * {
    transition: none !important;
    animation: none !important;
  }

  /* ── Selection action bar vocabulary (.rt-actionbar) ─────────────────
     Fixed bottom bar used by tap-to-move on Parts/Parcels: "N selected",
     destination buttons, Cancel. */
  .rt-actionbar {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 1200;
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 10px max(12px, env(safe-area-inset-right, 0px))
             calc(10px + env(safe-area-inset-bottom, 0px))
             max(12px, env(safe-area-inset-left, 0px));
    background: var(--surface, #FFFFFF);
    border-top: 0.5px solid var(--line, #E0DDD2);
    box-shadow: 0 -8px 28px rgba(31, 30, 26, 0.14);
  }
  .rt-actionbar .rt-actionbar-count {
    flex-shrink: 0;
    font-size: 13px;
    font-weight: 600;
    color: var(--ink, #1F1E1A);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
  }
  .rt-actionbar .rt-actionbar-actions {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 8px;
    flex: 1 1 auto;
    flex-wrap: wrap;
    min-width: 0;
  }
  .rt-actionbar button,
  .rt-actionbar .btn {
    min-height: 44px;
  }

  /* ── :focus-visible ring — every interactive element at ≤767px ───────
     2px var(--info) outline, 2px offset (keyboard/a11y principle 5).
     Specificity is deliberately (0,3,0): :root + :is(max arg [tabindex]
     = class weight) + :focus-visible. That OUTRANKS every page-level
     `…:focus { outline: none }` suppression at ≤(0,2,1) (e.g.
     .fin-page .dinput:focus) and, at (0,3,0) ties (e.g. .cd-page
     .cl-att-row:focus-visible's inset variant), wins by source order —
     one canonical ring everywhere. A page needing a bespoke ring must
     say so at (0,4,0)+; the default is: don't. Inside .cd-page
     var(--info) is the warm-paper #15558F; legacy pages resolve the
     :root #3b82f6 — both are AA-visible against the paper bg. */
  :root :is(a, button, input, select, textarea, summary,
            [role="button"], [tabindex]):focus-visible {
    outline: 2px solid var(--info, #15558F);
    outline-offset: 2px;
  }
  /* Bootstrap's .btn:focus-visible box-shadow glow would double up with
     the ring — same (0,2,0), this file loads later, so it drops. */
  .btn:focus-visible { box-shadow: none; }

  /* ── WCAG net — the ink-4 readable-text ban (principle 5) ────────────
     #8E8B80 (ink-4) on white is ≈2.9:1 — FAILS AA. At ≤767px every
     class this file owns keeps text at var(--ink-3) or darker; the
     legacy token lift lives in main.css (--text-secondary #718096 was
     ≈4.0:1, lifted at ≤767px). PAGE AGENTS MUST DO THE SAME in their
     own files: any TEXT set in ink-4 / #8E8B80 / #888780 / #858585 moves
     to var(--ink-3); ink-4 stays ONLY on decorative glyphs (dot
     separators, dashes, carets, disabled fills). The shell cannot reach
     those classes without seizing page files — see the round-4 handoff
     list (financials2 .h-note/.mnote/.dim2/.share…, computers-list
     .cl-money-sub.muted/.clf-*, parcels-board .section-label/.tl .meta/
     .le-meta/.cost .lab, parts-board .ctrl-lab/.ds-label/.eta/.chip-sub/
     .empty-line, dashboard .pipe-act .go/.nrow…, repairs-panel
     --rp-text-tertiary #888780, computer-detail budget/probe captions). */

  /* .muted-note — cross-page utility for state text that desktop keeps
     in quiet ink-4 (inline style): phones lift it to AA-readable ink-3.
     !important is required to outrank the inline style. */
  .muted-note { color: var(--ink-3, #605D54) !important; }

  /* ── Toast fit ───────────────────────────────────────────────────────
     Desktop centers a shrink-to-fit pill; phones stretch it edge-to-edge
     with wrapping text. (body-prefixed selectors outrank the inline
     #toast-container styles in base.html regardless of source order.) */
  body #toast-container {
    top: max(12px, env(safe-area-inset-top, 0px));
    left: 12px;
    right: 12px;
    transform: none;
    align-items: stretch;
  }
  body .toast-notification {
    max-width: 100%;
    white-space: normal;
    word-break: break-word;
  }
  /* Only danger toasts ever render (base.html filters the rest). Phones
     drop the legacy left color stripe for the app's danger tone pair —
     full hairline border + tinted surface, text in the danger ink. */
  body .toast-notification.danger {
    border-left: 0.5px solid #D9B4B4;
    border: 0.5px solid #D9B4B4;
    background: #F4DCDC;
    color: #8E2424;
  }

  /* ── Content padding — the 12px outer gutter of the shared grid ──────
     Legacy .content-wrapper pages get it here; redesigned pages own
     their body padding and MUST use the same 12px side gutter (card
     rhythm tokens: 12px between cards, 14px internal card padding — see
     the GRID & RHYTHM block in the file header). */
  .content-wrapper {
    padding: 12px;
  }
}

/* Reduced motion: kill the sheet/search transitions on phones. */
@media (max-width: 767px) and (prefers-reduced-motion: reduce) {
  .rt-sheet,
  .cd-page .cd-toolbar > .tb-search {
    transition: none !important;
  }
}
