/* Reusable UI-primitive components (buttons, pills, badges, modals, tips,
   form controls, filter popover) — extracted from 41.01-pipeline.html
   (Task 2, frontend rebuild). */
  .avatar {
    width: 30px; height: 30px; border-radius: 50%;
    background: var(--accent); color: var(--surface);
    display: flex; align-items: center; justify-content: center;
    font-size: 12px; font-weight: 700; flex-shrink: 0;
  }
  .identity-chip {
    display: flex; align-items: center; gap: 8px; font-size: 13px;
    padding: 6px 10px 6px 6px; border: 1px solid var(--border); border-radius: 999px;
    color: var(--ink-dim); background: var(--surface);
  }
  .identity-chip .avatar { width: 22px; height: 22px; font-size: 10px; }
  .identity-chip b { color: var(--ink); font-weight: 600; }
  .identity-chip button.switch {
    color: var(--accent); cursor: pointer; font-size: 12px; margin-left: 2px;
    background: none; border: none; padding: 0; font-family: inherit;
  }
  /* Theme toggle, moved from a standalone rail icon (general UX ask, not a
     mockup item — P53) and restyled as a switch. P54: sits next to the
     identity chip (.topbar-right), not nested inside it — separate but
     inline — with a bulb icon alongside the track/thumb. */
  .topbar-right { display: flex; align-items: center; gap: 10px; }
  .theme-switch {
    appearance: none; border: none; background: none; padding: 0;
    cursor: pointer; display: inline-flex; align-items: center; gap: 6px; flex-shrink: 0;
    color: var(--ink-dim);
  }
  .theme-switch-icon { display: flex; }
  :root[data-theme="light"] .theme-switch-icon { color: var(--accent); }
  .theme-switch-track {
    display: inline-block; width: 32px; height: 17px; border-radius: 999px;
    background: var(--surface-3); border: 1px solid var(--border); position: relative;
    transition: background 0.15s ease;
  }
  .theme-switch-thumb {
    position: absolute; top: 1px; left: 1px; width: 13px; height: 13px; border-radius: 50%;
    background: var(--ink-dim); transition: transform 0.15s ease, background 0.15s ease;
  }
  :root[data-theme="light"] .theme-switch-thumb { transform: translateX(15px); background: var(--accent); }
  :root[data-theme="light"] .theme-switch-track { background: var(--accent-wash); }
  .kpi {
    padding: 16px 20px; border-right: 1px solid var(--border);
    text-align: left; background: none; border-top: none; border-bottom: none; border-left: none;
    cursor: default; width: 100%;
  }
  .kpi:last-child { border-right: none; }
  .kpi.clickable { cursor: pointer; }
  .kpi.clickable:hover { background: var(--surface-2); }
  .kpi.active { background: var(--accent-wash); }
  .kpi .num { font-family: var(--mono); font-variant-numeric: tabular-nums; font-size: 25px; font-weight: 600; line-height: 1; }
  .kpi.overdue .num { color: var(--bad); }
  .kpi.at-risk .num { color: var(--warn); }
  .kpi.on-track .num { color: var(--good); }
  .kpi .lbl { margin-top: 6px; font-size: 11px; text-transform: uppercase; letter-spacing: 0.07em; color: var(--ink-dim); }
  .fselect, .fsearch {
    font-size: 13px; padding: 6px 10px; border: 1px solid var(--border); border-radius: 5px;
    background: var(--surface); color: var(--ink);
  }
  /* DP272 (P69): was flex:1 (grow to fill all remaining space) — matched
     against the mockup, the search field reads as a modest fixed-ish box,
     not a bar stretching across most of the toolbar. Shrinkable, not
     growable. */
  .fsearch { flex: 0 1 242px; min-width: 140px; }
  /* DP272 (P69): no border/background box around Mine-only — the mockup
     treats it as a bare label+toggle, same visual weight as the other
     unboxed toolbar text (legend, sort-note), not a bordered chip like
     Filter (which stays a real button, opens a popover). */
  .ftoggle {
    display: flex; align-items: center; gap: 7px; font-size: 13px; color: var(--ink-dim);
  }
  /* DP272 (P69): restyled from a native checkbox to a track/thumb toggle,
     matching the locked mockup and reusing .theme-switch-track/-thumb's
     exact dimensions (32x17 track, 13x13 thumb, translateX(15px) on).
     Same underlying <input type="checkbox" id="filterMine">, same JS
     wiring (DP48/53 role-aware default) — appearance:none + pseudo-element
     thumb is a pure CSS reskin, no behavior change. */
  .ftoggle input#filterMine {
    appearance: none; -webkit-appearance: none;
    width: 32px; height: 17px; border-radius: 999px;
    background: var(--surface-3); border: 1px solid var(--border);
    position: relative; cursor: pointer; flex-shrink: 0; margin: 0;
    transition: background 0.15s ease;
  }
  .ftoggle input#filterMine::before {
    content: ""; position: absolute; top: 1px; left: 1px;
    width: 13px; height: 13px; border-radius: 50%; background: var(--ink-dim);
    transition: transform 0.15s ease, background 0.15s ease;
  }
  .ftoggle input#filterMine:checked { background: var(--accent-wash); }
  .ftoggle input#filterMine:checked::before { transform: translateX(15px); background: var(--accent); }
  /* No margin-left:auto here — .tier-legend (right before this in DOM
     order) already claims the free space. Two separate auto margins would
     each grab a share of it, opening a visible gap between the legend and
     this note instead of grouping them flush together at the right edge
     (P69, caught live: "On track" and "Sorted by urgency" had a stray gap
     between them). */
  .sort-note { font-size: 12px; color: var(--ink-dim); white-space: nowrap; }
  .filter-popover-anchor { position: relative; }
  .filter-popover {
    position: absolute; top: calc(100% + 6px); left: 0; z-index: 20; min-width: 220px;
    background: var(--surface); border: 1px solid var(--border); border-radius: 8px;
    padding: 12px; box-shadow: 0 8px 24px rgba(0,0,0,0.18); display: flex; flex-direction: column; gap: 12px;
  }
  .filter-popover.hidden { display: none; }
  .fp-group { display: flex; flex-direction: column; gap: 6px; }
  .fp-label { font-size: 10.5px; text-transform: uppercase; letter-spacing: 0.06em; color: var(--ink-dim); }
  .fp-check { display: flex; align-items: center; gap: 7px; font-size: 13px; }
  .fp-check input { accent-color: var(--accent); }
  #filterCount { font-size: 11px; font-weight: 700; }
  .alert-pill {
    display: inline-flex; align-items: center; gap: 6px; font-size: 11px; font-weight: 700;
    text-transform: uppercase; letter-spacing: 0.04em; padding: 4px 8px 4px 6px; border-radius: 4px; white-space: nowrap;
  }
  .alert-pill .dot { width: 7px; height: 7px; border-radius: 50%; }
  .alert-pill.overdue { background: var(--bad-wash); color: var(--bad); }
  .alert-pill.overdue .dot { background: var(--bad); }
  .alert-pill.at-risk { background: var(--warn-wash); color: var(--warn); }
  .alert-pill.at-risk .dot { background: var(--warn); }
  .alert-pill.on-track { background: var(--good-wash); color: var(--good); }
  .alert-pill.on-track .dot { background: var(--good); }
  .alert-pill.not-started { background: var(--surface-2); color: var(--ink-dim); }
  .alert-pill.not-started .dot { background: var(--ink-dim); }
  /* DP195 — day-countdown ramp: constant gray box, escalating text color
     only (tiers 3/2/1). The 0/overdue tier reuses .alert-pill.overdue
     as-is (already red-wash/red) rather than a new class. */
  .alert-pill.due-d3 { background: var(--surface-2); color: var(--good); }
  .alert-pill.due-d3 .dot { background: var(--good); }
  .alert-pill.due-d2 { background: var(--surface-2); color: var(--warn); }
  .alert-pill.due-d2 .dot { background: var(--warn); }
  .alert-pill.due-d1 { background: var(--surface-2); color: var(--bad); }
  .alert-pill.due-d1 .dot { background: var(--bad); }
  /* DP196 — "Action needed" gate: wash-style like .at-risk/.overdue
     (not the gray-box ramp above), since this isn't a day-countdown. */
  .alert-pill.action-needed-red { background: var(--bad-wash); color: var(--bad); }
  .alert-pill.action-needed-red .dot { background: var(--bad); }
  /* DP265 — touched/unresolved, no date at all. Reuses the spine's own
     --accent blue (dgroup-badge--accent) rather than a new colour. */
  .alert-pill.action-needed-blue { background: var(--accent-wash); color: var(--accent); }
  .alert-pill.action-needed-blue .dot { background: var(--accent); }
  /* Stage Action's neutral item-name pills */
  .alert-pill.stage-action-name { background: var(--surface-2); color: var(--ink); }
  /* DP177 P47 fix: one pill per row, stacked vertically — was unstyled,
     letting inline-flex pills overflow the cell horizontally instead of
     wrapping. Stage Action/Status columns both use this, one child per
     `stage_action` entry, kept in index-lockstep between the two columns. */
  .stage-action-stack { display: flex; flex-direction: column; align-items: flex-start; gap: 4px; }
  /* Invisible row-height placeholder so a stage_action item with neither a
     pill nor a gate still occupies its paired row in the Status column,
     instead of collapsing the column out of alignment with Stage Action. */
  .alert-pill.stack-placeholder { visibility: hidden; }
  /* DP205 — My Week's four urgency sections (Overdue/Critical/Coming Up/
     Under control), one table each. Title styling mirrors .dsection h3's
     existing small-caps label pattern used on Screen C. */
  .myweek-section { padding: 4px 20px 20px; }
  .myweek-section-title {
    font-size: 11px; text-transform: uppercase; letter-spacing: 0.06em;
    color: var(--ink-dim); margin: 20px 0 10px; font-weight: 700;
  }
  .myweek-section-empty { font-size: 13px; color: var(--ink-dim); padding: 4px 0; }
  /* DP277 Task 5 — My Week drawer body classes. These are new class names
     introduced by 42.17-my-week-drawer.js itself (not part of the reused
     .detail-*/.dbtn shell) and had no CSS anywhere in the codebase before
     this task — the brief's "zero new CSS" note covered only the shell.
     Added here, grouped with the other myweek-* rules, so the clicked
     card is actually visibly highlighted (not just scrolled to) and the
     "Shared" note/empty state read as intentional UI, not raw text. */
  .empty-drawer { font-size: 13px; color: var(--ink-dim); padding: 4px 0; }
  .shared-note { color: var(--ink-dim); font-size: 11px; margin-top: 8px; }
  .active-target { outline: 2px solid var(--focus); outline-offset: -1px; }
  .mw-chip-row { display: flex; gap: 6px; margin: 14px 20px; flex-wrap: wrap; }
  .mw-chip {
    appearance: none; border: 1px solid var(--border); background: var(--surface);
    color: var(--ink-dim); font-family: inherit; font-size: 12.5px; font-weight: 600;
    padding: 7px 13px; border-radius: 999px; cursor: pointer;
  }
  .mw-chip:hover { background: var(--surface-2); color: var(--ink); }
  .mw-chip.active { background: var(--ink); color: var(--surface); border-color: var(--ink); }
  .mw-queue-table { min-width: 760px; }
  .mw-c-style { width: 30%; }
  .mw-c-stage { width: 16%; }
  .mw-c-supplier { width: 20%; }
  .mw-c-delivery { width: 14%; }
  .mw-c-due { width: 20%; }
  .mw-cell { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .mw-style-strong { font-weight: 600; }
  .mw-style-dim { color: var(--ink-dim); }
  .mw-ellipsis { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; display: block; }
  .mw-due-cell { white-space: nowrap; }
  /* DP197 — plain delivery countdown text, no box */
  .delivery-days { color: var(--ink-dim); font-size: 12px; font-weight: 500; }
  /* DP131 — text stays --ink-dim; the removed status colour survives as a
     leading dot instead (delivery-days has no separate dot element like
     .alert-pill .dot does, so the signal would otherwise be lost outright). */
  .delivery-days.due-yellow::before {
    content: ""; display: inline-block; width: 6px; height: 6px;
    border-radius: 50%; background: var(--warn); margin-right: 5px;
  }
  .delivery-days.due-red::before {
    content: ""; display: inline-block; width: 6px; height: 6px;
    border-radius: 50%; background: var(--bad); margin-right: 5px;
  }
  .style-id .alert-pill, .detail-title-row .alert-pill { margin-left: 6px; }
  .deleted-badge {
    display: inline-block; font-size: 10px; font-weight: 700; text-transform: uppercase;
    letter-spacing: 0.04em; color: var(--ink-dim); border: 1px solid var(--border);
    border-radius: 3px; padding: 1px 5px; margin-left: 6px;
  }
  .club-chip {
    display: inline-block; margin-left: 8px; padding: 1px 7px;
    border-radius: 999px; font-size: 10.5px; font-weight: 650;
    background: var(--surface-2); color: var(--ink-dim); border: 1px solid var(--border);
  }
  .colour-group-row { cursor: pointer; font-weight: 600; }
  .colour-group-row .dgroup-chevron {
    display: inline-block; color: var(--ink-dim);
  }
  .colour-group-row .dgroup-chevron.open { transform: rotate(90deg); }
  .colour-sub-row td:first-child { padding-left: 28px; }
  .toast {
    position: fixed; bottom: 22px; left: 50%; transform: translateX(-50%) translateY(8px);
    background: var(--ink); color: var(--surface); font-size: 13px; padding: 9px 16px;
    border-radius: 6px; box-shadow: var(--shadow); opacity: 0; pointer-events: none;
    transition: opacity 0.15s ease, transform 0.15s ease;
    z-index: 80;
  }
  .toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }
  .detail-overlay {
    position: fixed; inset: 0; background: rgba(10, 14, 12, 0.42);
    z-index: 40; opacity: 0; transition: opacity 0.15s ease;
  }
  .detail-overlay.hidden { display: none; }
  .detail-overlay.show { opacity: 1; }
  .detail-panel {
    position: fixed; top: 0; right: 0; bottom: 0; width: min(560px, 100vw);
    background: var(--surface); border-left: 1px solid var(--border);
    box-shadow: var(--shadow); z-index: 41; display: flex; flex-direction: column;
    transform: translateX(100%); transition: transform 0.18s ease;
  }
  .detail-panel.hidden { display: none; }
  .detail-panel.show { transform: translateX(0); }
  .detail-head {
    display: flex; align-items: flex-start; gap: 12px; padding: 18px 20px;
    border-bottom: 1px solid var(--border); background: var(--surface); flex-shrink: 0;
  }
  .detail-close {
    width: 28px; height: 28px; border-radius: 6px; border: 1px solid var(--border);
    background: var(--surface); color: var(--ink-dim); font-size: 16px; line-height: 1; cursor: pointer;
    display: flex; align-items: center; justify-content: center; flex-shrink: 0;
  }
  .detail-close:hover { background: var(--warn-wash); color: var(--warn); border-color: var(--warn); }
  .detail-delete {
    width: 28px; height: 28px; border-radius: 6px; border: 1px solid var(--border);
    background: var(--surface); color: var(--ink-dim); cursor: pointer;
    display: flex; align-items: center; justify-content: center; flex-shrink: 0;
  }
  .detail-delete.mode-delete:hover { background: var(--bad-wash); color: var(--bad); border-color: var(--bad); }
  .detail-delete.mode-restore:hover { background: var(--good-wash); color: var(--good); border-color: var(--good); }
  .detail-delete:disabled { opacity: 0.5; cursor: default; }
  .detail-delete.hidden { display: none; }
  .detail-actions { margin-left: auto; display: flex; gap: 8px; flex-shrink: 0; }
  .detail-status-btn {
    width: 28px; height: 28px; border-radius: 6px; border: 1px solid var(--border);
    background: var(--surface); color: var(--ink-dim); cursor: pointer;
    display: flex; align-items: center; justify-content: center; flex-shrink: 0;
  }
  .detail-status-btn:hover { background: var(--good-wash); color: var(--good); border-color: var(--good); }
  .detail-status-btn.hidden { display: none; }
  /* /v2's header lifecycle buttons (Complete/Cancel/Reopen/Delete) get the
     locked mockup's exact .btn/.btn.primary/.btn.ghost system instead of
     the rules above, which are the old `/` route's 28x28 icon-square
     styling (P53 gap — these never matched the mockup). Values copied
     verbatim from the mockup's own CSS (claude.ai/code/artifact/
     55308fd0-25fa-4ba3-a52a-4caeec1cbf76), not approximated from .dbtn —
     the base (non-primary/ghost) button is neutral (surface-2/border/ink),
     not accent-filled, and .btn.ghost keeps that neutral border/text with
     only the background dropped, unlike .dbtn.ghost's accent-colored
     border/text. Scoped to .dv-header, which only exists on /v2, so the
     old route's icon-only buttons are untouched. Duplicated rather than
     relying on cascade order against .detail-status-btn/.detail-delete
     above, which stay live for that route. */
  .dv-header .detail-status-btn,
  .dv-header .detail-delete {
    width: auto; height: auto; display: inline-block;
    appearance: none; border: 1px solid var(--border);
    background: var(--surface-2); color: var(--ink);
    font-size: 12.5px; font-weight: 600; padding: 7px 13px;
    border-radius: 7px; cursor: pointer; font-family: inherit;
    transition: filter 0.12s ease; margin-top: 0;
  }
  .dv-header .detail-status-btn:hover,
  .dv-header .detail-delete:hover { filter: brightness(1.15); }
  .dv-header .detail-status-btn.primary,
  .dv-header .detail-delete.primary {
    background: var(--accent); color: var(--accent-ink); border-color: transparent;
  }
  .dv-header .detail-status-btn.ghost,
  .dv-header .detail-delete.ghost { background: transparent; }
  /* Delete stays icon-only even on /v2 (Parthiv, P54) — square button,
     bin icon, not text-labeled like Complete/Cancel/Reopen. Overrides the
     text-button rule above back to the old route's 28x28 icon-square
     shape (higher specificity by winning the .icon-square modifier). */
  .dv-header .detail-delete.icon-square {
    width: 32px; height: 32px; padding: 0; display: flex;
    align-items: center; justify-content: center;
    background: var(--surface-2); color: var(--ink-dim); border-color: var(--border);
  }
  .dv-header .detail-delete.icon-square:hover {
    background: var(--bad-wash); color: var(--bad); border-color: var(--bad); filter: none;
  }
  .detail-title-row { display: flex; align-items: center; gap: 4px; }
  .detail-style { font-family: var(--mono); font-weight: 700; font-size: 16px; }
  .detail-sub { color: var(--ink-dim); font-size: 12.5px; margin-top: 3px; }
  .detail-body { flex: 1; overflow-y: auto; padding: 4px 20px 28px; }
  .detail-loading, .detail-error { color: var(--ink-dim); font-size: 13px; padding: 24px 0; }
  .detail-footer {
    flex-shrink: 0; padding: 12px 20px; border-top: 1px solid var(--border);
    background: var(--surface); display: flex; align-items: center; justify-content: space-between; gap: 10px;
  }
  .detail-footer.hidden { display: none; }
  .detail-footer-status { font-size: 12px; color: var(--bad); }
  .detail-footer-actions { display: flex; align-items: center; gap: 10px; }
  /* Was only reset on #stickySave, not #stickyCancel — .dbtn's base
     margin-top:8px left Cancel sitting lower than Save within this flex
     row's align-items:center (P53 manual-verification alignment bug). */
  .detail-footer-actions .dbtn { margin-top: 0; }
  .dcard-head .status-badge {
    font-size: 10.5px; text-transform: uppercase; letter-spacing: 0.03em; font-weight: 700;
    padding: 3px 7px; border-radius: 4px; background: var(--accent-wash); color: var(--accent); white-space: nowrap;
  }
  .triage-input { width: 160px; font-size: 13px; padding: 4px 7px;
                  border: 1px solid var(--border); border-radius: 5px;
                  background: var(--surface); color: var(--ink); }
  .dgroup-badge {
    display: inline-flex; align-items: center; gap: 4px; font-size: 10.5px; font-weight: 700;
    text-transform: uppercase; letter-spacing: 0.03em; padding: 3px 8px; border-radius: 4px;
    white-space: nowrap;
  }
  .dgroup-badge--accent { background: var(--accent-wash); color: var(--accent); }
  .dgroup-badge--warn { background: var(--warn-wash); color: var(--warn); }
  .dgroup-badge--good { background: var(--good-wash); color: var(--good); }
  /* DP131 is superseded (spec §2) — `.dgroup-badge--*` sets `color:` directly
     per tier above, so ICON_LOCK/ICON_CHECK (see 42.04-core.js, which render
     via stroke/fill="currentColor") automatically inherit the matching tier
     colour without needing this dot as their colour carrier. The dot below
     is now a secondary reinforcing cue, not the sole source of the signal. */
  .dgroup-badge--warn::before {
    content: ""; display: inline-block; width: 6px; height: 6px;
    border-radius: 50%; background: var(--warn); margin-right: 5px;
  }
  .dgroup-badge--good::before {
    content: ""; display: inline-block; width: 6px; height: 6px;
    border-radius: 50%; background: var(--good); margin-right: 5px;
  }
  .packaging-checkbox-row { display: flex; align-items: center; gap: 8px; font-size: 13.5px; cursor: pointer; }
  .packaging-checkbox-row input { accent-color: var(--accent); }
  .tip {
    position: fixed; z-index: 90; background: var(--ink); color: var(--surface);
    font-size: 11px; line-height: 1.4; padding: 6px 10px; border-radius: 5px;
    max-width: 260px; white-space: normal; pointer-events: none; opacity: 0;
    transition: opacity 0.1s ease; box-shadow: var(--shadow);
  }
  .tip.show { opacity: 1; }
  .tip::after {
    content: ""; position: absolute; top: 100%; left: var(--arrow-x, 50%);
    transform: translateX(-50%); border: 5px solid transparent; border-top-color: var(--ink);
  }
  .tip.flip::after {
    top: auto; bottom: 100%; border-top-color: transparent; border-bottom-color: var(--ink);
  }
  .dform-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
  .dform-grid label { display: flex; flex-direction: column; gap: 3px; font-size: 11px; color: var(--ink-dim); }
  .dform-grid input, .dform-grid select, .dform-grid textarea {
    font-size: 13px; padding: 6px 8px; border: 1px solid var(--border); border-radius: 5px;
    background: var(--surface); color: var(--ink); font-family: inherit;
    width: 100%; box-sizing: border-box; height: 32px;
  }
  /* Native <select> chrome (esp. on macOS/Safari) reserves its own internal
     width/padding independent of box-sizing, so it never quite matches a
     same-width text/date input — strip it and draw a theme-aware chevron with
     a pure-CSS gradient instead of an <select>/input pixel mismatch. */
  .dform-grid select {
    appearance: none; -webkit-appearance: none; -moz-appearance: none;
    padding-right: 26px;
    background-image:
      linear-gradient(45deg, transparent 50%, var(--ink-dim) 50%),
      linear-gradient(135deg, var(--ink-dim) 50%, transparent 50%);
    background-position: right 15px center, right 10px center;
    background-size: 5px 5px, 5px 5px;
    background-repeat: no-repeat;
  }
  .dform-grid textarea { resize: vertical; min-height: 32px; }
  .dform-grid .full { grid-column: 1 / -1; }
  /* Compulsory-but-empty date field, live per the current Status selection
     (wireRequiredDateHighlights, 42.09-stage-cards.js) — green text/box
     instead of the old eager pop-up-on-select; a blocking pop-up (via the
     server's 400 + stickySave's existing failure toast) only shows if
     Save is actually attempted while still empty (Parthiv, P54). */
  label.field-required-empty { color: var(--good); }
  input.field-required-empty { border-color: var(--good); }
  .icon-btn {
    width: 22px; height: 22px; border-radius: 5px; border: 1px solid var(--border);
    background: var(--surface); color: var(--ink-dim); cursor: pointer; padding: 0;
    display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0;
  }
  .icon-btn:hover { background: var(--warn-wash); color: var(--warn); border-color: var(--warn); }
  .icon-btn.icon-cancel { background: var(--bad-wash); color: var(--bad); border-color: var(--bad); }
  .icon-btn.icon-cancel:hover { background: var(--bad); color: var(--surface); }
  .icon-btn:disabled { opacity: 0.5; cursor: default; }
  .inline-edit-group-head { display: flex; justify-content: flex-end; margin: -4px 0 4px; }
  .dbtn {
    font-size: 12.5px; font-weight: 600; padding: 6px 12px; border-radius: 5px;
    border: 1px solid var(--accent); background: var(--accent); color: var(--surface);
    cursor: pointer; margin-top: 8px;
  }
  .dbtn:hover { filter: brightness(1.15); }
  .dbtn.ghost { background: none; color: var(--accent); }
  .dbtn.ghost:hover { background: var(--accent-wash); }
  .dbtn:disabled { opacity: 0.5; cursor: default; }
  /* Lifecycle picker's Go back/Confirm (Parthiv, P54) — replaces the dead
     .dbtn-primary modifier (never had a CSS rule, so Confirm previously
     rendered identically to Go back). */
  .dbtn.danger { background: var(--bad); border-color: var(--bad); }
  .dbtn.good { background: var(--good); border-color: var(--good); }
  .po-chip-entry { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 8px; margin-bottom: 8px; }
  /* Qty-per-colour mode (P76, 2+ colours) — column count varies with colour
     count, so this wraps instead of a fixed 3-column grid. */
  .po-chip-entry.po-chip-entry-multi { display: flex; flex-wrap: wrap; }
  .po-chip-entry.po-chip-entry-multi label { flex: 1 1 100px; }
  .po-chip-entry label { display: flex; flex-direction: column; gap: 3px; font-size: 11px; color: var(--ink-dim); }
  .po-chip-entry input {
    font: inherit; font-size: 13px; color: var(--ink);
    background: var(--surface-2); border: 1px solid var(--border); border-radius: 6px; padding: 6px 8px;
  }
  .po-chip-list { display: flex; flex-wrap: wrap; gap: 6px; margin: 10px 0; }
  .po-chip {
    display: inline-flex; align-items: center; gap: 6px; font-size: 12px;
    background: var(--accent-wash); color: var(--accent); border-radius: 20px; padding: 4px 10px;
  }
  .po-chip-x { cursor: pointer; font-weight: 600; }
  .po-chip-x:hover { color: var(--bad); }
  .add-po-total { font-size: 12.5px; color: var(--ink-dim); font-variant-numeric: tabular-nums; }
  /* Colour chips (creation wizard step 1, P76) — same tag styling as
     .po-chip, entered via the same type-then-commit mechanic as Add PO.
     Input+button sit in the Colour label's own grid cell; committed tags
     render in the adjacent cell (where Pantone used to sit) via a plain
     .po-chip-list sibling, keeping Buyer/Supplier on their own row below. */
  .colour-input-row { display: flex; align-items: center; gap: 6px; }
  .colour-input-row input { flex: 1 1 auto; min-width: 0; }
  .colour-help {
    display: inline-flex; align-items: center; justify-content: center;
    width: 15px; height: 15px; border-radius: 50%; font-size: 10px; font-weight: 700;
    border: 1px solid var(--ink-dim); color: var(--ink-dim); cursor: help; margin-left: 4px;
  }
  .colour-help:hover, .colour-help:focus { border-color: var(--accent); color: var(--accent); outline: none; }
  /* Order Details' read-only list of already-recorded POs — distinct from
     .po-chip-* above (that's the Add PO entry widget). Follows the same
     dashed-row-separator convention as .dext-row/.dlog-row. */
  .po-list { display: flex; flex-direction: column; gap: 2px; }
  .po-list-row {
    font-size: 12.5px; padding: 7px 0; border-bottom: 1px dashed var(--border);
  }
  .po-list-row:last-child { border-bottom: none; }
  .gate-modal {
    position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);
    width: min(420px, 92vw); background: var(--surface); border: 1px solid var(--border);
    border-radius: 10px; box-shadow: var(--shadow); z-index: 60; padding: 20px;
  }
  .gate-modal.hidden { display: none; }
  .create-modal { width: min(600px, 94vw); padding: 0; overflow: visible; }
  .modal-close-x {
    position: absolute; top: 10px; right: 12px; background: none; border: none;
    font-size: 24px; line-height: 1; color: var(--ink-dim); cursor: pointer; z-index: 2; padding: 2px 6px;
  }
  .modal-close-x:hover { color: var(--ink); }
  .create-modal-body { max-height: 86vh; overflow-y: auto; padding: 20px; }
  .create-modal h3 { margin: 0 0 4px; font-size: 15px; padding-right: 24px; }
  .create-modal .dform-grid { margin-bottom: 4px; }
  .quality-wrap { display: flex; gap: 4px; }
  .quality-wrap input { flex: 1; min-width: 0; }
  .quality-wrap select { flex: 0 0 62px; }
  .create-modal-body .hidden { display: none; }
  .wizard-sticky-head {
    position: sticky; top: -20px; margin: -20px -20px 0; padding: 20px 20px 8px;
    background: var(--surface); z-index: 1;
  }
  .wizard-sticky-footer {
    position: sticky; bottom: -20px; margin: 16px -20px -20px;
    padding: 12px 20px; background: var(--surface); border-top: 1px solid var(--border); z-index: 1;
  }
  .money-row { display: flex; gap: 8px; align-items: end; margin: 4px 0 14px; }
  /* Addendum §2 (DP221): ownership-403 banner + page-wide read-only state,
     `/v2`-only (#dvOwnerBanner/#styleDetailView only exist on Task 4's
     markup). `.dv-readonly` disables real write-triggering controls only —
     `.ghost` buttons (Cancel/Back) and inputs outside a write flow stay
     interactive; the server (`_require_style_write_access`) remains the
     actual security boundary, this is just a UX guard against confusing
     failed-request attempts. */
  .dv-owner-banner {
    background: var(--warn-wash); color: var(--ink); padding: 10px 24px;
    font-size: 13px; border-bottom: 1px solid var(--border);
    display: flex; align-items: center; gap: 8px;
  }
  /* Icon carrying colour is DP131-compliant — the rule targets status
     *text*, not icons/dots (explicitly exempted). Background/text stay the
     same non-alarm --warn-wash/--ink DP221 already chose. */
  .dv-owner-banner-icon { color: var(--warn); flex-shrink: 0; display: flex; }
  .dv-owner-banner-icon svg { width: 15px; height: 15px; }
  .dv-readonly .dbtn:not(.ghost),
  .dv-readonly .detail-status-btn,
  .dv-readonly .detail-delete,
  .dv-readonly .icon-btn:not(.icon-cancel),
  .dv-readonly input, .dv-readonly select, .dv-readonly textarea {
    pointer-events: none; opacity: 0.55;
  }
  .money-row label {
    display: flex; flex-direction: column; gap: 3px; font-size: 11px; color: var(--ink-dim); flex: 1 1 0;
  }
  .money-row label.money-currency { flex: 0 0 64px; }
  .money-row input, .money-row select {
    font-size: 13px; padding: 0 9px; border: 1px solid var(--border); border-radius: 5px;
    background: var(--surface); color: var(--ink); width: 100%; box-sizing: border-box;
    height: 34px; line-height: 32px;
  }
  .domain-input-wrap { display: flex; align-items: center; gap: 0; }
  .domain-at {
    padding: 7px 0 7px 9px; border: 1px solid var(--border); border-right: none;
    border-radius: 5px 0 0 5px; background: var(--surface-2); color: var(--ink-dim); font-size: 13px;
  }
  .domain-input-wrap input {
    flex: 1; font-size: 13px; padding: 7px 9px; border: 1px solid var(--border); border-radius: 0 5px 5px 0;
    background: var(--surface); color: var(--ink);
  }
  .gate-modal h3 { margin: 0 0 8px; font-size: 15px; }
  .gate-modal p { margin: 0 0 14px; font-size: 13.5px; }
  .gate-modal label { display: flex; flex-direction: column; gap: 4px; font-size: 12px; color: var(--ink-dim); margin-bottom: 14px; }
  .gate-modal input { font-size: 13px; padding: 7px 9px; border: 1px solid var(--border); border-radius: 5px; background: var(--surface); color: var(--ink); }
  .gate-actions { display: flex; justify-content: flex-end; gap: 8px; }
  .dv-club-banner {
    margin-top: 12px; padding: 10px 20px;
    background: var(--surface-2); border-bottom: 1px solid var(--border);
    display: flex; align-items: center; gap: 16px; flex-wrap: wrap;
    font-size: 12.5px;
  }
  .dv-club-banner-label { color: var(--accent); font-weight: 650; }
  .dv-club-banner-po { color: var(--ink); }
  .dv-club-banner-date { color: var(--ink-faint); }
  /* delivery_alert_tier (DP197's existing yellow/red thresholds, P54). */
  .dv-club-banner-date--yellow { color: var(--warn); font-weight: 600; }
  .dv-club-banner-date--red { color: var(--bad); font-weight: 600; }
  .dv-club-banner-note { margin-left: auto; color: var(--ink-faint); font-size: 11.5px; }
  .dv-lifecycle-veil {
    position: fixed; inset: 0; background: rgba(6,7,9,0.6);
    display: flex; align-items: center; justify-content: center; z-index: 50;
  }
  .dv-lifecycle-modal {
    background: var(--surface); border: 1px solid var(--border);
    border-radius: 12px; padding: 22px 24px; width: 320px;
    box-shadow: var(--shadow);
  }
  .dv-lifecycle-check { display: flex; align-items: center; gap: 9px; padding: 7px 0; font-size: 13px; color: var(--ink); }
  .dv-lifecycle-actions { display: flex; gap: 8px; margin-top: 16px; }
  .dv-lifecycle-actions .dbtn { flex: 1; text-align: center; }
  .dv-po-tabs {
    display: flex; gap: 2px; border-bottom: 1px solid var(--border);
    margin-bottom: 14px; overflow-x: auto;
  }
  .dv-po-tab {
    appearance: none; border: none; background: transparent;
    padding: 8px 15px; color: var(--ink-faint); font-size: 12.5px;
    border-bottom: 2px solid transparent; cursor: pointer; white-space: nowrap;
    font-weight: 600; font-family: inherit;
    display: flex; align-items: center; gap: 6px;
  }
  .dv-po-tab.active { color: var(--ink); border-bottom-color: var(--accent); }
  .dv-po-tab:hover:not(.active) { color: var(--ink-dim); }
  /* Alert-status dot per tab (addendum §3.6, P54) — mirrors the mockup's
     .po-tab .dot, colour-mapped the same way .alert-pill's dot is
     (due-d3/d2/d1/overdue -> good/warn/bad). No modifier = no live due
     signal for this stage on this PO (resolved/not started), rendered
     ink-faint rather than omitted so every tab still reads as "checked". */
  .dv-po-tab-dot {
    width: 6px; height: 6px; border-radius: 50%; flex-shrink: 0;
    background: var(--ink-faint);
  }
  .dv-po-tab-dot--good { background: var(--good); }
  .dv-po-tab-dot--warn { background: var(--warn); }
  .dv-po-tab-dot--bad { background: var(--bad); }
  .dv-po-panel.hidden { display: none; }

/* DP272 — Manifest edge-light. Flat rectangles only: no box-shadow, no
   border-radius, anywhere in this block — that was built, iterated on,
   and explicitly rejected as the final design call. Don't reintroduce it
   "for polish". */

.style-cell {
  position: relative; padding-left: 0 !important;
}
/* --edge-inset: vertical margin between the edge/gutter bar and the row's
   own top/bottom border — the bar is SHORTER than the row, not edge-to-
   edge (P69, corrected against a pixel measurement of the locked mockup
   screenshot: a consistent ~15.75% gap / 68.5% bar / 15.75% gap on every
   row, hover or not — 14px is the equivalent fixed inset for this app's
   own row heights, chosen as fixed rather than percentage so it stays
   visually consistent across rows of different heights, e.g. multi-line
   aggregated delivery dates). Both .mf-edge and .mf-gutter reference the
   same custom property so they can never drift apart vertically. */
.mf-edge {
  position: absolute; left: 0; top: var(--edge-inset, 14px); bottom: var(--edge-inset, 14px);
  width: 4px; background: transparent; z-index: 1;
}
.mf-edge-red { background: var(--bad); }
.mf-edge-yellow { background: var(--warn); }
.mf-edge-blue { background: var(--accent); }
.mf-edge-green { background: var(--good); }
/* Single-position color stops only (never the "<color> <pos> <pos>" double-
   position shorthand) — that shorthand is CSS Images Level 4 and silently
   invalid-and-dropped on some Safari versions, which fails the WHOLE
   background declaration (not just the gradient), leaving .mf-edge
   transparent and exposing whatever the gutter's first (red) pill paints
   underneath via its deliberate 1px overlap — rendered as solid red, easy
   to mistake for "split isn't computing," when the JS/data were correct
   the whole time (P69, caught in Safari after Chromium-only testing
   showed it working). Six single-position stops produce the identical
   hard-seamed red-top/yellow-bottom result in every browser. */
.mf-edge-split {
  background: linear-gradient(to bottom,
    var(--bad) 0%, var(--bad) calc(50% - 1px),
    var(--surface) calc(50% - 1px), var(--surface) calc(50% + 1px),
    var(--warn) calc(50% + 1px), var(--warn) 100%);
}

/* Gutter is wider than .mf-edge (up to 49px for 3 pills, vs. edge's 4px)
   and its left:-1px only ever meant "align seamlessly with the row's left
   boundary" — but since gutter is so much wider than edge, it doesn't
   just nick 1px off edge, it sits on top of and completely blankets the
   whole 4px edge underneath (gutter's first pill is a SINGLE solid
   colour spanning gutter's full height, unlike edge's own possibly-split
   colour). At rest, .mf-style-text's cover only starts at left:4px
   (deliberately, so .mf-edge itself stays visible — see .mf-style-clip's
   own comment) — it can't also hide the sliver of gutter that overlaps
   into edge's 0-4px zone, so that sliver painted on top of edge,
   uncovered, always: a split edge's yellow half never showed, because
   the first (red) pill was sitting on top of the ENTIRE edge, not just
   overlapping into the seam (P69, confirmed by hiding gutter entirely
   and watching the edge's true split render correctly on its own).
   Fix: .mf-edge outranks .mf-gutter at rest (z-index 1 vs 0) so edge's
   own colour wins in their shared region; only on row hover does gutter
   rise above edge (z-index 2, below) so the revealed pills read as one
   continuous block with the edge, matching the locked mockup's hover
   state. */
.mf-gutter {
  position: absolute; left: -1px; top: var(--edge-inset, 14px); bottom: var(--edge-inset, 14px);
  width: var(--pw, 0px); display: flex; z-index: 0; pointer-events: none;
}
.styles-table tbody tr:hover .mf-gutter { z-index: 2; }
.mf-pill {
  width: 17px; display: flex; align-items: center; justify-content: center;
  writing-mode: vertical-rl; transform: rotate(180deg);
  font-family: var(--mono); font-size: 9.5px; font-weight: 600; letter-spacing: .03em;
  color: var(--accent-ink);
}
.mf-pill:not(:first-child) { margin-left: -1px; }
.mf-pill-red { background: var(--bad); }
.mf-pill-yellow { background: var(--warn); }
.mf-pill-blue { background: var(--accent); }

/* position:absolute + left:4px (not inset:0) is deliberate on both counts:
   (1) a normal-flow, height:100% block never actually reaches full row
   height — table cells vertically CENTER normal-flow content shorter than
   the row (vertical-align:middle on .styles-table td), so a height:100%
   child that resolves to its own auto content height gets centered inside
   the row instead of stretching to fill it, leaving the .mf-gutter's pill
   colours exposed above and below at rest on every row, always — not just
   on hover (P69 bug, caught via live screenshot against the locked
   mockup). Absolute positioning with an explicit top/bottom gives this
   element a definite height so it actually fills the row.
   (2) left:4px (not left:0) leaves .mf-edge's own 4px permanently
   uncovered — the edge is meant to be visible AT ALL TIMES (that's the
   entire point of "edge-light" as a glance-level cue), not hidden behind
   the same text cover that hides the gutter's pills. Only the gutter
   (which starts at left:-1px, overlapping into the edge) needs hiding. */
.mf-style-clip {
  position: absolute; left: 4px; top: 0; right: 0; bottom: 0; overflow: hidden;
}
/* NOT background-color: inherit — inherit only pulls from the direct
   parent (.mf-style-clip, which has no background of its own), so the
   chain silently resolves to transparent and the "hidden" pill isn't
   actually hidden. Every state below sets an explicit colour. */
.mf-style-text {
  position: relative; z-index: 1; background-color: var(--surface);
  padding-left: 14px; height: 100%; display: flex; align-items: center; gap: 6px;
  transition: transform .16s cubic-bezier(.2,.7,.3,1), background-color .1s ease;
}
.mf-style-text.mf-indent { padding-left: 34px; }
.mf-style-lines { display: flex; flex-direction: column; justify-content: center; min-width: 0; }
.styles-table tbody tr:hover .mf-style-text {
  transform: translateX(var(--gw, 0px)); background-color: var(--surface-2);
}
/* Clubbed header row's .mf-style-text intentionally has NO override here
   — it falls through to the plain rules above (var(--surface) at rest,
   var(--surface-2) on hover), identical to any plain single-colour row.
   Only expanded sub-rows get their own shade, matching the row-level
   var(--bg)/var(--surface) treatment in views.css. Needs the
   .styles-table tbody prefix to beat that same file's generic hover rule
   on specificity (equal class-tier, generic wins the type-tier tiebreak
   otherwise — bit us at P70 when this override was on colour-group-row
   instead and silently never applied). */
.styles-table tbody tr.colour-sub-row .mf-style-text { background-color: var(--bg); }
.styles-table tbody tr.colour-sub-row:hover .mf-style-text { background-color: var(--surface); }

/* Chevron lives INSIDE .mf-style-text (built that way in Task 3, not as a
   sibling positioned outside it) so it moves WITH the sliding text on
   hover — the reverse (chevron pinned separately) breaks the "hidden at
   rest" behaviour specifically on club-header rows, because it shifts
   where the opaque cover actually starts relative to where the gutter
   sits. See the plan's Known Trap 3. */
/* Plain ">" rotated to a chevron rather than a Unicode caret glyph — only
   specific IBM Plex weight subsets are self-hosted (DP176), so an
   uncommon chevron character risks an inconsistent system-font fallback;
   ">" is guaranteed present in any subset carrying ASCII. rotate(90deg)
   at rest points it down (collapsed, prompting expand); club-open adds
   another 90deg to point up (expanded, prompting collapse). */
.mf-chev {
  font-size: 16px; line-height: 1; color: var(--ink-dim); flex: none;
  font-weight: 600; display: inline-block; transform: rotate(90deg);
  transition: transform .15s ease;
}
tr.club-open .mf-chev { transform: rotate(270deg); }
/* Aggregate signal steps back once the individual colours are showing
   their own — otherwise the header's worst-case edge is just noise
   competing with the real per-colour ones directly below it. */
tr.club-open .mf-edge { background: transparent; }
tr.club-open .mf-gutter { display: none; }

/* Toolbar legend (spec §7) — small vertical lines, not dots, matching the
   edge-light's own visual language. margin-left:auto pushes this AND
   .sort-note after it (which must NOT have its own margin-left:auto — two
   separate auto margins each grab a share of the free space, opening a
   visible gap between them instead of grouping them, caught live P69) as
   one right-aligned group stuck to the toolbar's right edge. #newStyleBtn
   (P69, reverted after Parthiv's follow-up) stays clustered with search
   on the left, not part of this group. */
.tier-legend { display: flex; align-items: center; gap: 12px; font-size: 11.5px; color: var(--ink-faint); margin-left: auto; }
.tl-item { display: inline-flex; align-items: center; gap: 5px; white-space: nowrap; }
.tl-dot { width: 3px; height: 13px; flex: none; }
.tl-red { background: var(--bad); }
.tl-yellow { background: var(--warn); }
.tl-blue { background: var(--accent); }
.tl-green { background: var(--good); }

.open-activities-cell { text-align: right; }
.oa-count { font-family: var(--mono); font-variant-numeric: tabular-nums; font-size: 14px; font-weight: 600; }
.oa-critical { font-size: 11px; margin-top: 2px; font-family: var(--mono); }
.oa-critical.has { color: var(--bad); }
.oa-critical.none { color: var(--ink-faint); }
