/* ========== canonical source — ships WHOLE ==========
   `tools/sync_brand_pack.py` copies this file byte-for-byte into the app
   as `assets/brand/01-brand-components.css`. Nothing is stripped. An earlier
   header described a two-tier system (marker comments that the sync stripped
   before shipping); that machinery never existed and was removed 2026-07-08
   (audit UX 360, finding D2). If a reference-only tier is ever needed,
   reintroduce it with real markers and a test.

   BRAND.md §0 still draws a DOCUMENTATION line between classes Atlas applies
   verbatim (use the class name) and design-reference patterns Atlas implements
   via Dash native widgets (dbc.Button, dbc.Input, .empty-state, .atlas-kpi-skel,
   the parallel .filter-pill system, etc.). That line is a hint to CODE, not an
   enforced boundary — the whole file ships either way.

   CRITICAL — CSS does NOT support nested comments: the first inner
   slash-star-close closes the outer comment and everything after it parses as
   CSS until the next slash-star-open. A May 2026 regression this way silently
   stripped properties from .status-pill and other rules. Never write literal
   slash-star delimiters inside a comment body — tokens/generate.py validates
   against this before every regen.
   ==================================================== */

/* ==========================================================================
   Markwins Atlas — shared component classes
   --------------------------------------------------------------------------
   The canonical, app-facing component CSS. Both the Atlas app and the brand
   book import this file. Reuse these class names — do NOT reinvent bespoke
   ones (see RULES.md anti-patterns).

   Depends on tokens/brand-tokens.css for all var(--*) values.
   Load order:  brand-tokens.css  ->  brand-components.css
   ========================================================================== */

/* ============ BUTTONS ============ */
/* Promoted T2 -> T1 (#A1 admin panel / issue #107): the admin page is the
   first Atlas page to use the canonical button family directly. Existing
   dbc.Button usages render as Bootstrap buttons that inherit our
   .btn-primary / .btn-secondary / etc. as the later-loaded rule, so the
   promotion strengthens brand consistency rather than regressing it. */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  font-family: var(--font-sans); font-weight: 600; font-size: 14px;
  line-height: 1; padding: 10px 16px; border-radius: var(--radius-md);
  border: 1px solid transparent; cursor: pointer;
  transition: background var(--motion-base) var(--motion-ease-out), border-color var(--motion-base), transform 100ms var(--motion-ease-out);
  white-space: nowrap; text-decoration: none;
}
/* Press feedback (audit-w1 PR-4). */
.btn:active { transform: scale(0.97); }
.btn:focus-visible {
  outline: 3px solid color-mix(in srgb, var(--pink) 50%, transparent);
  outline-offset: 2px;
}
.btn .ic { font-size: 14px; line-height: 1; }

/* Sizes */
.btn-sm { padding: 7px 12px; font-size: 13px; border-radius: var(--radius-sm); }
.btn-md { padding: 10px 16px; font-size: 14px; }
.btn-lg { padding: 14px 22px; font-size: 15px; border-radius: 10px; }

/* Primary — brand pink. Routine confirm / submit. */
.btn-primary { background: var(--pink); color: white; }
.btn-primary:hover { background: var(--pink-deep); }
.btn-primary:active { background: #B82E6A; }

/* Secondary — neutral. */
.btn-secondary { background: white; color: var(--ink); border-color: var(--hairline); }
.btn-secondary:hover { background: var(--surface); border-color: var(--pink-light); }
.btn-secondary:active { background: var(--pink-light); }

/* Ghost — Reset / Refresh / Cancel chrome controls (Rule 02). */
.btn-ghost { background: transparent; color: var(--pink-deep); }
.btn-ghost:hover { background: var(--surface); }

/* Destructive / terminal cherry action — Save / Apply / Send / delete-confirm
   (Rule 02). ONE per screen. NOTE: Export is NOT cherry — see .btn-export. */
.btn-destructive { background: var(--cherry); color: white; }
.btn-destructive:hover { background: var(--cherry-deep); }

/* Export — NOT a destructive/final-commit action, so never cherry (Rule 02).
   Ghost/secondary tone; the leading ↓ download icon is allowed. */
.btn-export { background: white; color: var(--ink-2); border-color: var(--hairline); }
.btn-export:hover { background: var(--pink-tint); border-color: var(--pink-light); }
.btn-export:active { background: var(--pink-tint); }

/* States */
.btn-disabled, .btn[disabled] {
  background: var(--hairline); color: var(--ink-3); cursor: not-allowed;
  border-color: transparent;
}
.btn-loading { pointer-events: none; opacity: 0.85; position: relative; color: transparent !important; }
.btn-loading::after {
  content: ''; position: absolute; width: 14px; height: 14px;
  border: 2px solid currentColor; border-right-color: transparent;
  border-radius: 50%; animation: spin var(--motion-spin) linear infinite; color: white;
}
.btn-secondary.btn-loading::after { color: var(--pink-deep); }
@keyframes spin { to { transform: rotate(360deg); } }

/* Keyboard focus ring — interactive elements that are not native buttons
   (chips, segmented, paginator, popover rows, icon buttons, clickable table
   rows, grid columns) get a visible ring on :focus-visible only (a11y AA,
   audit-w1 PR-3). :where() keeps specificity 0 so component rules still win. */
:where(.fb-chip, .segmented button, .paginator-btn, .popover .pb-row,
       .ic-btn, .kpi-table-clickable tbody tr, .atlas-grid .ag-col):focus-visible {
  outline: 2px solid var(--pink-deep); outline-offset: 2px;
}

/* ============ INPUTS ============ */
.field { display: flex; flex-direction: column; gap: 6px; min-width: 240px; }
.field label {
  font-family: var(--font-mono-heavy); font-weight: 700;
  font-size: 10px; letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--ink-2);
}
.field .help { font-size: 12px; color: var(--ink-3); }
.field.error .help { color: var(--error); }

.input, .select, .textarea {
  font-family: var(--font-sans); font-size: 14px;
  padding: 10px 12px; border-radius: var(--radius-sm);
  border: 1px solid var(--hairline); background: white; color: var(--ink);
  transition: border-color var(--motion-base), box-shadow var(--motion-base);
}
.input:hover, .select:hover, .textarea:hover { border-color: var(--pink-light); }
.input:focus, .select:focus, .textarea:focus {
  outline: none; border-color: var(--pink);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--pink) 25%, transparent);
}
.input:disabled { background: var(--surface); color: var(--ink-3); cursor: not-allowed; }
.field.error .input { border-color: var(--error); }
.field.error .input:focus { box-shadow: 0 0 0 3px color-mix(in srgb, var(--error) 25%, transparent); }

/* Hairline-bordered box holding an optional inline magnifier icon + a
   chromeless input. Flex model (field-tested in-app; supersedes the old
   absolute-icon / padding-left model, audit Fase 2 shadowed DEC1 — app
   winner). Width via min-width or a host wrapper. */
.search-wrap {
  display: inline-flex; align-items: center; gap: 8px;
  height: 32px; padding: 0 12px; min-width: 210px;
  border: 1px solid var(--hairline); border-radius: var(--radius-md);
  background: var(--paper);
  transition: border-color var(--motion-base);
}
.search-wrap:focus-within { border-color: var(--pink-light); }
.search-wrap svg, .search-wrap .search-icon { color: var(--ink-3); flex: 0 0 14px; }
.search-wrap .input, .search-wrap input {
  border: none !important; outline: none !important; background: transparent !important;
  font-family: var(--font-sans); font-size: 13px; color: var(--ink);
  width: 100%; padding: 0 !important;
}
.search-wrap .input::placeholder, .search-wrap input::placeholder { color: var(--ink-3-text); }

.segmented { display: inline-flex; background: var(--surface); border-radius: var(--radius-md); padding: 3px; gap: 2px; }
.segmented button {
  font-family: var(--font-sans); font-weight: 500; font-size: 13px;
  padding: 7px 14px; border: 0; background: transparent; color: var(--ink-2);
  border-radius: var(--radius-sm); cursor: pointer;
}
.segmented button.is-active { background: white; color: var(--ink); box-shadow: 0 1px 2px rgba(10,10,15,0.08); }

/* Dropdown segment (TODO_COWORK B4 — was Atlas .tab-toolbar). One segment can
   be a dropdown trigger instead of a toggle: it carries a selected VALUE, so
   its text is --ink (not --ink-2), with a trailing caret in --ink-3. Opens a
   canonical .popover — no bespoke menu CSS. .is-open hook is for the
   open/highlighted state if Atlas has one (CODE to confirm). */
.segmented button.seg-drop { color: var(--ink); display: inline-flex; align-items: center; gap: 6px; }
.segmented button.seg-drop .caret { font-size: 10px; color: var(--ink-3); }
.segmented button.seg-drop.is-open { background: white; box-shadow: 0 1px 2px rgba(10,10,15,0.08); }

/* Small segmented (Style round · B8) — quieter size for a SECONDARY toggle
   (e.g. a chart range selector below the chart, lighter than the
   granularity / view toggles above it). */
.segmented--sm { padding: 2px; }
.segmented--sm button { font-size: 11.5px; padding: 4px 10px; }

.switch { width: 38px; height: 22px; background: var(--hairline); border-radius: var(--radius-pill); position: relative; cursor: pointer; transition: background var(--motion-base); }
.switch::after {
  content: ''; position: absolute; top: 2px; left: 2px;
  width: 18px; height: 18px; background: white; border-radius: 50%;
  box-shadow: 0 1px 2px rgba(10,10,15,0.15); transition: transform var(--motion-base) var(--motion-ease-out);
}
.switch.on { background: var(--pink); }
.switch.on::after { transform: translateX(16px); }

/* Custom scrollbar — scoped to scroll containers (audit-w1 PR-4). Thin,
   hairline thumb that darkens on hover; transparent track. Canon (not a
   style.css shadow) so it lives with the .drawer / .atlas-grid / .matrix-scroll
   classes it decorates. */
.drawer::-webkit-scrollbar,
.atlas-grid::-webkit-scrollbar,
.matrix-scroll::-webkit-scrollbar { width: 8px; height: 8px; }
.drawer::-webkit-scrollbar-track,
.atlas-grid::-webkit-scrollbar-track,
.matrix-scroll::-webkit-scrollbar-track { background: transparent; }
.drawer::-webkit-scrollbar-thumb,
.atlas-grid::-webkit-scrollbar-thumb,
.matrix-scroll::-webkit-scrollbar-thumb { background: var(--hairline); border-radius: 4px; }
.drawer::-webkit-scrollbar-thumb:hover,
.atlas-grid::-webkit-scrollbar-thumb:hover,
.matrix-scroll::-webkit-scrollbar-thumb:hover { background: var(--ink-3); }

.checkbox {
  width: 18px; height: 18px; border: 1.5px solid var(--ink-3);
  border-radius: 4px; display: inline-flex; align-items: center; justify-content: center; cursor: pointer;
}
.checkbox.on { background: var(--pink); border-color: var(--pink); }
.checkbox.on::after {
  content: ''; width: 5px; height: 9px; border: 2px solid white;
  border-top: 0; border-left: 0; transform: rotate(45deg) translate(-1px, -1px);
}

.radio {
  width: 18px; height: 18px; border: 1.5px solid var(--ink-3);
  border-radius: 50%; display: inline-flex; align-items: center; justify-content: center; cursor: pointer;
}
.radio.on { border-color: var(--pink); }
.radio.on::after { content: ''; width: 9px; height: 9px; background: var(--pink); border-radius: 50%; }

/* ============ CHIPS / BADGES ============ */
.chip {
  display: inline-flex; align-items: center; gap: 6px;
  font-family: var(--font-sans); font-weight: 500; font-size: 12px;
  padding: 4px 10px; border-radius: var(--radius-pill);
  background: var(--surface); color: var(--pink-deep); border: 1px solid var(--pink-light);
}
.chip.pink { background: var(--pink); color: white; border-color: var(--pink); }
.chip.ink { background: var(--ink); color: white; border-color: var(--ink); }
.chip.outline { background: white; color: var(--ink-2); border-color: var(--hairline); }

.badge {
  display: inline-flex; align-items: center; gap: 6px;
  font-family: var(--font-mono-heavy); font-weight: 700; font-size: 10px;
  letter-spacing: 0.14em; text-transform: uppercase;
  padding: 3px 8px; border-radius: 4px;
}
.badge-success { background: color-mix(in srgb, var(--success) 18%, white); color: #3F5F49; }
.badge-warning { background: color-mix(in srgb, var(--warning) 22%, white); color: #7A5A14; }
.badge-error   { background: color-mix(in srgb, var(--error) 14%, white); color: var(--cherry-deep); }
.badge-info    { background: color-mix(in srgb, var(--info) 18%, white); color: var(--info-deep); }

/* ============ SOURCE PROVENANCE BADGE (Part E / F · Brand V3) ============
   External-source badge for data that originates outside the Markwins data
   layer — today Circana panel; tomorrow any other external research source.
   Inherits the .badge-info palette via --info-deep + the color-mix recipe.
   Self-contained — call sites write <span class="badge-src" title="…">…</span>.

   Placement rules — see RULES.md "External data — source provenance":
     KPI card    → inline with .kpi-card__delta
     Table th    → end of column label (static .data-table or .atlas-grid)
     Chart       → in the .legend row, after the swatch label
     Insight     → in .insight-callout__eyebrow inline
     Panel-head  → right-side action of .pm-panel-head

   Tooltip canonical (the LONG form lives ONLY here; short form is in
   chart-tooltip foots — see RULES.md "Tooltip · canonical contract"):
     title="Source: Circana panel · MAT P5-2026 · 4 EU markets aggregate (UK · DE · FR · ES)"
   The period token (P5-2026) refreshes monthly from data/circana_meta.json. */
.badge-src {
  display: inline-flex; align-items: center; gap: 5px;
  font-family: var(--font-mono-heavy); font-weight: 700;
  font-size: 10px; letter-spacing: 0.14em; text-transform: uppercase;
  padding: 3px 8px; border-radius: 4px;
  background: color-mix(in srgb, var(--info) 18%, white);
  color: var(--info-deep);
  cursor: help;
  white-space: nowrap;
}
.badge-src::before {
  content: ''; width: 5px; height: 5px;
  background: var(--info); border-radius: 50%;
  display: inline-block; flex: 0 0 5px;
}

/* ============ SCOPE BADGE (audit-w1 PR-5) ============
   Disclosure pill for surfaces that do NOT subscribe to the global filters
   (Stock, Budget tab, Client, Brand·Innovations). Amber/cream tone (the
   --open-edge "provisional / forward" family) = "this is not the global
   scope". Optional title="" carries the real fixed scope.
   Usage: <span class="scope-badge" title="Latest snapshot · all brands">
            FIXED SCOPE · GLOBAL FILTERS NOT APPLIED</span> */
.scope-badge {
  display: inline-flex; align-items: center; gap: 6px;
  font-family: var(--font-mono-heavy); font-weight: 700;
  font-size: 11px; letter-spacing: 0.12em; text-transform: uppercase;
  padding: 3px 9px; border-radius: 4px;
  background: var(--cream); border: 1px solid var(--open-edge);
  color: var(--warning-deep); white-space: nowrap;
}
.scope-badge::before {
  content: ''; width: 5px; height: 5px;
  background: var(--open-edge); border-radius: 50%;
  display: inline-block; flex: 0 0 5px;
}
.scope-badge[title] { cursor: help; }

/* ============ COMPETITOR NPD · CLASSIFICATION CHIP (Part F · Brand V3) ============
   Hot / Mid / Cold classification chip used by the Competitor NPD Watch
   table (brand-market.html priority HIGH per brief §3 Tab 4) and the
   Missed Opportunities tier column (brand-innovations.html Section 4 +
   brand-market.html Section 3). Reusable for any classification chip
   Hot / Mid / Cold. Composes existing semantic tokens via color-mix;
   no new tokens. Thresholds per brief §6.4:
     Hot: ≥$2M MAT_VAL + subcat growth ≥+15%
     Mid: $500K–$2M MAT_VAL
     Cold: <$500K (hidden by default chip filter) */
.cnpd-class {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 3px 8px; border-radius: 4px;
  font-family: var(--font-mono-heavy); font-weight: 700;
  font-size: 10px; letter-spacing: 0.14em; text-transform: uppercase;
  white-space: nowrap;
}
.cnpd-class::before {
  content: ''; width: 6px; height: 6px;
  border-radius: 50%; display: inline-block;
}
.cnpd-class--hot {
  background: color-mix(in srgb, var(--cherry) 16%, white);
  color: var(--cherry-deep);
}
.cnpd-class--hot::before { background: var(--cherry); }
.cnpd-class--mid {
  background: color-mix(in srgb, var(--warning) 18%, white);
  color: var(--warning-deep);
}
.cnpd-class--mid::before { background: var(--warning); }
.cnpd-class--cold {
  background: var(--surface);
  color: var(--ink-3);
}
.cnpd-class--cold::before { background: var(--ink-3); }

/* ============ STATUS PILLS (TODO_COWORK B1 — the one canonical pill) ============ */
/* THE single status-indicator family. Atlas reinvented this chrome six times
   (stock-pill / cov-badge / score-badge / status-badge / kpi-yoy-pill / ...);
   they all collapse here. The chrome is canonical; the LABEL text stays
   domain-specific ("IN STOCK", "HERO", "NEW" — whatever the column means).
   Do NOT add a 7th bespoke family — pick a tone below.

   Tone semantics + threshold map per Atlas use case:
     up      green  — good / on track / in stock / full coverage / hero
     neutral gray   — neutral / steady / unknown / N/A / existing / inactive
     warn    amber  — at risk / low / thin coverage / watch / obsolete
     down    red    — stalled / OOS / coverage gap / cut / deleted
     info    teal   — lifecycle marker, "look here": new / limited edition

     status-pill (KPI / goal):  up >=80% | warn 50-80% | down 20-50% ... neutral n/a
     stock-pill:   up >1 month cover | warn <1 month | down OOS (0) | neutral unknown
     cov-badge:    up >=90% | neutral 60-90% or N/A | warn 30-60% | down <30%
     score-badge:  up hero (top quartile) | neutral steady | warn watch | down cut
     status-badge: neutral existing/inactive | info new/ltd-ed | warn obsolete | down deleted */
.status-pill {
  display: inline-flex; align-items: center; white-space: nowrap;
  font-family: var(--font-mono-heavy); font-weight: 700;
  font-size: 10px; letter-spacing: 0.12em; text-transform: uppercase;
  padding: 3px 9px; border-radius: var(--radius-pill);
}
/* Status pills are DECORATIVE STATUS CHROME, not body text. They classify
   under WCAG SC 1.4.11 (non-text contrast, 3:1 threshold) — not SC 1.4.3
   (body text, 4.5:1). The brighter literals below are the canonical pill
   palette (May 2026 — restored from book version). All pass 3:1 non-text
   contrast on their tinted backgrounds; "STATUS" is conveyed by color AND
   uppercase label AND tone, satisfying SC 1.4.1 (use of color). Do NOT
   "fix" these to darker variants — they intentionally match the book. */
.status-pill-up      { color: #047857; background: #d1fae5; }                /* emerald-700 (AA on bg) */
.status-pill-neutral { color: #4b5563; background: #f3f4f6; }                /* slate-600 (AA on bg)   */
.status-pill-warn    { color: var(--warning-deep); background: var(--warning-tint); }    /* warning-deep #7A5A14 (AA on bg) */
.status-pill-down    { color: #b91c1c; background: #fee2e2; }                /* red-700 (AA on bg)     */
.status-pill-info    { color: #1F4F5A; background: #e2f3f8; }                /* teal-deep (AA on bg) */

/* Size variants — only TWO, no more. Default (sm) is the chip/identifier
   pill (h≈18px, max radius 9px); --md is the table-context indicator
   (h≈25px, max radius 12.5px — above the visual capsule threshold).
   Default reads as "rounded-rect" inside tall table rows because its 9px
   max radius is below the perceptual capsule threshold (~12px). --md
   lifts the radius into clear-capsule territory without competing with
   cell numbers. Do NOT add --lg / --xl — pick a size, not a stack.
   May 2026: --md padding bumped from 4×11 to 6×13 (Round 11 push-back
   from CODE: the original 4×11 only yielded h≈19px / max radius 9.5px,
   STILL below the capsule threshold. Atlas had to override locally with
   6×13, which is the geometry that actually works — promoted to canonical
   so Atlas can drop its override). */
.status-pill--md {
  font-size: 11px;
  padding: 6px 13px;
  letter-spacing: 0.11em;
}

/* ============ ABC CLASSIFICATION CARD ============ */
/* For ABC analysis cells (A = top performers, B = good, C = watch, D = cut).
   This is a CARD, not a pill — the letter is content, not chrome, so the
   element is fixed-square 28×28 with body-text-AA contrast on its tint.
   Previously misnamed `abc-pill` in Atlas (12px radius rectangle, not a
   capsule) — renamed canonical to clarify the semantic: it's classification
   chrome, not a status indicator. Use ONLY for ABC/XYZ-style fixed-set
   classification (max 4-5 categories). Anything with variable / many labels
   belongs in `.status-pill` (chrome) or `.chip` (data tag). */
.abc-card {
  display: inline-flex; align-items: center; justify-content: center;
  width: 28px; height: 28px;
  border-radius: 12px;
  font-family: var(--font-mono-heavy); font-weight: 700;
  font-size: 14px;
  letter-spacing: 0;
  flex: 0 0 auto;
}
.abc-card-a { color: #047857;             background: #d1fae5; }   /* top: emerald-700 AA 4.54 */
.abc-card-b { color: #1f6f86;             background: #e2f3f8; }   /* good: teal AA 5.01      */
.abc-card-c { color: var(--warning-deep); background: var(--warning-tint); }   /* watch: amber AAA 5.92   */
.abc-card-d { color: #6b7280;             background: #f3f4f6; }   /* cut: slate AA-large 4.39 — acceptable here because the letter D itself is the carrier; tone+position+letter all encode the class */

/* ============ ABC INSIGHT (Direction B — replaces stacked stat-cards) ============ */
/* Canonical replacement for the Atlas Brand → Assortment "ABC Classification"
   block. Was 3 vertically-stacked stat-cards (the old `.abc-pill` outer wrapper)
   eating ~270px to repeat info the Pareto chart above already showed. This
   pattern is a single insight-led component:
   - Headline sentence ("164 SKUs drive 80% of revenue · 284 long-tail SKUs
     deliver just 5%") — the takeaway IS the title, no scanning needed
   - Stacked horizontal bar showing A/B/C proportions inscribed with $ totals
   - 3-cell legend with SKU count + amount per tier
   One component, ~210px shorter, scales to any width. Use anywhere an
   ABC/Pareto distribution needs to be summarised in a card. */
.abc-insight {
  background: var(--paper);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-md);
  padding: 18px 20px;
}
.abc-insight__headline {
  font-family: var(--font-sans);
  font-size: 15px;
  line-height: 1.4;
  color: var(--ink);
  margin: 0 0 14px;
}
.abc-insight__headline strong { font-weight: 600; }
.abc-insight__headline .accent {
  color: var(--pink-text);
  font-family: var(--font-mono-heavy);
  font-weight: 700;
}
.abc-insight__bar {
  position: relative;
  height: 32px;
  border-radius: var(--radius-sm);
  overflow: hidden;
  background: #f5f3f4;
  display: flex;
}
.abc-insight__bar-seg {
  height: 100%;
  display: flex;
  align-items: center;
  padding: 0 12px;
  font-family: var(--font-mono-heavy);
  font-size: 11px;
  font-weight: 700;
  color: #fff;
  letter-spacing: 0.04em;
  white-space: nowrap;
}
.abc-insight__bar-seg--a { background: var(--success); }
.abc-insight__bar-seg--b { background: var(--ink-3); }
.abc-insight__bar-seg--c { background: var(--warning); color: var(--warning-deep); padding: 0 4px; justify-content: center; font-size: 10px; }
.abc-insight__legend {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 12px;
  margin-top: 14px;
  padding-top: 14px;
  border-top: 1px dashed var(--hairline);
}
.abc-insight__leg-cell { font-size: 12.5px; color: var(--ink); }
.abc-insight__leg-cell .dot {
  display: inline-block;
  width: 8px; height: 8px;
  border-radius: 2px;
  margin-right: 6px;
  vertical-align: 1px;
}
.abc-insight__leg-cell .label {
  font-family: var(--font-mono-heavy);
  font-size: 10.5px; font-weight: 700;
  letter-spacing: 0.18em; text-transform: uppercase;
  color: var(--ink-3);
}
.abc-insight__leg-cell .stats { margin-top: 4px; }
.abc-insight__leg-cell .stats .n {
  font-family: var(--font-mono-heavy);
  font-weight: 700;
}
.abc-insight__leg-cell--a .dot { background: var(--success); }
.abc-insight__leg-cell--b .dot { background: var(--ink-3); }
.abc-insight__leg-cell--c .dot { background: var(--warning); }

/* ============ SKU ROW HELPERS ============ */
.sku-thumb {
  width: 36px; height: 36px; border-radius: var(--radius-sm);
  background: linear-gradient(135deg, #fff0f3 0%, #ffe5ee 100%);
  border: 1px solid var(--hairline);
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--pink-deep); font-size: 14px;
}
.sku-code {
  font-family: var(--font-mono); font-weight: 500; font-size: 12px;
  color: var(--pink-text); text-decoration: none;  /* was --pink-deep (AA-large only). --pink-text passes AA at 6.34 on white. Weight 500 (not 600) — DM Mono at 600 in dark-pink renders blurry / strikethrough-like at this size. */
  background: var(--pink-tint); padding: 2px 6px; border-radius: 4px;
  font-variant-ligatures: none; /* DM Mono ligatures (especially around digits) cause stroke artefacts on dark text. Disable. */
}
.sku-code:hover { background: var(--pink-light); }
.sku-shade-inline {
  font-style: italic; text-transform: uppercase;
  letter-spacing: 0.04em; color: #9ca3af; font-size: 11px; margin-left: 2px;
}

/* The stacked SKU + name pattern — THE canonical row identifier for ranked
   product tables (Top SKUs, All Products grid, Rotation & Coverage,
   Best/Worst) and every Sales tab. SKU code on top, product name + shade
   below, both at 13.5px so they read as peer of the row's numeric values.
   The .sku-code KEEPS its chip here (style round · B7 — the chip is
   canonical everywhere); this block only resizes it. The name line carries
   an optional .sku-shade-inline shade. */
.td-sku-name {
  display: flex; flex-direction: column; align-items: flex-start; gap: 3px;
  line-height: 1.25; min-width: 200px;
}
.td-sku-name .sku-code { font-size: 13.5px; letter-spacing: 0.02em; }
.td-sku-name .name {
  font-family: var(--font-sans); font-weight: 500; font-size: 13.5px;
  color: var(--ink); line-height: 1.3;
}

/* ============ KPI CARD (Style round · B10 — unified) ============
   ONE component. Eyebrow + value, an optional delta line, and an
   optional forward block (dashed divider + Open-ETA / EOM-forecast
   lines). B10 merged three converging vocabularies that had drifted
   apart: the header card (.kpi / .kpi-card-static / .kpi-card-clickable),
   the composite V2 card (.kpi-card--clickable / .kpi-card__value /
   .delta--*) and the dead .metric-pill. Grammar is BEM, matching the
   sibling component .hero-kpi-card:

     .kpi-card                   base — a plain summary card (static)
     .kpi-card--clickable        metric SELECTOR — drives the chart/table
     .kpi-card--selected         the metric currently driving the chart
     .kpi-card--risk[-critical]  persistent amber / cherry status edge
     .kpi-card--skeleton         loading shell (see SKELETON section)
     .kpi-card__eyebrow          label row (+ __eyebrow-text for ellipsis)
     .kpi-card__value            the number — SANS, not mono (Rule 03)
     .kpi-card__delta            delta / sub line (+ --up/--down/--warn)
     .kpi-card__fwd              optional forward block

   Decision tree (RULES.md / BRAND.md §5):
     .kpi-card             -> numeric summary / metric selector
     .hero-kpi-card        -> icon-led, narrative, sentiment border
   Never mix .kpi-card and .hero-kpi-card in one row. */

/* --- row layout: one grid for KPI cards (replaces .kpi-row-clickable,
   .hero-kpi-row, .kpi-grid). Auto-fit by default; --3/--4 force a
   fixed column count and collapse responsively. --- */
.kpi-row {
  display: grid; gap: 16px;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
}
.kpi-row--3 { grid-template-columns: repeat(3, 1fr); }
.kpi-row--4 { grid-template-columns: repeat(4, 1fr); }
@media (max-width: 980px) { .kpi-row--3, .kpi-row--4 { grid-template-columns: 1fr 1fr; } }
@media (max-width: 620px) { .kpi-row--3, .kpi-row--4 { grid-template-columns: 1fr; } }

/* --- the card · F-Code.10 compact (Andrea 2026-05-31) --------------------
   Tightened Atlas-wide so Sales + Brand KPI strips share one compact height:
   padding 14/16 (was 18/20), gap 4 (was 6), radius-md (was -lg). The value
   stays SANS via --type-kpi-value-size (Rule 03) and dual-delta / gauge are
   unchanged — only the box tightens. */
.kpi-card {
  background: var(--paper); border: 1px solid var(--hairline);
  border-radius: var(--radius-md); padding: 14px 16px;
  display: flex; flex-direction: column; gap: 4px; min-width: 0;
  transition: box-shadow var(--motion-medium), border-color var(--motion-medium),
              transform var(--motion-medium);
}

/* clickable — a metric selector. Cursor + hover lift; no lift while
   selected (a selected card is already the active state — UX rule). */
.kpi-card--clickable { cursor: pointer; }
.kpi-card--clickable:not(.kpi-card--selected):hover {
  border-color: var(--pink-light);
  box-shadow: var(--shadow-card-hover);
  transform: translateY(-1px);
}
.kpi-card--clickable:focus-visible {
  outline: 3px solid color-mix(in srgb, var(--pink) 50%, transparent);
  outline-offset: 2px;
}

/* selected — the metric currently driving the chart. A 3px --pink top
   edge via inset shadow (follows the radius, never overhangs a corner).
   This is the ONLY thing in the KPI header that paints a pink edge:
   anchor vs tab-zone is carried by .kpi-header__divider, never a
   per-card edge (B10 dropped .kpi-card--tab for exactly this reason —
   a pink edge means "selected" and nothing else). */
.kpi-card--selected {
  border-color: var(--pink);
  box-shadow: inset 0 3px 0 var(--pink);
}

/* risk — a persistent status edge (Open Orders · Overdue). 3px inset on
   the left; --risk-critical escalates amber -> cherry. Inset shadow so
   it composes with --selected without a layout shift. */
.kpi-card--risk          { box-shadow: inset 3px 0 0 var(--warning); }
.kpi-card--risk-critical { box-shadow: inset 3px 0 0 var(--cherry); }
.kpi-card--selected.kpi-card--risk          { box-shadow: inset 0 3px 0 var(--pink), inset 3px 0 0 var(--warning); }
.kpi-card--selected.kpi-card--risk-critical { box-shadow: inset 0 3px 0 var(--pink), inset 3px 0 0 var(--cherry); }
.kpi-card--risk.kpi-card--clickable:not(.kpi-card--selected):hover          { box-shadow: inset 3px 0 0 var(--warning), var(--shadow-card-hover); }
.kpi-card--risk-critical.kpi-card--clickable:not(.kpi-card--selected):hover { box-shadow: inset 3px 0 0 var(--cherry), var(--shadow-card-hover); }

/* --- eyebrow (label). __eyebrow is the flex row that also holds the
   .kpi-info-icon; wrap long label text in __eyebrow-text for ellipsis. */
.kpi-card__eyebrow {
  display: flex; align-items: center; gap: 6px; min-width: 0;
  font-family: var(--font-mono-heavy); font-weight: 700;
  font-size: 10px; letter-spacing: 0.18em; text-transform: uppercase;
  color: var(--ink-3);
}
.kpi-card__eyebrow-text { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* --- value — Rule 03: SANS, not mono. nowrap: a negative sign must
   never wrap and break the card (Modal Cliente · "Δ vs Budget"). --- */
.kpi-card__value {
  font-family: var(--font-sans); font-weight: var(--type-kpi-value-weight);
  font-size: var(--type-kpi-value-size); line-height: 1.05; color: var(--ink);
  font-variant-numeric: tabular-nums; letter-spacing: var(--type-kpi-value-tracking);
  white-space: nowrap;
}

/* --- delta / sub line. Bare = neutral sub text; the sentiment
   modifier colours the delta. __delta-vs is the muted "vs LY" suffix. */
.kpi-card__delta {
  display: flex; align-items: baseline; gap: 6px;
  font-family: var(--font-mono-heavy); font-weight: 700; font-size: 11.5px;
  font-variant-numeric: tabular-nums; color: var(--ink-3);
}
.kpi-card__delta--up   { color: var(--success-deep); }
.kpi-card__delta--down { color: var(--cherry-deep); }
.kpi-card__delta--warn { color: var(--warning-deep); }
.kpi-card__delta--flat { color: var(--ink-3); }
.kpi-card__delta-vs {
  font-family: var(--font-sans); font-weight: 400; font-size: 11px;
  letter-spacing: 0; color: var(--ink-3);
}

/* --- forward block — dashed divider + Open-ETA / EOM-forecast lines.
   Optional; used by the composite cards on Sales Overview. --- */
.kpi-card__fwd {
  margin-top: 6px; padding-top: 10px;
  border-top: 1px dashed var(--hairline);
  display: flex; flex-direction: column; gap: 5px;
}
.kpi-card__fwd-line { display: flex; align-items: center; justify-content: space-between; font-size: 12px; }
.kpi-card__fwd-label {
  display: flex; align-items: center; gap: 6px;
  font-family: var(--font-sans); color: var(--ink-3);
}
.kpi-card__fwd-value {
  font-family: var(--font-mono-heavy); font-weight: 700; font-size: 12px;
  color: var(--ink); font-variant-numeric: tabular-nums;
}
.kpi-card__fwd-line--forecast .kpi-card__fwd-label { color: var(--ink-2); font-weight: 600; }
.kpi-card__fwd-line--forecast .kpi-card__fwd-value { color: var(--pink-text); font-size: 12.5px; }
.kpi-card__pip { flex: 0 0 7px; width: 7px; height: 7px; border-radius: 2px; display: inline-block; }

/* --- gauge — budget-pace visual (Shell v3 canonization).
   A thin track + a coloured fill + a vertical pace tick. The fill = attainment
   %, the tick = expected pace given day-of-month. Fill past the tick = ahead
   (success), short of it = behind (warning). Use on the Monthly Budget card
   in the Sales KPI header; secondary use on metric cards where within-period
   pacing matters. No new tokens — all primitives already exist. The dual
   __delta lines stack naturally via the parent .kpi-card flex column gap; no
   extra spacing rule is needed (a __delta sibling pair inherits the 6px row
   gap already declared on .kpi-card). */
.kpi-card__gauge { margin-top: 8px; }
.kpi-card__gauge-track {
  position: relative; height: 8px;
  border-radius: var(--radius-pill); background: var(--hairline);
}
.kpi-card__gauge-fill {
  position: absolute; left: 0; top: 0; bottom: 0;
  border-radius: var(--radius-pill);
}
.kpi-card__gauge-fill--ahead  { background: var(--success); }
.kpi-card__gauge-fill--behind { background: var(--warning); }
.kpi-card__gauge-pace {
  position: absolute; top: -3px; bottom: -3px;
  width: 2px; background: var(--ink); border-radius: 1px;
}
.kpi-card__gauge-foot {
  display: flex; justify-content: space-between; margin-top: 6px;
  font-family: var(--font-mono); font-size: 10px;
  letter-spacing: 0.04em; color: var(--ink-3);
}
.kpi-card__gauge-foot b { color: var(--ink-2); font-weight: 500; }

@media (prefers-reduced-motion: reduce) {
  .kpi-card { transition: none; }
  .kpi-card--clickable:hover { transform: none; }
}

/* ============ KPI STRIP — Sales v2 (5 slim cards, one row) ============
   The canonical KPI surface (supersedes the .kpi-header anchor|divider|tab
   split on the surfaces that adopt it). Rule: ALWAYS exactly 5 slim cards,
   one row, uniform height, NO divider. Each card may carry ONE micro-graphic
   — bullet (target+pace tick), mix (semantic composition) or spark (6-month
   trend) — only when it adds reading; a DERIVED card carries none and keeps
   the empty slot (min-height holds the height, never shrinks). Info-icon is
   pinned to the card's top-right corner (C7-bis). Built only from tokens.
   Component class: kpi_strip() in components/kpi_strip.py. */
.kpi-strip {
  display: flex; align-items: stretch; gap: 12px; flex-wrap: wrap;
}
.kpi-strip > .kpi-card {
  flex: 1 1 0; min-width: 150px;
  /* uniform height across every strip, every section — the DERIVED card
     (no micro-graphic) does NOT shrink, the slot stays empty (C7-bis). */
  min-height: 104px;
  padding: 10px 14px; gap: 2px;
}
/* slim grammar (Brand strip sizing): value 21px, delta 10.5px. */
.kpi-strip .kpi-card__value { font-size: 21px; }
.kpi-strip .kpi-card__delta { font-size: 10.5px; }

/* clock prefix in the eyebrow — mono 9px pink ("PERIOD ·", "NOW ·", "FY ·"…). */
.kpi-card__clock {
  font-family: var(--font-mono-heavy); font-weight: 700;
  font-size: 9px; letter-spacing: 0.14em; color: var(--pink-text);
  margin-right: 4px;
}

/* --- micro-graphics — max ONE per card, bottom-pinned (margin-top:auto) so
   they share a baseline across the strip regardless of sub-line length. --- */
/* bullet (TARGET): fill = attained, dark tick = expected pace today. */
.kpi-card__bullet {
  position: relative; height: 5px; margin-top: auto;
  border-radius: var(--radius-pill); background: var(--surface);
}
.kpi-card__bullet-fill {
  height: 100%; border-radius: var(--radius-pill); background: var(--success);
}
.kpi-card__bullet-fill--behind { background: var(--warning); }
.kpi-card__bullet-tick {
  position: absolute; top: -2px; width: 2px; height: 9px;
  background: var(--ink-2); border-radius: 1px;
}
/* mix (MIX): semantic-coloured composition segments (sum = 100%). */
.kpi-card__mix {
  display: flex; height: 5px; margin-top: auto; overflow: hidden;
  border-radius: var(--radius-pill); background: var(--surface);
}
.kpi-card__mix-seg { height: 100%; }
/* spark (TREND): 6-month bar-sparkline, last bar emphasised (pink). ≤22px
   tall. Bars (not an SVG line) because dash.html exposes no <svg> element —
   a flex of thin token-coloured columns reads the trend and stays drift-clean. */
.kpi-card__spark {
  display: flex; align-items: flex-end; gap: 2px; height: 20px; margin-top: auto;
}
.kpi-card__spark-bar {
  flex: 1 1 0; min-width: 2px; border-radius: 1px;
  background: var(--ink-3); opacity: 0.5;
}
.kpi-card__spark-bar--last { background: var(--pink); opacity: 1; }

/* C7-bis — info-icon pinned to the card's top-right CORNER (not inline in the
   eyebrow row). The card is the positioning context; the eyebrow gains right
   padding so a long label never slides under the glyph. Canon-wide: applies
   to every .kpi-card on every surface, not just the strip. Offset 14/14 =
   the field-tested app placement, merged into canon (audit Fase 2 shadowed
   DEC1 — app winner, was 8/10 here). */
.kpi-card { position: relative; }
.kpi-card > .kpi-card__eyebrow { padding-right: 18px; }
.kpi-card .kpi-info-icon {
  position: absolute; top: 14px; right: 14px; margin-left: 0; z-index: 2;
}
/* leftmost strip card: open its tooltip to the RIGHT so it never slides under
   the sidebar (mirrors the .kpi-header first-child rule). */
.kpi-strip > *:first-child .kpi-info-tip { left: 0; right: auto; }

/* Touch / no-hover devices: kill the hover-lift (hover is emulated on tap,
   so the translateY(-1px) would stick after a tap) — audit-w1 minors. */
@media (hover: none) {
  .kpi-card--clickable:hover,
  .hero-kpi-card.is-clickable:hover { transform: none; }
}

/* ============ METRIC SELECT (F-Code.11) ============
   The canonical chart metric switcher. A topbar-style pill (matching the
   Time / Filters `.fb-chip` family) + a popover of options. This is the
   ONLY way to switch a chart's displayed metric — it REPLACES the old
   clickable-KPI mechanism (`.kpi-card--clickable` / `--selected`), which
   conflated "card communicates status" with "card drives the chart".
   KPI cards are display-only (RULES.md · "KPI card — pick by intent");
   metric-switching lives here. Use on any chart whose metric is
   user-switchable, anywhere in Atlas — not just Sales · Overview.

     .metric-select                base inline-flex anchor (position: relative)
     .metric-select__trigger       the pill (closed); .is-open while popover open
     .metric-select__eye           the eyebrow handle ("METRIC") — mono, uppercase
     .metric-select__sep           the hairline middot between eyebrow and value
     .metric-select__value         the active metric label
     .metric-select__chevron       the disclosure chevron (rotates 180° when open)
     .metric-select__popover       the options surface (anchored under the pill)
     .metric-select__option        one option row; .is-active for the current metric */
.metric-select { display: inline-flex; position: relative; }
.metric-select__trigger {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 6px 10px 6px 12px;
  border: 1px solid var(--hairline); border-radius: var(--radius-md);
  background: var(--paper); cursor: pointer;
  font-size: 15px; line-height: 1;
  transition: border-color var(--motion-base);
}
.metric-select__trigger:hover { border-color: var(--ink-3); }
.metric-select__trigger.is-open {
  border-color: var(--pink-deep);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--pink) 20%, transparent);
}
.metric-select__eye {
  font-family: var(--font-mono-heavy); font-weight: 700;
  font-size: 9px; letter-spacing: 0.22em; text-transform: uppercase;
  color: var(--ink-3);
}
.metric-select__trigger.is-open .metric-select__eye { color: var(--pink-deep); }
.metric-select__sep { color: var(--hairline); }
.metric-select__value { color: var(--ink); font-weight: 600; }
.metric-select__chevron {
  width: 11px; height: 11px; stroke: var(--ink-3); margin-left: 2px;
  transition: transform var(--motion-base);
}
.metric-select__trigger.is-open .metric-select__chevron {
  transform: rotate(180deg); stroke: var(--pink-deep);
}

.metric-select__popover {
  position: absolute; top: 100%; left: 0; margin-top: 8px;
  width: 280px; background: var(--paper);
  border: 1px solid var(--hairline); border-radius: var(--radius-md);
  box-shadow: var(--shadow-menu); overflow: hidden; z-index: 10;
}
.metric-select__popover-head {
  padding: 8px 12px 6px; border-bottom: 1px solid var(--hairline);
  font-family: var(--font-mono-heavy); font-weight: 700;
  font-size: 9px; letter-spacing: 0.22em; text-transform: uppercase;
  color: var(--ink-3);
}
.metric-select__option {
  display: flex; align-items: center; gap: 10px;
  padding: 8px 10px; margin: 2px;
  border-radius: var(--radius-sm); cursor: pointer;
}
.metric-select__option:hover { background: color-mix(in srgb, var(--pink-tint) 50%, white); }
.metric-select__option.is-active {
  background: var(--pink-tint); color: var(--pink-deep); font-weight: 600;
}
.metric-select__option-check { width: 12px; flex-shrink: 0; }
/* Labels left-aligned: the option is a <button> (text-align:center by
   default), and the label span inherits it — pin left so options read as a
   left-aligned list, value-less or not (the approved layout). */
.metric-select__option-label { font-family: var(--font-sans); font-size: 13px; flex: 1; text-align: left; }
.metric-select__option-value {
  font-family: var(--font-mono); font-size: 11px;
  color: var(--ink-3); font-variant-numeric: tabular-nums;
}
.metric-select__option.is-active .metric-select__option-value {
  color: var(--pink-deep); font-weight: 700;
}

/* === CHART TITLE · dropdown-as-title (F-Code.11 companion) ===============
   Canonical chart panel-head title: a `.metric-select` pill + a sans suffix
   that read as ONE title ("Gross Sales — monthly trend"). Use on ANY Atlas
   chart whose title carries a metric switcher, so every chart reads the same
   (Sales · Overview / Compare / Countries, Brand, …). The pill is the
   interactive head; `__suffix` is the static granularity/scope tail. */
.chart-title { display: flex; align-items: center; gap: 9px; }
.chart-title__suffix {
  font-family: var(--font-sans); font-size: 16px; font-weight: 500;
  color: var(--ink-2);
}

/* ---- Hero KPI card (§5.2b): icon-led + sentiment border accent ---- */
.hero-kpi-card {
  background: var(--paper);
  border: 1px solid var(--hairline);
  border-left: 4px solid var(--hairline);
  border-radius: var(--radius-lg);
  padding: 18px 20px;
  display: flex; flex-direction: column; gap: 6px;
  min-width: 0;
  transition: box-shadow var(--motion-medium) var(--motion-ease-out), transform var(--motion-medium);
}
.hero-kpi-card--up      { border-left-color: var(--success); }
.hero-kpi-card--down    { border-left-color: var(--cherry); }
.hero-kpi-card--warn    { border-left-color: var(--warning); }
.hero-kpi-card--neutral { border-left-color: var(--ink-3); }

.hero-kpi-card__head { display: flex; align-items: center; gap: 8px; margin-bottom: 4px; min-width: 0; }
.hero-kpi-card__icon { width: 16px; height: 16px; flex: 0 0 16px; color: var(--ink-3); display: inline-flex; }
.hero-kpi-card__icon svg { width: 100%; height: 100%; }
.hero-kpi-card__eyebrow {
  font-family: var(--font-mono-heavy); font-weight: 700; font-size: 11px;
  letter-spacing: 0.08em; text-transform: uppercase; color: var(--ink-3);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.hero-kpi-card__value {
  font-family: var(--font-sans); font-weight: 600; font-size: var(--type-kpi-value-size);
  line-height: 1.05; color: var(--ink);
  font-variant-numeric: tabular-nums; letter-spacing: var(--type-kpi-value-tracking);
  white-space: nowrap;
}
.hero-kpi-card__detail {
  font-family: var(--font-mono-heavy); font-size: 11px; line-height: 1.5;
  color: var(--ink-2); margin-top: 4px;
}
.hero-kpi-card__detail b { font-weight: 700; color: var(--ink); }
.hero-kpi-card__detail .up   { color: #3F5F49; }
.hero-kpi-card__detail .down { color: var(--cherry-deep); }
.hero-kpi-card__detail .warn { color: #7A5A14; }

.hero-kpi-card--up   .hero-kpi-card__icon { color: var(--success); }
.hero-kpi-card--down .hero-kpi-card__icon { color: var(--cherry); }
.hero-kpi-card--warn .hero-kpi-card__icon { color: var(--warning); }

.hero-kpi-card.is-clickable { cursor: pointer; }
.hero-kpi-card.is-clickable:hover { box-shadow: var(--shadow-card-hover); transform: translateY(-1px); }
.hero-kpi-card.is-clickable:focus-visible {
  outline: 3px solid color-mix(in srgb, var(--pink) 50%, transparent); outline-offset: 2px;
}
@media (prefers-reduced-motion: reduce) {
  .hero-kpi-card { transition: none; }
  .hero-kpi-card.is-clickable:hover { transform: none; }
}

/* ============ CROSS-TAB KPI HEADER (Style round · B1 + B10) ============
   The header row every Sales tab opens with. A fixed ANCHOR of 3 cards
   (Gross Sales / Open Orders / EOM Forecast — identical on every tab) +
   a TAB ZONE of 1-2 tab-specific cards, split by .kpi-header__divider.
   The divider is the ONLY anchor / tab-zone separator — there is no
   per-card "tab" edge. B10 dropped .kpi-card--tab: a pink edge means
   "selected" and nothing else, so tab membership must never borrow it.
   The cards are the canonical .kpi-card (or .hero-kpi-card). */
.kpi-header {
  display: flex; align-items: stretch; gap: 16px; flex-wrap: wrap;
}
.kpi-header > * { flex: 1 1 0; min-width: 150px; }
.kpi-header__divider {
  flex: 0 0 1px; min-width: 0; align-self: stretch;
  background: var(--hairline); margin: 2px 0;
}

/* ============ PANEL — the canonical card / frame wrapper ============
   White card with hairline border + 12px radius + padding. THE canonical
   container for tables, KPI rows, chart blocks, and any "section" of the
   dashboard that needs its own frame. Atlas used `.section` for this same
   role; canonical is `.panel`. Variants: `.panel--flat` (no border, just
   bg + radius — for nested panels) and `.panel--tight` (reduced padding,
   for compact summary cards). */
.panel {
  background: white;
  border: 1px solid var(--hairline);
  border-radius: var(--radius-lg);
  padding: 18px 20px;
}
.panel--flat   { border: 0; }
.panel--tight  { padding: 14px 16px; }

/* ============ OPEN-ORDERS TREATMENT (Style round · B2) ============
   How not-yet-invoiced ("open") value is rendered. The amber --open-edge
   means "provisional / forward". Three cases in CHARTS (see RULES.md):
     1. open ON one series (Overview)        -> amber diagonal hatch
     2. open in a DECOMPOSED chart (Compare)  -> hatch keeps the dimension's
        OWN colour, not amber
     3. open STANDS ALONE (Open Orders tab)   -> solid amber fill
   In TABLES, open value gets the .col-open column: a --cream fill with
   --open-edge side borders, numbers in --warning-deep. This is the one
   sanctioned in-dashboard use of --cream (Rule 1) — the column marks
   provisional value, it is not decorative surface. */
.col-open {
  background: var(--cream);
  border-left: 1px solid var(--open-edge);
  border-right: 1px solid var(--open-edge);
}
th.col-open {
  background: color-mix(in srgb, var(--open-edge) 32%, var(--cream));
}
.col-open .num, .col-open .td-num, .col-open .td-value,
td.col-open.num { color: var(--warning-deep); }

/* kpi-table variant — the deeper cream BAND for the OPEN USD column (the single
   source of the future/pipeline visual identity). Header th carries the
   --open-band tone; body td the --cream fill; both with --open-band-edge
   vertical borders, numbers in --warning-deep. Canon move from app style.css
   (audit Fase 2 shadowed DEC3). */
.kpi-table th.td-col-open {
  background: var(--open-band);
  border-left: 1px solid var(--open-band-edge);
  border-right: 1px solid var(--open-band-edge);
}
.kpi-table td.td-col-open {
  background: var(--cream);
  border-left: 1px solid var(--open-band-edge);
  border-right: 1px solid var(--open-band-edge);
  color: var(--warning-deep);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

/* ============ TABLES ============
   Tables are SEAMLESS by default — no own border, no own radius. The
   surrounding `.panel` provides the outer frame. This kills the
   double-outline issue (table border + card border = ugly double rule).
   For a standalone table not living inside a card, opt in with
   .data-table--bordered. The thead background is WHITE (not pink
   --surface) — the eyebrow text already says "header"; a 1.5px ink-3
   bottom rule under thead reinforces it. */
.data-table, .kpi-table {
  width: 100%;
  border-collapse: separate; border-spacing: 0;
  font-size: 13.5px;
}
.data-table thead, .kpi-table thead { background: white; }
.data-table th, .kpi-table th {
  font-family: var(--font-mono-heavy); font-weight: 700; font-size: 10px;
  text-transform: uppercase; letter-spacing: 0.16em; color: var(--ink-2);
  text-align: left; padding: 14px 16px; line-height: 1.3;
  vertical-align: bottom;
  border-bottom: 1.5px solid var(--ink-3);
}
.data-table th, .kpi-table th { white-space: nowrap; }  /* default: headers never wrap. Opt in to wrap via class="wrap" */
.data-table th.wrap, .kpi-table th.wrap { white-space: normal; }
.data-table th.num, .kpi-table th.num {
  text-align: right;
  min-width: 70px;
}
.data-table td.num, .kpi-table td.num { min-width: 70px; }
.data-table td, .kpi-table td {
  padding: 14px 16px; border-bottom: 1px solid var(--hairline);
  color: var(--ink); vertical-align: middle;
}
/* The dim + numeric cell pair: TWO things at once.
   1. vertical-align: top — when the value cell is a 2-line stack (primary +
      delta), the dim row identifier aligns with the PRIMARY value at the top
      of the cell, not centered between primary and delta.
   2. width: 1%; white-space: nowrap — hug content. The default table-layout
      auto + width 100% distributes leftover space evenly across all columns,
      creating huge horizontal gaps between cells in wide tables. By hugging
      content on dim and numeric cells, the leftover space goes to the first
      UNCLASSED column (typically a viz column like a bullet bar, or a long
      text column). Generic td default stays `middle` + auto width. */
.data-table td.td-dim, .data-table td.td-num,
.kpi-table  td.td-dim, .kpi-table  td.td-num,
.data-table td.num,    .kpi-table  td.num,
.td-dim, .td-num, .td-value, .td-num__delta {
  vertical-align: top;
  width: 1%; white-space: nowrap;
}
/* Same hug treatment for the other short-text td-* classes — but NOT for
   .td-name / .td-desc which are intentionally multi-line / ellipsis. */
.td-sku, .td-mono, .td-light { width: 1%; white-space: nowrap; }
/* CANON AMENDMENT (round 3, Andrea 2026-06-11): in any row that carries
   the 2-line product identity (.td-sku-name), EVERY cell — numbers and
   chips included — centres vertically on the row. The td.num `top` rule
   above exists for 2-line value stacks; next to a 2-line identity it
   reads broken (numbers hugging the top edge). :has() wins by
   specificity over the td.num rule. */
.data-table tr:has(.td-sku-name) td,
.kpi-table  tr:has(.td-sku-name) td { vertical-align: middle; }
/* Numeric cell: primary value SANS 13.5px, secondary delta MONO 11.5px stacked. */
.data-table td.num, .kpi-table td.num, .td-num {
  text-align: right; font-family: var(--font-sans); font-weight: var(--type-table-num-weight);
  font-size: var(--type-table-num-size); font-variant-numeric: tabular-nums;
  color: var(--ink); line-height: 1.25;
}
.td-num__delta {
  display: block; font-family: var(--font-mono); font-weight: 500;
  font-size: var(--type-table-delta-size); color: var(--ink-3); margin-top: 2px; letter-spacing: 0;
}
.td-num__delta.up   { color: #059669; }
.td-num__delta.down { color: var(--cherry-deep); }
.td-num__delta.warn { color: var(--warning-deep); } /* was literal #DDA73C — WCAG FAIL 2.17. Now token-driven via --warning-deep (#7A5A14, AAA 7.79). */
.data-table tbody tr:last-child td, .kpi-table tbody tr:last-child td { border-bottom: 0; }
.data-table tbody tr:hover,
.kpi-table tbody tr:hover { background: color-mix(in srgb, var(--surface) 70%, white); }
/* ============ STATIC TABLE · SELECTED ROW (Part F · Brand V3) ============
   Mirrors the canonical .ag-row.is-selected pattern (pink-tint bg + 3px
   pink inset edge) for static .data-table rows. Used in master-detail
   simulations: the row clicked in the master table shows .is-selected,
   the detail panel below populates with that row's drill. */
.data-table tr.is-selected td { background: var(--pink-tint); color: var(--ink); font-weight: 500; }
.data-table tr.is-selected td:first-child { box-shadow: inset 3px 0 0 var(--pink); }

/* ============ GROUP ROW · expandable multi-track territory (Innovations V2 · 2026-06-05) ============
   A .data-table row that aggregates N sub-rows — e.g. Spain = AROMYA (DIST)
   + DTR. The group row carries the chevron + the aggregate numbers; sort and
   pagination act on the aggregate, the sub-rows follow the parent. Mirrors
   the .matrix-tr-group / -sub typography on the static table. Default state:
   collapsed; .is-open on the group row reveals its .dt-row--sub siblings. */
.dt-row--group > td { font-weight: 600; }
.dt-row--group { cursor: pointer; }
.dt-row--group .dt-chev {
  font-family: var(--font-mono); font-size: 10px; color: var(--ink-3);
  margin-right: 6px; display: inline-block;
  transition: transform var(--motion-slow);
}
.dt-row--group.is-open .dt-chev { transform: rotate(90deg); color: var(--pink); }
.dt-row--sub > td { background: #FCFAFB; }  /* same quiet band as --zebra */
.dt-row--sub > td.td-dim {
  padding-left: 30px; color: var(--ink-2); font-size: 12px; font-weight: 400;
}
/* 3° livello (Innovations All · Country › Brand › Track, 2026-06-07): banda
   piu' profonda + indent maggiore. Sort/paginazione restano sull'aggregato. */
.dt-row--sub2 > td { background: #FBF8FA; }
.dt-row--sub2 > td.td-dim { padding-left: 52px; color: var(--ink-2); font-weight: 400; }

/* ============ STATUS DOTS · glifo multi-brand (Innovations All) ============
   Un pallino per brand, colore = stato vs piano attivo del brand. Il testo lo
   da' il tooltip canonico (tip_status_brands). Mai a capo. */
.status-dots { display: inline-flex; align-items: center; gap: 4px; cursor: help; }
.status-dot {
  width: 8px; height: 8px; border-radius: 50%; flex: 0 0 8px;
  box-shadow: 0 0 0 1.5px var(--paper);
}
.status-dot--up   { background: var(--success); }
.status-dot--warn { background: var(--warning); }
.status-dot--down { background: var(--error); }
.status-dot--neu  { background: var(--ink-3); }

/* ============ TIMING LANE · Launch → pickup (Innovations All) ============
   Corsia Mar→Dic: tick vuoto = launch pianificato, tick pieno (colore-brand) =
   actual pickup, segmento --warning = drift. Tooltip canonico (tip_timing). */
.tl { position: relative; width: 160px; height: 22px; cursor: help; }
.tl-axis { position: absolute; left: 0; right: 0; top: 10px; height: 2px;
  background: var(--hairline); border-radius: 2px; }
.tl-seg { position: absolute; top: 10px; height: 2px; background: var(--warning);
  border-radius: 2px; }
.tl-plan { position: absolute; top: 6px; width: 9px; height: 9px; border-radius: 50%;
  background: var(--paper); border: 1.5px solid var(--ink-3); transform: translateX(-50%); }
.tl-act  { position: absolute; top: 6px; width: 9px; height: 9px; border-radius: 50%;
  transform: translateX(-50%); }

/* ============ ACHIEVEMENT value · bar + cifra (Innovations) ============
   `.ach-cell` (bar) + cifra actual/plan mono a fianco. */
.ach-row { display: flex; align-items: center; gap: 12px; }
.ach-row .ach-cell { width: 190px; max-width: 190px; flex: 0 0 190px; }
.ach-val { font-family: var(--font-mono); font-size: 12px; color: var(--ink-2);
  font-variant-numeric: tabular-nums; white-space: nowrap; }
.ach-val b { color: var(--ink); font-family: var(--font-mono-heavy); font-weight: 700; }

/* Brand glyph nelle righe expand (dot colore chart-palette + label mono). */
.brand-dot { display: inline-block; width: 7px; height: 7px; border-radius: 50%;
  margin-right: 6px; flex: 0 0 7px; vertical-align: 0; }
.brand-dot--wnw  { background: var(--c3); }
.brand-dot--pf   { background: var(--c4); }
.brand-dot--lsmk { background: var(--c9); }
.brand-glyph-label { font-family: var(--font-mono-heavy); font-weight: 700;
  font-size: 10px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--ink-2); }
/* Mese CAPS per le righe expand (launch → actual esatto). */
.tl-month { font-family: var(--font-mono-heavy); font-weight: 700; font-size: 11.5px;
  letter-spacing: 0.06em; text-transform: uppercase; color: var(--ink-2);
  font-variant-numeric: tabular-nums; }
.tl-month--none { color: var(--ink-3); font-weight: 500; }
.tl-arrow { color: var(--ink-3); margin: 0 5px; font-family: var(--font-mono); }

/* ============ RICH CELL TOOLTIP · ink-dark, mono, label LEFT / value RIGHT ====
   Per i glifi della vista All (status-dots, timing lane, achievement): il glifo
   nella cella, le PAROLE in un hover HTML ricco (pallini colorati, split per
   brand, divider) — non esprimibili con un data-tooltip testuale. Stesso
   contratto di ogni tooltip Atlas (ink bg, white, mono). Reveal su hover/focus. */
.tooltip-rich { position: relative; }
.tooltip-rich > .tt-pop {
  display: none; position: absolute; bottom: calc(100% + 9px); left: 0;
  /* font-mono-heavy (JetBrains Mono) = stessa famiglia del tooltip dei grafici
     Plotly (HOVERLABEL_DARK), così labels+valori sono pixel-allineati e NON
     rendono "strani" come con DM Mono (Andrea 2026-06-08). */
  background: var(--ink); color: #fff; font-family: var(--font-mono-heavy);
  font-weight: 400;
  font-size: 12px; line-height: 1.55; padding: 11px 14px;
  border-radius: var(--radius-sm); white-space: nowrap; text-align: left;
  box-shadow: var(--shadow-menu); z-index: 60; min-width: 200px; pointer-events: none;
}
/* `!important` per battere il `display:none` INLINE di fallback sul .tt-pop
   (robustezza: se questo CSS non è ancora caricato, l'inline tiene il tooltip
   nascosto invece di dumparlo come testo nella cella). */
.tooltip-rich:hover > .tt-pop,
.tooltip-rich:focus-visible > .tt-pop { display: block !important; }
.tt-eye { font-family: var(--font-mono-heavy); font-weight: 700; font-size: 10px;
  letter-spacing: 0.14em; text-transform: uppercase; color: var(--pink-light);
  margin-bottom: 6px; }
.tt-row { display: flex; justify-content: space-between; gap: 22px; }
.tt-lbl { color: rgba(255,255,255,0.62); }
.tt-val { color: #fff; font-weight: 500; font-variant-numeric: tabular-nums;
  font-family: var(--font-mono-heavy); }
.tt-open { color: var(--open-edge); }
.tt-div { height: 1px; background: rgba(255,255,255,0.16); margin: 7px 0; }
.tt-sub { color: rgba(255,255,255,0.45); font-size: 10px; letter-spacing: 0.1em;
  text-transform: uppercase; margin: 2px 0 4px; }
.tt-dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%;
  margin-right: 6px; vertical-align: 0; }

/* Drift cell — plan-version pickup drift, `Apr → Jul · +3 mo` (Innovations V2).
   Mono, quiet; the drift amount is amber (--warning-deep): a slipped plan is
   a schedule signal in the provisional/forward family, NOT an invoiced loss —
   cherry stays reserved for actual gaps. */
.td-drift { font-family: var(--font-mono); font-size: 11.5px; color: var(--ink-2); }
.td-drift b { color: var(--warning-deep); font-weight: 600; }

.delta-up   { color: #3F5F49; font-family: var(--font-mono-heavy); }
.delta-down { color: var(--cherry-deep); font-family: var(--font-mono-heavy); }

/* Clickable rows — master/detail navigation. */
.kpi-table-clickable tbody tr { cursor: pointer; }
.kpi-table-clickable tbody tr:hover { background: var(--pink-tint); }
.kpi-table-clickable tbody tr.brand-cat-row-selected {
  background: var(--pink-tint); box-shadow: inset 3px 0 0 var(--pink);
}
.brand-cat-row-chev { transition: transform var(--motion-slow); color: #d1d5db; }
.brand-cat-row-selected .brand-cat-row-chev { color: var(--pink); transform: rotate(90deg); }

/* Bordered variant — opt-in for STANDALONE tables (not living inside a card).
   The seamless behaviour is now the default, so this is the rare case. */
.data-table--bordered,
.kpi-table--bordered {
  background: white;
  border: 1px solid var(--hairline);
  border-radius: var(--radius-lg);
  overflow: hidden;
}

/* DEPRECATED: --seamless is now a no-op (seamless became the default).
   Kept as an inert class so existing Atlas call sites don't break. */
.data-table--seamless,
.kpi-table--seamless { /* no-op */ }

/* Narrow variant — for tables with few columns / sparse data, where the
   default `width: 100%` creates a sprawling horizontal gap between the dim
   column and the right-aligned numbers (e.g. "Performance by Category" with
   3 rows × 3 cols inside a wide card). Hugs content; dim column collapses
   to its longest cell + padding. */
.data-table--narrow,
.kpi-table--narrow {
  width: auto; max-width: 100%; table-layout: auto;
}
.data-table--narrow .td-dim,
.kpi-table--narrow  .td-dim { width: 1%; white-space: nowrap; padding-right: 32px; }

/* Zebra variant — for tables with many rows (≥ 8), where the eye needs help
   tracking a row across wide horizontal layouts. Every other row gets a 1%
   tinted background so the band is visible but not striping-loud. Use it on
   dense ranked lists, country scorecards, large SKU tables. */
.data-table--zebra tbody tr:nth-child(even) td,
.kpi-table--zebra  tbody tr:nth-child(even) td { background: #FCFAFB; }

/* Breathing variant — for HALF-SCREEN non-product tables (Performance by
   Category, Top 10 Markets) with 2-3 short-value columns. The default
   hug-content behaviour (Round 5 width:1%) makes the columns cluster on
   the right with empty space on the left; here we want them to distribute
   evenly across the panel width — numbers breathe, the eye scans
   comfortably. Override width:1% to auto. Keep nowrap. */
.data-table--breathe, .kpi-table--breathe { table-layout: fixed; }  /* equal-column distribution for half-page tables; combined with width:auto on cells, columns share the container evenly */
.data-table--breathe td.num,
.data-table--breathe td.td-num,
.data-table--breathe td.td-dim,
.data-table--breathe td.td-value,
.data-table--breathe td.td-num__delta,
.kpi-table--breathe  td.num,
.kpi-table--breathe  td.td-num,
.kpi-table--breathe  td.td-dim,
.kpi-table--breathe  td.td-value,
.kpi-table--breathe  td.td-num__delta { width: auto; }

/* Inline progress cell — used in tracker tables (SKU Launch Tracker) where a
   "progress" column consolidates "actual / target / conv%". A row of mono
   numbers (actual · conv% sage · "of pipeline" ink-3) sits above a 6px
   bullet bar whose width = conv %. Replaces 3 columns (pipeline / actual /
   conv) with 1 dense, scannable cell. */
.cell-progress {
  display: flex; flex-direction: column; gap: 5px;
  min-width: 180px;
}
.cell-progress .nums {
  display: flex; justify-content: space-between; align-items: baseline;
  font-family: var(--font-mono); font-size: 11.5px; line-height: 1;
}
.cell-progress .nums .actual { color: var(--ink); font-weight: 500; }
.cell-progress .nums .conv   { color: #3F5F49; font-weight: 500; }  /* AAA sage for the on-track conv %; switch to var(--warning-deep) or cherry-deep for warn/down rows */
.cell-progress .nums .pipe   { color: var(--ink-3); }
.cell-progress .bar {
  position: relative; width: 100%; height: 6px;
  border-radius: var(--radius-pill); background: #f3f1f7;
}
.cell-progress .bar .fill {
  position: absolute; left: 0; top: 0; bottom: 0;
  border-radius: var(--radius-pill); background: var(--pink);
}
.cell-progress .bar .fill.warn { background: var(--pink-light); }
.cell-progress .bar .fill.down { background: var(--cherry); opacity: 0.85; }

/* Table strip — wrapper above the table for "title + subtitle | filters".
   Flex-wrap so filters drop cleanly to a new line when narrow, instead of
   pushing the title or breaking the layout. */
.data-table-strip {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 14px; flex-wrap: wrap;
  margin-bottom: 10px;
}
.data-table-strip > :last-child {
  display: flex; gap: 8px; flex-wrap: wrap; align-items: center;
}

/* ============ TOAST ============ */
/* Promoted T2 -> T1 (#A1 admin panel / issue #107): admin quick-action
   callbacks render success/error feedback via .toast (drop-in shape from
   the brand book). Tabs & breadcrumbs ship in the same batch since the
   admin shell uses canonical .tabs. */
.toast {
  display: flex; align-items: flex-start; gap: 12px;
  background: white; border: 1px solid var(--hairline);
  border-radius: 10px; padding: 14px 16px;
  box-shadow: var(--shadow-toast); min-width: 320px; max-width: 420px;
}
.toast .dot { width: 10px; height: 10px; border-radius: 50%; flex-shrink: 0; margin-top: 6px; }
.toast .title { font-weight: 600; font-size: 14px; }
.toast .body { font-size: 13px; color: var(--ink-2); margin-top: 2px; }
.toast.success .dot { background: var(--success); }
.toast.warning .dot { background: var(--warning); }
.toast.error   .dot { background: var(--error); }
.toast.info    .dot { background: var(--info); }

/* ============ EMPTY STATE (simple) ============ */
.empty {
  border: 1px dashed var(--pink-light); border-radius: var(--radius-lg);
  padding: 40px 32px; text-align: center; background: var(--surface);
}
.empty .ic { font-size: 36px; margin-bottom: 12px; }
.empty h4 { font-family: var(--font-wordmark); color: var(--pink); font-weight: 400; font-size: 22px; margin: 0 0 6px; }
.empty p { margin: 0 auto 16px; font-size: 13.5px; max-width: 360px; }

/* ============ TABS ============ */
.tabs { display: flex; gap: 0; border-bottom: 1px solid var(--hairline); }
.tabs button {
  font-family: var(--font-sans); font-weight: 500; font-size: 14px;
  padding: 12px 16px; border: 0; background: transparent; color: var(--ink-3);
  cursor: pointer; border-bottom: 2px solid transparent; margin-bottom: -1px;
}
.tabs button:hover { color: var(--ink); }
.tabs button.is-active { color: var(--pink-deep); border-bottom-color: var(--pink); }

/* ============ BREADCRUMBS ============ */
.breadcrumbs {
  display: flex; align-items: center; gap: 6px;
  font-family: var(--font-mono-heavy); font-size: 11px;
  letter-spacing: 0.14em; text-transform: uppercase;
}
.breadcrumbs a { color: var(--pink-deep); text-decoration: none; }
.breadcrumbs .sep { color: var(--ink-3); }
.breadcrumbs .current { color: var(--ink); font-weight: 700; }

/* ============ TOP LOADING BAR (§5.7) ============
   Linear-growth progressive: width 0%->95% in ~2.5s con easing out, hold
   a 95%. Pattern GitHub/Stripe: la barra cresce in modo percepibile
   mentre il callback e' in volo; quando dcc.Loading rimuove il
   custom_spinner dal DOM (callback completato), la barra sparisce. */
.page-loading-bar {
  position: fixed; top: 0; left: 0; right: 0; height: 3px;
  background: #f3f4f6; z-index: 9999; pointer-events: none; overflow: hidden;
}
.page-loading-bar-fill {
  position: absolute; inset: 0 auto 0 0; width: 0%;
  background: linear-gradient(90deg, var(--pink) 0%, var(--pink) 88%, transparent 100%);
  animation: pageLoadingProgress 2.5s cubic-bezier(.2, .6, .3, 1) forwards;
}
@keyframes pageLoadingProgress {
  0%   { width: 0%; }
  30%  { width: 55%; }
  60%  { width: 82%; }
  100% { width: 95%; }
}

/* ============ EMPTY / ERROR STATES (Batch 1 — empty-states.html) ============
   Promoted T2 -> T1 (#A1 admin panel / issue #107): the admin "not
   authorised" view + every "external API unavailable" state use
   .empty-block / .empty-inline from the brand book. Atlas's older
   .empty-state helper is now an alias that can be migrated in a later
   pass; no need to keep the canon stripped to preserve the alias.
   Page-level block + inline strip. Copy rules: state the cause, offer one
   next move, no emoji / no apologies. */
.empty-block {
  display: flex; flex-direction: column; align-items: center; gap: 10px;
  text-align: center; max-width: 360px;
}
.empty-block .ic {
  width: 44px; height: 44px; border-radius: var(--radius-lg);
  display: flex; align-items: center; justify-content: center;
  background: var(--pink-tint); color: var(--pink-deep);
}
.empty-block.warn .ic    { background: var(--warning-tint); color: var(--warning); }
.empty-block.error .ic   { background: #fee2e2; color: var(--cherry-deep); }
.empty-block.neutral .ic { background: #f3f4f6; color: var(--ink-3); }
.empty-block .ic svg { width: 22px; height: 22px; }
.empty-block .h { font-family: var(--font-wordmark); font-weight: 400; color: var(--ink); font-size: 20px; line-height: 1.1; }
.empty-block .p { font-size: 13px; color: var(--ink-2); line-height: 1.5; }
.empty-block .actions { display: flex; gap: 8px; margin-top: 8px; }

/* Inline strip — compact empty state inside a chart / table container. */
.empty-inline {
  padding: 22px 28px; border: 1px dashed var(--hairline); border-radius: 10px;
  display: flex; align-items: center; gap: 16px; max-width: 460px;
}
.empty-inline .ic {
  width: 36px; height: 36px; border-radius: 10px; flex: 0 0 36px;
  background: var(--pink-tint); color: var(--pink-deep);
  display: flex; align-items: center; justify-content: center;
}
.empty-inline .ic svg { width: 18px; height: 18px; }
.empty-inline .text { flex: 1; font-size: 13px; color: var(--ink-2); line-height: 1.45; }
.empty-inline .text strong { color: var(--ink); display: block; margin-bottom: 2px; font-size: 13.5px; font-weight: 600; }
.empty-inline .text a { color: var(--pink-deep); text-decoration: none; font-weight: 500; }

/* ============ BANNER — provisional / cached / inline alert ============
   Above-content strip for "data is incomplete, not missing". Also the
   canonical home for the Atlas inline-alert cases (TODO_COWORK C1:
   unmapped licences, stock unavailable).
   Base style = warning. Use modifier classes for info / success / error.
   All four text colours pass WCAG AA on their tinted backgrounds. */
.banner {
  display: flex; align-items: center; gap: 12px;
  padding: 10px 14px; border-radius: var(--radius-md);
  background: var(--warning-tint); border: 1px solid #f3dba0;
  font-size: 13px; color: #7a5a14;
}
.banner.error   { background: #fef0f1; border-color: #f4c0c0; color: var(--cherry-deep); }
.banner.info    { background: #e2f3f8; border-color: #b0cdd2; color: #1F4F5A; }
.banner.success { background: #e5ece7; border-color: #c5d4cb; color: #3F5F49; }
.banner svg { width: 18px; height: 18px; flex: 0 0 18px; }
.banner .b-title { font-weight: 600; }
.banner .b-body { color: rgba(0,0,0,0.6); }

/* ============ SKELETON (shimmer loading placeholder) ============
   Promoted T2 -> T1 (#A1 admin panel / issue #107): the admin first-paint
   uses .kpi-card--skeleton + .sk before the 30s poll returns. Atlas's
   older .atlas-kpi-skel becomes an alias migrated in a later pass.
   .sk is the shimmer primitive — size it with .sk-* helpers or inline.
   Use for < 800ms loads; the page-loading-bar covers longer ones. */
@keyframes shimmer {
  0%   { background-position: -200px 0; }
  100% { background-position: 220px 0; }
}
.sk {
  background: var(--skeleton-base);
  background-image: linear-gradient(90deg, var(--skeleton-base) 0px, var(--skeleton-highlight) 80px, var(--skeleton-base) 160px);
  background-size: 280px 100%;
  animation: shimmer var(--motion-shimmer) linear infinite;
  border-radius: var(--radius-sm);
}
@media (prefers-reduced-motion: reduce) { .sk { animation: none; } }

/* KPI card skeleton — a real .kpi-card shell in loading state. Drop it
   into a .kpi-row grid and it inherits the EXACT width of a .kpi-card.
   Three .sk bars inside = eyebrow / value / delta. Never hand-size a
   KPI loader. */
.kpi-card--skeleton {
  background: var(--paper); border: 1px solid var(--hairline);
  border-radius: var(--radius-lg); padding: 18px 20px;
  display: flex; flex-direction: column; gap: 10px;
}
.kpi-card--skeleton .sk-label { height: 8px; width: 55%; }
.kpi-card--skeleton .sk-value { height: 24px; width: 78%; }
.kpi-card--skeleton .sk-delta { height: 8px; width: 38%; }

/* ===== CHART SKELETON (area/bar chart loading — opacity pulse, NOT shimmer) =====
   Lo shimmer a scala chart si spezza → le barre pulsano opacity. Gridlines e
   radius da token. Base barra = --skeleton-base (shared con .sk). Pulse sfalsato
   (animation-delay per barra) lo setta il loader inline. */
@keyframes atlasChartBarPulse { 0%, 100% { opacity: .5 } 50% { opacity: 1 } }
.atlas-chart-skel { position: relative; width: 100%; min-height: 300px;
  padding: 18px 24px 30px 46px; box-sizing: border-box; pointer-events: none; }
.atlas-chart-skel-grid { position: absolute; left: 46px; right: 24px; height: 0;
  border-top: 1px dashed var(--hairline); }
.atlas-chart-skel-grid--strong { border-top: 1px solid var(--hairline); }
.atlas-chart-skel-bars { position: absolute; top: 18px; bottom: 30px; left: 46px;
  right: 24px; display: flex; align-items: flex-end; gap: 10px; }
.atlas-chart-skel-bar { flex: 1; background: var(--skeleton-base);
  border-top-left-radius: var(--radius-sm); border-top-right-radius: var(--radius-sm);
  animation: atlasChartBarPulse 1.2s ease-in-out infinite; transform-origin: bottom; }
.atlas-chart-skel--donut { display: flex; align-items: center; justify-content: center;
  min-height: 260px; padding: 16px; }
.atlas-chart-skel-donut { width: 168px; height: 168px; border: 24px solid var(--skeleton-base);
  border-radius: 50%; animation: atlasChartBarPulse 1.2s ease-in-out infinite; }
@media (prefers-reduced-motion: reduce) {
  .atlas-chart-skel-bar, .atlas-chart-skel-donut { animation: none; } }

/* ============ FILTER SYSTEM (Batch 2 — filters.html) ============
   The global filter bar + its popovers + the applied-filter pill row +
   saved-views dropdown. Already token-based for colour; literal radii and
   the `#fbfbfc` panel tint are left as-is (tokenise in a later pass).
   NOTE: `.time-pop .compare-row .switch` is a scoped local switch —
   reconcile with the canonical `.switch` later.

   Promoted from T2 to T1 (Shell v3, 26 May 2026): Atlas now uses the
   canonical filter system end-to-end — .filter-bar, .fb-chip, .fb-search,
   .popover/.popover--filters/.ph/.pb/.pf and .pb-row--group/.ph-back are
   all live in the Atlas header bar + Filters drill-in popover (PR #158).
   The legacy "parallel filter pill system" note no longer holds. */

/* ----- Filter bar ----- */
.filter-bar {
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px;
}
.fb-label {
  font-family: 'JetBrains Mono', monospace; font-size: 10px;
  letter-spacing: 0.22em; text-transform: uppercase; color: var(--ink-3);
  margin-right: 4px;
}
.fb-chip {
  display: inline-flex; align-items: center; gap: 6px;
  height: 32px; padding: 0 10px; border-radius: 8px;
  border: 1px solid var(--hairline); background: white;
  font-family: 'DM Sans', sans-serif; font-size: 13px; color: var(--ink);
  cursor: pointer;
  transition: border-color var(--motion-base), transform 100ms var(--motion-ease-out);
}
.fb-chip:active { transform: scale(0.97); }
.fb-chip:hover { border-color: var(--pink-light); }
.fb-chip.active {
  background: var(--pink-tint); border-color: var(--pink-light);
  color: var(--pink-deep); font-weight: 500;
}
.fb-chip .lbl {
  font-family: 'JetBrains Mono', monospace; font-size: 10px;
  letter-spacing: 0.18em; text-transform: uppercase;
  color: var(--ink-3); padding-right: 4px; border-right: 1px solid var(--hairline);
  margin-right: 4px;
}
.fb-chip.active .lbl { color: var(--pink-deep); border-right-color: rgba(214,63,126,0.2); }
.fb-chip .count {
  background: var(--pink); color: white; border-radius: 999px;
  font-family: 'JetBrains Mono', monospace; font-size: 10px; font-weight: 700;
  padding: 1px 6px; min-width: 16px; text-align: center;
}
.fb-chip .caret { color: var(--ink-3); font-size: 9px; }
/* Disabled chip — fixed-scope pages (scope canon · Innovations v9 · 2026-06-04).
   When a page does NOT subscribe to the global Time/Filters scope, the chips
   it ignores render disabled (gray, inert) and the .scope-badge docks in the
   header bar immediately AFTER them — the badge is never floating in page
   content. Chips that DO apply to the page (e.g. the Brand chip on
   Brand·Innovations) stay active. The title="" on the badge keeps carrying
   the real fixed scope. */
/* Runtime: il chip è disabilitato via prop `disabled` sul <button> (blocco
   funzionale — `pointer-events:none` impedisce l'apertura del popover DMC);
   `.is-disabled` resta come variante statica equivalente. */
.fb-chip.is-disabled,
.fb-chip:disabled {
  cursor: not-allowed;
  pointer-events: none;
  background: var(--paper);
  color: var(--ink-3);
  border-color: var(--hairline);
  opacity: 0.65;
}
.fb-chip.is-disabled:hover,
.fb-chip:disabled:hover { border-color: var(--hairline); }
.fb-chip.is-disabled:active,
.fb-chip:disabled:active { transform: none; }
.fb-chip.is-disabled .lbl,
.fb-chip:disabled .lbl { color: var(--ink-3); }
.fb-chip.is-disabled .caret,
.fb-chip:disabled .caret { opacity: 0.4; }
/* Scope-badge dockata nella header bar, subito dopo i chip che sovrascrive. */
.header-scope-slot { display: inline-flex; align-items: center; }
.header-scope-slot:empty { display: none; }
.fb-divider {
  width: 1px; height: 20px; background: var(--hairline); margin: 0 4px;
}
.fb-search {
  flex: 1; min-width: 220px; max-width: 360px;
  display: inline-flex; align-items: center; gap: 8px;
  height: 32px; padding: 0 10px; border-radius: 8px;
  border: 1px solid var(--hairline); background: white;
}
.fb-search input {
  flex: 1; border: 0; outline: 0; font-family: inherit; font-size: 13px;
  color: var(--ink); background: transparent;
}
.fb-search input::placeholder { color: var(--ink-3); }
/* Icon: inline <svg> OPPURE lo sprite dell'Atlas Icon helper
   (<span class="atlas-icon … fb-search-icon">, CSS-mask + currentColor). Stesso
   trattamento ink-3 14px così `.fb-search` è canon ovunque, non solo
   nell'header (prima la regola targettava solo <svg> → lo sprite restava
   md/scuro fuori dall'header). */
.fb-search svg,
.fb-search .fb-search-icon {
  color: var(--ink-3); width: 14px; height: 14px; flex: 0 0 14px;
}
/* Focus ring — Shell v3 spotlight canonization. Matches the input-family
   3px pink halo, sized at 22% so it sits a touch quieter than the .input
   25% (the bar field shares header chrome with chips and icon-buttons; 22%
   reads as "active here" without dominating the chip neighbours). Uses
   :focus-within so the ring lights up when the inner <input> takes focus. */
.fb-search:focus-within {
  border-color: var(--pink);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--pink) 22%, transparent);
}
.fb-actions { margin-left: auto; display: inline-flex; gap: 6px; }
.fb-ghost {
  height: 32px; padding: 0 12px; border-radius: 8px;
  background: transparent; border: 1px solid var(--hairline);
  color: var(--ink-3); font-family: inherit; font-size: 12.5px; font-weight: 500;
  cursor: pointer;
}
.fb-ghost:hover { background: var(--pink-tint); color: var(--pink-deep); border-color: var(--pink-light); }

/* ============ DATA-FRESHNESS CHIP + POPOVER (header dock) ============
   CODE-PING-data-freshness-chip (Proposta A rev 3, Andrea 2026-07-08).
   Chip INFORMATIVO a sinistra dell'avatar: dice a quando è aggiornato il
   dato vendite servito (Today / Yesterday / giorno reale). NON è un'azione —
   non deve mai leggersi come il vecchio bottone "Refresh" (rimosso 2026-07-03).
   Principio: QUIET di default, warn/down SOLO l'eccezione (dato in ritardo).
   Compone solo token esistenti; l'hex literal amber/red è il mirror 1:1 della
   palette canonica `.status-pill-warn` / `.status-pill-down` (stesso hex,
   stesso motivo AA-on-bg) → atlas-ignore, non è drift. Sorgente: il mockup
   `brand-mockups/data-freshness-proposals.html`. */
.data-chip {
  display: inline-flex; align-items: center; gap: 7px;
  height: 32px; padding: 0 12px;               /* allineato ai chip fratelli (.fb-chip 32px) */
  border-radius: 999px;                         /* pill = status readout, non un dropdown */
  background: white; border: 1px solid var(--hairline);
  cursor: pointer; white-space: nowrap;
  transition: background var(--motion-fast), border-color var(--motion-fast);
}
/* Hover QUIET — mai bordo pink: il chip è informativo, non deve leggersi come
   un bottone (né come il vecchio Refresh). Solo un velo di superficie. */
.data-chip:hover { background: var(--surface); }
.data-chip::before {
  content:''; width:6px; height:6px; border-radius:50%;
  background: var(--success); flex:0 0 6px;          /* UPDATED = dot verde */
}
/* Il chip è BINARIO (Andrea 2026-07-08): fuori solo lo stato sintetico
   `UPDATED` (verde) / `NOT UPDATED` (giallo); il Today/Yesterday per-dataset
   vive nel dropdown. Il timestamp completo resta nel tooltip hover. */
.data-chip .dc-status { font-family: var(--font-mono-heavy); font-size:10px; font-weight:700; letter-spacing:.12em; text-transform:uppercase; color: var(--success-deep); }
/* NOT UPDATED — l'eccezione (giallo): il dato vendite non è aggiornato oggi.
   Mirror della palette canonica .status-pill-warn. */
.data-chip--stale { background:var(--warning-tint); border-color: var(--open-edge); }
.data-chip--stale:hover { background:var(--warning-tint); }                              /* niente velo grigio sopra l'amber */
.data-chip--stale::before { background: var(--open-edge); }
.data-chip--stale .dc-status { color: var(--warning-deep); }

/* freshness popover body (composes canonical .popover).
   NB specificità: padding+width vanno su `.popover.fresh-pop` (0,2,0) per
   BATTERE la `.mantine-Popover-dropdown` (0,1,0) del portale DMC, che
   altrimenti azzera la padding del dropdown → contenuto a filo del bordo,
   titolo clippato dall'angolo arrotondato. Larghezza tarata sul contenuto
   reale (righe corte) per togliere lo spazio morto nome↔valore. */
.popover.fresh-pop { width: 256px; min-width: 0; padding: 16px 16px 14px; }   /* min-width:0 sblocca il floor 280px di .popover — righe corte, popover snello */
.fresh-pop .fp-title { font-family: var(--font-mono-heavy); font-size:10px; font-weight:700; letter-spacing:.16em; text-transform:uppercase; color: var(--ink-3); margin-bottom:12px; }
.fresh-pop .fp-row { display:flex; align-items:center; gap:9px; padding:7px 0; font-size:12.5px; color: var(--ink); }
.fresh-pop .fp-row + .fp-row { border-top:1px solid var(--hairline); }
.fresh-pop .fp-row .nm { flex:1; font-weight:500; }
.fresh-pop .fp-row .ts { font-family: var(--font-mono); font-size:11px; color: var(--ink-2); }
.fresh-pop .fp-dot { width:6px; height:6px; border-radius:50%; background: var(--success); flex:0 0 6px; }
.fresh-pop .fp-dot--warn { background: var(--open-edge); }
.fresh-pop .fp-dot--down { background:#b91c1c; }   /* atlas-ignore — mirror .status-pill-down */
.fresh-pop .fp-dot--muted { background: var(--ink-3); }   /* dato non disponibile → dot neutro (mai verde) */
.fresh-pop .fp-foot { margin-top:12px; padding-top:12px; border-top:1px solid var(--hairline); font-size:12px; }
.fresh-pop .fp-foot a { color: var(--pink-deep); font-weight:600; text-decoration:none; }

/* ============ POPOVER · OPTION LIST (Part F follow-up · BrandChip ext) ============
   Generic single-select option list for focused popovers (BrandChip,
   future PageMenu, future ViewSwitch). Namespace-free sibling of
   `.time-pop .preset` — the latter is scoped under .time-pop because
   it ships with extra Time-popover spacing; this is the generic one.

   Each row: label SX (sans 13px) + key DX (mono 10px ink-3).
   Active row gets pink-tint bg + pink-deep text — same active treatment
   as `.time-pop .preset.active` for visual consistency.

   Composition:
     <div class="popover-option-list">
       <button class="popover-option [is-active]">
         <span class="popover-option__label">Wet n Wild</span>
         <span class="popover-option__key">WNW</span>
       </button>
       ...
     </div>
*/
.popover-option-list {
  display: flex; flex-direction: column; padding: 6px;
  min-width: 220px;
}
.popover-option {
  display: flex; justify-content: space-between; align-items: center;
  gap: 14px;
  padding: 8px 12px; border-radius: 6px; cursor: pointer;
  background: transparent; border: 0;
  font-family: 'DM Sans', sans-serif; font-size: 13px; color: var(--ink);
  text-align: left; width: 100%;
}
.popover-option:hover { background: var(--surface); }
.popover-option.is-active {
  background: var(--pink-tint); color: var(--pink-deep); font-weight: 500;
}
/* Tier ④ (menu row label) — see canon comment on .popover .pb-row .name
   below. Sans 13px regular, natural case. The flex grow rule is the
   only thing this needs; typography inherits from the .popover-option
   parent (which inherits body sans). */
.popover-option__label { flex: 1; }
/* Tier ⑤ (row meta) — single canonical treatment for the right-side
   mono accessory in any popover/palette row: 11px / 500 / mono / ink-3
   / tabular-nums. Natural case — sigle ("WNW", "LTM") render uppercase
   because the data is uppercase, not because the CSS forces it. Was
   10px / 700 / 0.12em uppercase pre-2026-05-28 pass 3 — pulled into the
   pass-3 canon: mono distinguishes etichetta from contenuto, the data
   keeps its natural case. */
.popover-option__key {
  font-family: 'JetBrains Mono', monospace; font-weight: 500;
  font-size: 11px; color: var(--ink-3); flex: 0 0 auto;
  font-variant-numeric: tabular-nums;
}
.popover-option.is-active .popover-option__key { color: var(--pink-deep); }

/* ----- Popover (multiselect) ----- */
.popover-host { position: relative; display: inline-block; }
/* Modifier — make the host a flex item that grows to fill the row instead
   of hugging its content. Use it when the host wraps a field that must
   stretch (the Hybrid bar spotlight: .fb-search lives inside .popover-host
   and needs to take the bar's remaining width). Pure positioning shim —
   never an inline style on the host. */
.popover-host--grow { display: flex; flex: 1; }
.popover {
  position: absolute; top: calc(100% + 6px); left: 0;
  background: white; border: 1px solid var(--hairline);
  border-radius: 12px; min-width: 280px;
  box-shadow: 0 16px 40px rgba(10,10,15,0.12), 0 6px 12px rgba(10,10,15,0.06);
  z-index: 10; overflow: hidden;
}
.popover .ph {
  padding: 12px 14px 8px; border-bottom: 1px solid var(--hairline);
  display: flex; flex-direction: column; gap: 8px;
}
.popover .ph-title {
  font-family: 'JetBrains Mono', monospace; font-weight: 700;
  font-size: 10px; letter-spacing: 0.18em; text-transform: uppercase;
  color: var(--ink-3);
}
.popover .ph-search {
  display: flex; align-items: center; gap: 6px;
  background: var(--surface); border-radius: 6px; padding: 6px 8px;
}
.popover .ph-search input {
  flex: 1; border: 0; outline: 0; font-family: inherit; font-size: 13px;
  color: var(--ink); background: transparent;
}
.popover .ph-search input::placeholder { color: var(--ink-3); }
.popover .ph-search svg { color: var(--ink-3); width: 12px; height: 12px; }
.popover .pb { max-height: 220px; overflow-y: auto; }
.popover .pb-row {
  display: flex; align-items: center; gap: 10px;
  padding: 8px 14px; cursor: pointer; font-size: 13px;
}
.popover .pb-row:hover { background: var(--surface); }
.popover .pb-row.checked { color: var(--ink); }
.popover .pb-row.checked .check {
  background: var(--pink); border-color: var(--pink);
  color: white;
}
.popover .pb-row.checked .check::after {
  content: ''; width: 4px; height: 8px; border: 1.5px solid white;
  border-top: 0; border-left: 0; transform: rotate(45deg) translate(-1px, -1px);
}
.popover .pb-row .check {
  width: 16px; height: 16px; border-radius: 4px;
  border: 1.5px solid var(--ink-3);
  display: inline-flex; align-items: center; justify-content: center;
  flex: 0 0 16px;
}
/* Tier ④ (menu row label) — canon Andrea 2026-05-28 pass 3.
   ALL row labels in ALL popovers (Filters, BrandChip, Time presets, any
   future single/multi-select popover) share this treatment: sans regular
   400, **natural case**, no letter-spacing — just plain sentence-case
   13px. The pass-1 + pass-2 uppercase + 0.06em rule was reverted on
   2026-05-28 (pass 3): with all-caps labels the menus read dated and
   crowded — "WET N WILD", "VIVIENNE COSMETICS", "LAST 12 MONTHS" all
   shout. Sentence case + mono/sans family distinction + ink-3/ink ink
   carry the hierarchy without typographic violence:
     - mono uppercase tracked = etichetta del chrome (eyebrow ① + ③)
     - sans natural case = contenuto (option row, chip value, etc.)
     - data with natural uppercase (sigle "WNW", "EMEA") render uppercase
       because the data is, not because the CSS forces it.
   Weight 400 keeps option rows lighter than the .ph-title (700 mono) +
   the .pf .btn-apply footer button (600 sans). See RULES.md
   §"Popover & menu surface — typography canonical". */
.popover .pb-row .name {
  flex: 1;
}
/* Tier ⑤ (row meta) — canonical right-side mono accessory in any
   popover row. Used for the value-shorthand on a group row ("All",
   "WNW", "Italy"), the count on a checkable row ("1,284"), the preset
   key on a time-pop row ("LTM"). Mono 500 · 11px · natural case ·
   ink-3 · tabular-nums. Sigle uppercase (WNW, LTM, EMEA) render
   uppercase because the data is; sentence values ("All", "Italy",
   "3 selected") render as stored. The forced-uppercase + 0.10em
   tracking added in pass 2 was reverted 2026-05-28 pass 3: mono +
   ink-3 already distinguish this as "etichetta short", the CSS
   uppercase only added shouty visuals. */
.popover .pb-row .meta {
  font-family: 'JetBrains Mono', monospace; font-weight: 500;
  font-size: 11px; color: var(--ink-3);
  font-variant-numeric: tabular-nums;
}
.popover .pf {
  padding: 10px 14px; border-top: 1px solid var(--hairline);
  display: flex; gap: 8px; justify-content: space-between; align-items: center;
  background: #fbfbfc;
}
/* Tier ⑥ (footer copy) — the "0 filters active" / "3 selected" status
   line in the popover footer. Mono 500 · 11.5px · sentence case · ink-3 ·
   tabular-nums. Same canon as .palette .p-foot and .time-pop .compare-row,
   so every quiet-hint copy beneath a popover surface looks identical. */
.popover .pf .stats {
  font-family: 'JetBrains Mono', monospace; font-weight: 500;
  font-size: 11.5px; color: var(--ink-3);
  font-variant-numeric: tabular-nums;
}
.popover .pf .actions { display: flex; gap: 6px; }
.popover .pf .btn-clear {
  font-family: inherit; font-size: 12px; color: var(--ink-3);
  background: transparent; border: 0; cursor: pointer; padding: 6px 8px;
  border-radius: 6px;
}
.popover .pf .btn-clear:hover { background: var(--surface); color: var(--ink); }
.popover .pf .btn-apply {
  font-family: inherit; font-size: 12px; font-weight: 600; color: white;
  background: var(--ink); border: 0; cursor: pointer; padding: 6px 12px;
  border-radius: 6px;
}

/* ----- Consolidated Filters popover — grouped + master-detail drill-in ----- */
/* ONE .popover surface. The header / body / footer CONTENT swaps between the
   group list (root) and a group's multi-select (drilled in). Never nest a
   second .popover (with its own .pf footer) inside — that double-footer is
   the drift this pattern exists to kill. One .ph, one .pb, one .pf, always.

   Drill-in header (v2, 26 May 2026): `.ph-back` and `.ph-title` sit
   inline inside a `.ph-nav` row — chevron-icon + group title, single
   row. The previous v1 styled .ph-back as a mono-uppercase eyebrow
   which stacked above the .ph-title on a column .ph and read as a
   double-eyebrow drift. Now .ph-back is a 22×22 chevron affordance
   (sans-serif, no letter-spacing) and .ph-nav owns the inline
   layout. */
.popover--filters { min-width: 300px; }
.popover--filters .ph-nav {
  display: flex; align-items: center; gap: 10px;
}
.popover--filters .ph-back {
  width: 22px; height: 22px;
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--pink-text); background: transparent; border: 0;
  cursor: pointer; padding: 0; font-size: 16px; line-height: 1;
  border-radius: var(--radius-sm);
  font-family: var(--font-sans); font-weight: 600; letter-spacing: 0;
}
.popover--filters .ph-back:hover {
  background: var(--surface); color: var(--pink-deep);
}
.popover .pb-row--group { justify-content: space-between; }
/* font-weight 400 (was 500 pre-canonization 2026-05-28) — uppercase +
   letter-spacing inherited from .popover .pb-row .name above. */
.popover .pb-row--group .name { flex: 0 1 auto; color: var(--ink); font-weight: 400; }
/* Tier ⑤ continued — same canon as .pb-row .meta. The group row's
   right-side summary ("All", "Italy", "3 selected") shares the row-meta
   treatment so the root filter list reads as one ladder. Natural case
   2026-05-28 pass 3. */
.popover .pb-row--group .pb-val {
  margin-left: auto;
  font-family: 'JetBrains Mono', monospace; font-weight: 500;
  font-size: 11px; color: var(--ink-3);
  font-variant-numeric: tabular-nums;
}
.popover .pb-row--group .pb-val.is-set { color: var(--pink-text); }
.popover .pb-row--group .caret { color: var(--ink-3); font-size: 14px; line-height: 1; }

/* ----- Time popover -----
   Width canon (Andrea 2026-05-28): min-width 440px.
   Was 360px — too tight to fit the 8 canonical presets in 2 columns
   without wrapping ("Moving annual total · MAT" + "Last 12 months · LTM"
   need ~200px per cell to breathe). 440px is also the width Atlas
   already runs at via the legacy `.time-popover` dbc.Popover wrap in
   `assets/style.css`; bumping the canon aligns both surfaces, which is
   load-bearing for the Part-H DMC migration (PR 3 mounts `.time-pop`
   directly inside `dmc.Popover.Dropdown`, no outer wrap, so the inner
   width canon takes over). Preset list: 8 entries (LTM · YTD · L3M ·
   LQ · LM · LW · MAT · ∞) — the canonical vocabulary per Atlas pivot
   needs. */
.time-pop {
  position: absolute; top: calc(100% + 6px); left: 0;
  background: white; border: 1px solid var(--hairline);
  border-radius: 12px; padding: 0; min-width: 440px;
  box-shadow: 0 16px 40px rgba(10,10,15,0.12);
  z-index: 10; overflow: hidden;
}
.time-pop .presets {
  display: grid; grid-template-columns: 1fr 1fr; gap: 4px;
  padding: 10px;
}
/* Tier ④ (menu row label) — natural case sans 13px, same canon as
   .popover .pb-row .name. The pass-1 + pass-2 uppercase + 0.06em rule
   was reverted 2026-05-28 pass 3. The .preset row IS the menu item;
   no nested .name span, the rule lives directly on the row. */
.time-pop .preset {
  padding: 8px 12px; border-radius: 6px; cursor: pointer; font-size: 13px;
  display: flex; justify-content: space-between; align-items: center;
}
.time-pop .preset:hover { background: var(--surface); }
.time-pop .preset.active {
  background: var(--pink-tint); color: var(--pink-deep); font-weight: 500;
}
/* Tier ⑤ (row meta) — preset key on the right of a time-pop row
   ("LTM", "YTD", "MAT"). Mono 500 · 11px · natural case · ink-3 —
   same canon as every other popover row meta. The data is uppercase
   by convention (preset codes are short identifiers), the CSS no
   longer forces uppercase + tracking (pass 3 revert). Was 10px —
   bumped 2026-05-28 pass 2 to align all metas at 11px. */
.time-pop .preset .k {
  font-family: 'JetBrains Mono', monospace; font-weight: 500;
  font-size: 11px; color: var(--ink-3);
}
.time-pop .preset.active .k { color: var(--pink-deep); }
.time-pop .divider { height: 1px; background: var(--hairline); }
.time-pop .custom {
  padding: 12px; display: flex; gap: 8px; align-items: center;
}
.time-pop .custom input {
  flex: 1; height: 32px; padding: 0 10px; border-radius: 6px;
  border: 1px solid var(--hairline); font-family: 'JetBrains Mono', monospace;
  font-size: 12px; color: var(--ink); outline: 0;
}
.time-pop .custom input:focus { border-color: var(--pink); }
.time-pop .custom .arrow { color: var(--ink-3); }
/* Tier ⑥ (footer copy) — the "Compare to previous year" toggle row at
   the foot of the time popover. Same 11.5px / mono 500 / sentence case /
   ink-3 as .pf .stats and .palette .p-foot — every quiet-hint copy
   beneath a popover surface shares this treatment, so the bottom strip
   reads consistently across Time / Filters / Spotlight. Was 12.5px sans
   sentence case — pulled into the canon 2026-05-28 pass 2. */
.time-pop .compare-row {
  padding: 10px 12px; border-top: 1px solid var(--hairline);
  display: flex; gap: 10px; align-items: center;
  font-family: 'JetBrains Mono', monospace; font-weight: 500;
  font-size: 11.5px; color: var(--ink-3);
}
.time-pop .compare-row .switch {
  width: 32px; height: 18px; border-radius: 999px; background: var(--pink);
  position: relative; cursor: pointer;
}
.time-pop .compare-row .switch::after {
  content: ''; position: absolute; top: 2px; right: 2px;
  width: 14px; height: 14px; background: white; border-radius: 50%;
}

/* ----- Active filter bar (applied-chips row) ----- */
.active-bar {
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
  padding: 10px 14px; background: #fbfbfc; border: 1px solid var(--hairline);
  border-radius: 10px;
}
.active-bar .lbl {
  font-family: 'JetBrains Mono', monospace; font-size: 10px;
  letter-spacing: 0.18em; text-transform: uppercase; color: var(--ink-3);
  margin-right: 4px;
}
.pill {
  display: inline-flex; align-items: center; gap: 6px;
  height: 26px; padding: 0 8px 0 10px; border-radius: 999px;
  background: white; border: 1px solid var(--hairline);
  font-size: 12px; color: var(--ink);
}
.pill .group {
  font-family: 'JetBrains Mono', monospace; font-size: 10px;
  letter-spacing: 0.14em; text-transform: uppercase; color: var(--ink-3);
}
.pill .x {
  display: inline-flex; align-items: center; justify-content: center;
  width: 16px; height: 16px; border-radius: 50%; cursor: pointer;
  color: var(--ink-3);
}
.pill .x:hover { background: var(--pink-tint); color: var(--pink-deep); }
.pill.applied {
  background: var(--pink-tint); border-color: var(--pink-light);
  color: var(--pink-deep);
}
.pill.applied .group { color: var(--pink-deep); opacity: 0.8; }

/* ----- Saved-view dropdown ----- */
.saved-views {
  position: relative; display: inline-block;
}
.views-pop {
  position: absolute; top: calc(100% + 6px); right: 0;
  background: white; border: 1px solid var(--hairline);
  border-radius: 12px; min-width: 280px;
  box-shadow: 0 16px 40px rgba(10,10,15,0.12);
  overflow: hidden; z-index: 10;
}
.views-pop .vp-h {
  padding: 10px 14px; border-bottom: 1px solid var(--hairline);
  font-family: 'JetBrains Mono', monospace; font-size: 10px;
  letter-spacing: 0.18em; text-transform: uppercase; color: var(--ink-3);
}
.views-pop .vp-row {
  display: flex; align-items: center; gap: 10px;
  padding: 10px 14px; cursor: pointer; font-size: 13px;
}
.views-pop .vp-row:hover { background: var(--surface); }
/* Tier ④ (menu row label) — saved-view name shares the popover row
   canon: sans 13px regular, natural case. Pass 3 revert (2026-05-28). */
.views-pop .vp-row .name { flex: 1; }
.views-pop .vp-row.is-current .check { color: var(--pink); }
.views-pop .vp-row .check { width: 14px; color: transparent; font-size: 11px; }
/* Tier ⑤ (row meta) — mono 500 · 11px · natural case · ink-3 · tab-nums.
   Same canon as every other popover row meta. Pass 3 revert. */
.views-pop .vp-row .meta {
  font-family: 'JetBrains Mono', monospace; font-weight: 500;
  font-size: 11px; color: var(--ink-3);
  font-variant-numeric: tabular-nums;
}
.views-pop .vp-foot {
  padding: 10px 14px; border-top: 1px solid var(--hairline);
  display: flex; gap: 8px; justify-content: space-between;
  background: #fbfbfc;
}
.views-pop .vp-foot button {
  font-family: inherit; font-size: 12px; color: var(--pink-deep);
  background: transparent; border: 0; cursor: pointer;
}
.views-pop .vp-foot button:hover { text-decoration: underline; }

/* ============ DRAWER / SIDE-SHEET (Batch 3 — drawer.html) ============
   The slide-in side sheet. `.drawer` is the container; `.d-*` are its parts. */
.drawer {
  background: white; border-left: 1px solid var(--hairline);
  overflow-y: auto; display: flex; flex-direction: column;
  position: relative; z-index: 1;
  box-shadow: -8px 0 24px rgba(10,10,15,0.04);
}
.d-head {
  padding: 14px 18px; border-bottom: 1px solid var(--hairline);
  display: flex; align-items: center; gap: 10px;
  background: #fbfbfc; position: sticky; top: 0;
}
.d-head .crumb {
  font-family: 'JetBrains Mono', monospace; font-size: 10px;
  letter-spacing: 0.18em; text-transform: uppercase; color: var(--ink-3);
  flex: 1;
}
.d-head .actions { display: inline-flex; gap: 4px; }
.d-head .ic-btn {
  width: 28px; height: 28px; border-radius: 6px;
  border: 0; background: transparent; cursor: pointer;
  color: var(--ink-3); display: inline-flex; align-items: center; justify-content: center;
}
.d-head .ic-btn:hover { background: var(--surface); color: var(--ink); }
.d-title {
  padding: 16px 18px 8px; display: flex; align-items: flex-start; gap: 14px;
}
.d-title .thumb {
  width: 56px; height: 56px; border-radius: 8px;
  background: linear-gradient(135deg, #fff0f3 0%, #ffe5ee 100%);
  border: 1px solid var(--hairline); flex-shrink: 0;
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--pink-deep); font-size: 24px;
}
.d-title .meta { flex: 1; }
.d-title h4 {
  margin: 0 0 4px; font-family: 'DM Sans', sans-serif;
  font-size: 16px; line-height: 1.3; color: var(--ink); font-weight: 600;
}
.d-title .sku {
  font-family: 'JetBrains Mono', monospace; font-size: 11px; color: var(--pink-deep);
  background: var(--pink-tint); padding: 2px 6px; border-radius: 4px;
  letter-spacing: 0.04em;
}
.d-title .shade {
  font-style: italic; text-transform: uppercase;
  letter-spacing: 0.04em; color: #9ca3af; font-size: 11px;
}
/* Identity meta line — quiet brand · category · lifecycle line under the
   .d-title h4. Replaces a row of chips (review: chip overload). */
.d-title .d-meta {
  font-family: var(--font-mono); font-size: 11px; letter-spacing: 0.03em;
  color: var(--ink-3); margin-top: 5px; line-height: 1.5;
}
.d-title .d-meta b { color: var(--ink-2); font-weight: 700; }
.d-tabs {
  display: flex; gap: 0; padding: 0 18px;
  border-bottom: 1px solid var(--hairline);
}
.d-tabs button {
  font-family: 'DM Sans', sans-serif; font-weight: 500; font-size: 12.5px;
  padding: 10px 12px; border: 0; background: transparent;
  color: var(--ink-3); cursor: pointer;
  border-bottom: 2px solid transparent; margin-bottom: -1px;
}
.d-tabs button.active { color: var(--pink-deep); border-bottom-color: var(--pink); }
.d-body { padding: 16px 18px; flex: 1; }
/* .d-empty — the drawer BODY in a whole-modal empty / error state. Fills the
   space between .d-head and the static .d-foot and centres a single
   .empty-block (icon-on-top, centred headline/body/actions). Use this instead
   of dropping bare text into .empty-block. See modal-empty-error-states.html. */
.d-empty {
  flex: 1; display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  text-align: center; padding: 40px 28px;
}
.d-section { margin-bottom: 18px; }
.d-section h6 {
  font-family: 'JetBrains Mono', monospace; font-weight: 700;
  font-size: 9px; letter-spacing: 0.22em; text-transform: uppercase;
  color: var(--ink-3); margin: 0 0 8px;
}
.d-kpi {
  display: grid; grid-template-columns: 1fr 1fr; gap: 8px;
}
.d-kpi .c {
  background: white; border: 1px solid var(--hairline);
  border-radius: 8px; padding: 10px 12px;
}
.d-kpi .l {
  font-family: 'JetBrains Mono', monospace; font-size: 9px;
  letter-spacing: 0.18em; text-transform: uppercase; color: var(--ink-3);
  margin-bottom: 4px;
}
.d-kpi .v {
  font-weight: 600; font-size: 20px; color: var(--ink);
  font-variant-numeric: tabular-nums; letter-spacing: -0.01em;
}
.d-kpi .d {
  font-family: 'JetBrains Mono', monospace; font-size: 10px; margin-top: 2px;
}
.d-kpi .d.up { color: #059669; }
.d-kpi .d.down { color: #dc2626; }
.d-foot {
  padding: 12px 18px; border-top: 1px solid var(--hairline);
  background: #fbfbfc; display: flex; gap: 8px;
  position: sticky; bottom: 0;
}
.d-foot .btn {
  padding: 8px 14px; border-radius: 8px; font-family: inherit;
  font-size: 13px; font-weight: 500; border: 1px solid var(--hairline);
  background: white; color: var(--ink); cursor: pointer;
}
.d-foot .btn.primary {
  background: var(--cherry); color: white; border-color: var(--cherry);
  font-weight: 600;
}
.d-foot .btn:not(.primary):hover { background: var(--surface); border-color: var(--pink-light); }

/* ---- Slide-over shell (W2-5 · de-facto canon, promosso da app/style.css) ----
   `.drawer` (sopra) è in-flow; `.drawer--aside` lo rende un pannello fixed che
   entra da destra al toggle di `.is-open`, sopra un `.drawer-backdrop` scrim.
   È la shell del modal review W2-1 (SKU · Customer · Open Order), guidata da
   callback Dash (toggle class) + a11y via assets/drawer_dialog.js + keyboard
   via assets/keyboard_click.js. Hex decorativi letterali per parità col resto
   della sezione. Vedi RULES.md § "Componenti de-facto canon (W2-5)". */
.drawer-backdrop {
  position: fixed; inset: 0;
  background: rgba(10, 10, 15, 0.4);
  backdrop-filter: blur(2px);
  z-index: 1000; opacity: 0; pointer-events: none;
  transition: opacity 0.25s ease;
}
.drawer-backdrop-open { opacity: 1; pointer-events: auto; }
.drawer.drawer--aside {
  position: fixed; top: 0; right: 0; bottom: 0;
  width: 640px; max-width: 90vw; z-index: 1001;
  overscroll-behavior: contain;
  transform: translateX(100%);
  transition: transform 0.28s cubic-bezier(.2, .7, .3, 1);
  /* niente ombra da chiuso: translateX(100%) parcheggia il pannello appena
     fuori viewport ma il box-shadow di .drawer sborderebbe rientrando come
     striscia scura lungo il bordo destro della pagina */
  box-shadow: none;
}
.drawer.drawer--aside.is-open { transform: translateX(0); box-shadow: -8px 0 24px rgba(10,10,15,0.04); }
@media (prefers-reduced-motion: reduce) {
  .drawer--aside { transition-duration: 1ms !important; }
}

/* ---- Product thumbnail (W2-5 · de-facto canon) ----
   Cella immagine SKU 44px + placeholder glyph. Usata in Innovations grid +
   modal SKU. La variante grande (120px) è `.drawer .d-title .thumb` (app
   override). Hex letterali = parità visiva col forked originale. */
.product-thumb {
  width: 44px; height: 44px; object-fit: cover;
  border-radius: 6px; border: 1px solid var(--hairline);
  background: #fafafa; display: block;
}
.product-thumb-placeholder {
  width: 44px; height: 44px; background: #f3f4f6;
  border-radius: 6px; display: flex;
  align-items: center; justify-content: center;
  color: #d4d4d8; font-size: 14px;
}
.kpi-cell-img { padding: 6px 8px 6px 4px; width: 60px; }

/* ---- Filter-chip wrapper (W2-5 · de-facto canon) ----
   Host inline per un `.fb-chip` (famiglia filtri). */
.fb-chip-wrap { display: inline-block; }

/* ---- Screen-reader-only utility (W2-5 · de-facto canon · a11y) ----
   Visualmente nascosto, annunciato dagli AT. Nome canonico = `.sr-only`
   (era duplicato da `.visually-hidden`, deduplicato W2-5). Per <h1> di
   pagina e label di icon-button. */
.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;
}

/* ============ KEYCAPS + COMMAND PALETTE (Batch 5 — keyboard.html) ============
   `kbd` keycaps, `.keys` group, the `.palette` command palette, and the
   `.hint-strip` trigger pill. */
kbd {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 22px; height: 22px; padding: 0 6px;
  border-radius: 5px;
  background: white; border: 1px solid var(--hairline);
  box-shadow: 0 1px 0 var(--hairline), inset 0 -1px 0 #f5f0f2;
  font-family: 'JetBrains Mono', monospace; font-size: 11px; font-weight: 600;
  color: var(--ink); letter-spacing: 0;
}
kbd + kbd { margin-left: 4px; }
.keys { display: inline-flex; align-items: center; gap: 2px; }
.keys .sep { color: var(--ink-3); font-size: 11px; padding: 0 2px; }

.palette {
  background: white; max-width: 560px; margin: 0 auto;
  border-radius: 14px; overflow: hidden;
  box-shadow: 0 24px 60px rgba(10,10,15,0.15), 0 8px 16px rgba(10,10,15,0.08);
  border: 1px solid var(--hairline);
}
/* Modifier — Shell v3 inline-spotlight canonization. Re-positions the same
   .palette as an anchored dropdown hanging under its host (.popover-host),
   no centered modal, no scrim. Omit .p-input under .palette--inline — the
   bar's .fb-search IS the input. Width is set by the consumer (field-width
   default; a future two-column wide variant can land later without changing
   this modifier). Z-index matches the .popover layer so the spotlight
   composes with the Time and Filters popovers next to it. */
.palette--inline {
  position: absolute; top: calc(100% + 6px); left: 0;
  margin: 0; max-width: none; z-index: 10;
}
.palette--inline .p-input { display: none; }
.palette .p-input {
  display: flex; align-items: center; gap: 10px;
  padding: 14px 18px; border-bottom: 1px solid var(--hairline);
}
.palette .p-input .ic { color: var(--ink-3); font-size: 16px; }
.palette .p-input input {
  flex: 1; border: 0; outline: 0; font-family: inherit;
  font-size: 15px; color: var(--ink); background: transparent;
}
.palette .p-input input::placeholder { color: var(--ink-3); }
.palette .p-input .esc {
  font-family: 'JetBrains Mono', monospace; font-size: 10px;
  color: var(--ink-3); letter-spacing: 0.16em; text-transform: uppercase;
}
.palette .p-section {
  padding: 8px 0;
}
/* Tier ③ (popover surface eyebrow) — the same 10px / mono 700 / 0.18em /
   uppercase / ink-3 as .popover .ph-title. The spotlight's "SET A FILTER" /
   "JUMP TO" / "PAGES" section labels join the popover-eyebrow family — they
   ARE eyebrows over an option list, just inside a palette host instead of
   a popover host. Was 9px / 0.22em — drifted from .ph-title; harmonized
   2026-05-28 pass 2 so the Spotlight reads as the same surface family. */
.palette .p-section h6 {
  font-family: 'JetBrains Mono', monospace; font-weight: 700;
  font-size: 10px; letter-spacing: 0.18em; text-transform: uppercase;
  color: var(--ink-3); margin: 8px 18px 6px;
}
.palette .p-row {
  display: flex; align-items: center; gap: 12px;
  padding: 9px 18px; cursor: pointer; font-size: 13.5px;
}
.palette .p-row:hover, .palette .p-row.active {
  background: var(--pink-tint);
}
.palette .p-row.active .name { color: var(--pink-deep); font-weight: 500; }
.palette .p-row .icon {
  width: 18px; height: 18px; color: var(--ink-3); flex-shrink: 0;
  display: inline-flex; align-items: center; justify-content: center;
}
.palette .p-row.active .icon { color: var(--pink-deep); }
.palette .p-row .name { flex: 1; color: var(--ink); }
/* Tier ⑤ (row meta) — the SKU / Page / ↵ label on the right of a palette
   result row. Mono 500 · 11px · natural case · ink-3 · tab-nums. Same
   canon as every other popover row meta. Pass 3 revert. */
.palette .p-row .meta {
  font-family: 'JetBrains Mono', monospace; font-weight: 500;
  font-size: 11px; color: var(--ink-3);
  font-variant-numeric: tabular-nums;
}
/* Tier ⑥ (footer copy) — the "↑↓ navigate · ↵ apply · esc close" hint
   strip at the foot of the palette. Same 11.5px / mono 500 / sentence
   case / ink-3 as .pf .stats and .time-pop .compare-row. Footer copy
   reads as a quiet hint, not a menu vocabulary — sentence case + no
   tracking, deliberately distinct from the uppercase row labels above.
   Was 10px / 0.1em uppercase — drift, aligned 2026-05-28 pass 2. */
.palette .p-foot {
  padding: 10px 18px; border-top: 1px solid var(--hairline);
  display: flex; gap: 14px; align-items: center; justify-content: space-between;
  background: #fbfbfc; font-family: 'JetBrains Mono', monospace;
  font-weight: 500; font-size: 11.5px; color: var(--ink-3);
}
.palette .p-foot .hint { display: inline-flex; align-items: center; gap: 4px; }

.hint-strip {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 7px 12px; border-radius: 999px;
  background: white; border: 1px solid var(--hairline);
  font-family: 'JetBrains Mono', monospace; font-size: 11px; color: var(--ink-2);
  letter-spacing: 0.06em;
}

/* ============ CHART PANEL + PRIMITIVES (Batch 6 — charts.html) ============
   The `.chart-card` panel, `.legend`, and the SVG `.ax` / `.gridline`
   primitives. */
.chart-card {
  background: white; border: 1px solid var(--hairline);
  border-radius: 12px; padding: 24px 24px 20px;
  margin-top: 16px;
}
.chart-card .ch-title {
  display: flex; justify-content: space-between; align-items: flex-start;
  margin-bottom: 18px; gap: 16px;
}
.chart-card .ch-title h4 {
  margin: 0; font-family: 'DM Sans', sans-serif; font-weight: 600;
  font-size: 15px; color: var(--ink);
}
.chart-card .ch-title p {
  margin: 4px 0 0; font-size: 12.5px; color: var(--ink-3); max-width: none;
}
.legend { display: flex; gap: 14px; flex-wrap: wrap; }
.legend .lg {
  display: inline-flex; align-items: center; gap: 6px;
  font-family: 'JetBrains Mono', monospace; font-size: 11px;
  color: var(--ink-2); letter-spacing: 0.04em;
}
.legend .lg .sw {
  width: 10px; height: 10px; border-radius: 2px;
}
/* OPEN swatch — amber diagonal hatch (matches the chart OPEN treatment);
   LY swatch — dashed line. Cross-app legend semantics (hatch = open /
   provisional, dash = last-year), canonized from app (audit Fase 2 shadowed
   DEC1). The block's page margin stays app-side (geometric). */
.legend .lg .sw.hatch {
  background: var(--cream);
  background-image: repeating-linear-gradient(45deg,
                      var(--open-edge) 0 1.3px, transparent 1.3px 5.7px);
  border: 1px solid var(--open-edge);
}
.legend .lg .sw.dash {
  height: 0; width: 16px; background: transparent;
  border-top: 2px dashed var(--ink-3); border-radius: 0; align-self: center;
}
/* SVG axis + gridline primitives */
.ax {
  font-family: 'JetBrains Mono', monospace; font-size: 10px;
  fill: var(--ink-3); letter-spacing: 0.05em;
}
.ax-strong { fill: var(--ink-2); font-weight: 700; }
.gridline { stroke: var(--hairline); stroke-width: 1; }
.gridline-strong { stroke: #d8cdd0; stroke-width: 1; }

/* ============ APP SHELL — SIDEBAR / HEADER / ACCOUNT MENU (Batch 4 — app-shell.html) ============
   The real sidebar (`.sb*`), header bar (`.header-*`) and account menu
   (`.avatar*`, `.menu*`). The `-mock` wrappers, logo lab and ruler stay
   page-local in app-shell.html. */

/* ----- Sidebar ----- */
.sb {
  flex: 1;
  padding: 22px 18px 18px;
  display: flex; flex-direction: column;
  font-family: 'DM Sans', sans-serif;
}
.sb.is-collapsed { padding: 22px 8px 16px; align-items: center; }
/* The brand row carries NO border, NO background, NO divider beneath it —
   the wordmark sits free, never boxed into a rectangle (RULES.md · App shell). */
.sb-brand {
  height: 40px;
  display: flex; align-items: center;
  margin-bottom: 28px;
  width: 100%;
}
.sb.is-collapsed .sb-brand { justify-content: center; }
.sb-collapse-toggle {
  margin-left: auto;
  width: 28px; height: 28px;
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--ink-3);
  border-radius: 6px;
  cursor: pointer;
}
.sb-collapse-toggle:hover { color: var(--ink); background: var(--surface); }
.sb.is-collapsed .sb-collapse-toggle { display: none; }
.sb-section {
  font-family: 'JetBrains Mono', monospace; font-weight: 700;
  font-size: 9.5px; letter-spacing: 0.22em; text-transform: uppercase;
  color: var(--ink-3);
  margin: 18px 4px 8px;
  line-height: 1;
}
.sb-section:first-of-type { margin-top: 0; }
.sb.is-collapsed .sb-section {
  width: 32px; height: 1px; background: var(--hairline);
  color: transparent; padding: 0; margin: 16px 0 12px;
}
.sb-link {
  display: flex; align-items: center; gap: 12px;
  height: 36px; padding: 0 10px;
  border-radius: 8px;
  color: var(--ink-2);
  font-size: 13.5px; font-weight: 500;
  text-decoration: none;
  cursor: pointer;
  transition: background var(--motion-fast);
}
.sb-link:hover { background: var(--surface); color: var(--ink); }
.sb-link.is-active {
  background: var(--pink-tint);
  color: var(--pink-deep);
  font-weight: 600;
}
.sb-link .ic {
  width: 18px; height: 18px; flex: 0 0 18px;
  color: var(--ink-3);
}
.sb-link.is-active .ic { color: var(--pink-deep); }
.sb-link:hover .ic { color: var(--ink); }
.sb.is-collapsed .sb-link {
  width: 44px; height: 44px; padding: 0;
  justify-content: center; gap: 0;
  border-radius: 10px;
}
.sb.is-collapsed .sb-link .label-text { display: none; }
.sb.is-collapsed .sb-link .ic { width: 20px; height: 20px; flex-basis: 20px; }
.sb-wm {
  font-family: 'Caprasimo', serif;
  color: var(--pink);
  line-height: 0.85; letter-spacing: -0.02em;
  font-weight: 400;
  font-size: 28px;
  display: inline-flex; align-items: center; gap: 8px;
}
.sb-collapsed-mark {
  width: 28px; height: 28px;
  display: inline-flex; align-items: center; justify-content: center;
}

/* ----- Header bar ----- */
.header-bar {
  height: 64px;
  padding: 0 20px;
  border-bottom: 1px solid var(--hairline);
  display: flex; align-items: center; gap: 14px;
  position: relative;
}
.header-pill {
  display: inline-flex; align-items: center; gap: 8px;
  height: 36px; padding: 0 14px;
  border-radius: 999px;
  background: white;
  border: 1px solid var(--hairline);
  font-family: 'DM Sans', sans-serif; font-size: 13px; font-weight: 500;
  color: var(--ink);
  cursor: pointer;
}
.header-pill:hover { border-color: var(--pink-light); }
.header-pill .ic { color: var(--ink-3); }
.header-pill .add { color: var(--pink-deep); font-weight: 700; margin-left: 2px; }
.header-right {
  margin-left: auto;
  display: flex; align-items: center; gap: 10px;
}
.header-iconbtn {
  width: 36px; height: 36px;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 8px;
  color: var(--ink-2);
  background: white;
  border: 1px solid var(--hairline);
  cursor: pointer;
  transition: background var(--motion-fast), border-color var(--motion-fast);
}
.header-iconbtn:hover { background: var(--surface); border-color: var(--pink-light); color: var(--ink); }
.header-divider {
  width: 1px; height: 24px; background: var(--hairline);
  margin: 0 4px;
}

/* ----- Avatar + account menu (header top-right) ----- */
.avatar {
  width: 36px; height: 36px;
  border-radius: 50%;
  background: var(--pink-tint);
  color: var(--pink-deep);
  display: inline-flex; align-items: center; justify-content: center;
  font-family: 'DM Sans', sans-serif; font-weight: 600; font-size: 14px;
  cursor: pointer;
  flex: 0 0 36px;
  aspect-ratio: 1 / 1;
  border: 2px solid transparent;
  transition: border-color var(--motion-base);
}
.avatar.is-open { border-color: var(--pink); }
.avatar:hover { border-color: var(--pink-light); }
.avatar-wrap { position: relative; }
.menu {
  position: absolute; top: calc(100% + 8px); right: 0;
  width: 260px;
  background: white;
  border: 1px solid var(--hairline);
  border-radius: 12px;
  box-shadow:
    0 12px 32px rgba(10,10,15,0.10),
    0 2px 6px rgba(10,10,15,0.04);
  z-index: 50;
  padding: 8px;
  display: none;
}
.menu.is-open { display: block; }
.menu::before {
  content: ''; position: absolute; top: -5px; right: 14px;
  width: 10px; height: 10px;
  background: white;
  border-top: 1px solid var(--hairline);
  border-left: 1px solid var(--hairline);
  transform: rotate(45deg);
}
.menu-user {
  display: flex; align-items: center; gap: 10px;
  padding: 10px 10px 12px;
  border-bottom: 1px solid var(--hairline);
  margin: 0 -8px 6px;
  padding-left: 18px; padding-right: 18px;
}
.menu-user .avatar { width: 32px; height: 32px; font-size: 13px; flex-basis: 32px; cursor: default; }
.menu-user .info { display: flex; flex-direction: column; min-width: 0; }
.menu-user .name {
  font-family: 'DM Sans', sans-serif; font-weight: 600; font-size: 13.5px;
  color: var(--ink); line-height: 1.1;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  max-width: 180px;
}
.menu-user .role {
  font-family: 'JetBrains Mono', monospace; font-size: 10px;
  letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--ink-3); margin-top: 4px;
}
.menu-item {
  display: flex; align-items: center; gap: 12px;
  padding: 9px 10px;
  border-radius: 6px;
  font-family: 'DM Sans', sans-serif; font-size: 13.5px; font-weight: 500;
  color: var(--ink);
  cursor: pointer;
  text-decoration: none;
  transition: background var(--motion-fast);
}
.menu-item:hover { background: var(--surface); }
.menu-item .ic { width: 16px; height: 16px; color: var(--ink-3); flex: 0 0 16px; }
.menu-item:hover .ic { color: var(--ink); }
.menu-item .chev { margin-left: auto; color: var(--ink-3); font-size: 12px; }
.menu-sep {
  height: 1px; background: var(--hairline);
  margin: 6px -8px;
}
.menu-item.is-signout { color: var(--cherry-deep); }
.menu-item.is-signout .ic { color: var(--cherry-deep); }

/* ============ MATRIX TABLE (TODO_COWORK A1) ============
   Category × period pivot / heatmap — Sales · Compare, Countries.
   Sticky first column + header. The total column sits right after the
   dimension column (.matrix-th-total / .matrix-td-total). The total row
   is plain — no fill — set off by a strong bottom rule. Heatmap tints
   each cell by |YoY| in discrete steps; the total row is NOT tinted. */
.matrix-scroll {
  overflow-x: auto;
  /* Seamless by default — was a 1px hairline + 12px radius frame, but the
     surrounding .panel already provides the outer frame. The standalone
     frame on .matrix-scroll caused a double-outline (panel border + scroll
     border), which Andrea flagged as the only table type still showing
     its own outline. Aligned with the .data-table seamless default (Round 5). */
}
.matrix-table {
  border-collapse: separate; border-spacing: 0;
  width: auto; max-width: 100%;  /* was width:100% — left-side dim was sprawling, eating the page */
  font-family: var(--font-sans); font-size: var(--type-table-num-size);
  /* Remap 2026-05-27 (Andrea): was hardcoded 12.5px — drift fix to the
     canonical table number size token (13.5px). Pairs with Compare /
     Countries Category × Month tables that now hide SKINCARE / NAIL by
     default, freeing vertical space for a more readable base font on
     the matrix heatmap. */
}
.matrix-table th, .matrix-table td {
  padding: 8px 12px; border-bottom: 1px solid var(--hairline);
  white-space: nowrap; text-align: right;
  width: 1%; /* hug content; leftover space sits AFTER the rightmost column (right of table) */
}
.matrix-table thead th {
  position: sticky; top: 0; z-index: 2;
  background: white; border-bottom: 1.5px solid var(--ink-3);
  font-family: var(--font-mono-heavy); font-weight: 700; font-size: 10px;
  letter-spacing: 0.1em; text-transform: uppercase; color: var(--ink-2);
}
.matrix-th-dim {
  position: sticky; left: 0; z-index: 3; text-align: left; min-width: 150px;
}
.matrix-th-month { min-width: 86px; }
.matrix-th-total, .matrix-td-total { border-right: 2px solid var(--hairline); }
.matrix-th-dim, .matrix-td-dim { border-right: 1px solid var(--hairline); }  /* vertical separator on the dim column, on every row (was: only TOTAL row visible) */
.matrix-td-dim {
  position: sticky; left: 0; z-index: 1; background: var(--paper);
  text-align: left; font-family: var(--font-sans); font-weight: 500; color: var(--ink);
}
.matrix-td-total .matrix-cell-val { font-weight: 600; }
.matrix-tr-total td {
  font-weight: 700; background: var(--paper);
  border-bottom: 2px solid var(--ink-3);
}
/* Modifier for a TOTAL row placed at the BOTTOM of the table (the Sales
   Compare M1 layout): the separator hairline goes ABOVE the row, not below. */
.matrix-tr-total--bottom td {
  border-top: 2px solid var(--pink-light); border-bottom: 0;
}
.matrix-tr-group .matrix-td-dim { font-weight: 700; color: var(--ink); }
.matrix-tr-sub .matrix-td-dim { color: var(--ink-2); }
.matrix-td-sub-indent { padding-left: 30px; }
.matrix-cell-val {
  display: block; font-weight: 500; font-variant-numeric: tabular-nums;
  color: var(--ink); letter-spacing: -0.01em;
  font-size: var(--type-matrix-val-size);
  /* Canon 2026-05-27 (Andrea): matrix heatmap cells need a stronger
     hierarchy than regular .data-table because val + YoY chip stack
     into 2 short lines per cell. Specific tokens (`matrix-val-size` 15px /
     `matrix-yoy-size` 10.5px = ratio 1.43) replace inheritance from
     `--type-table-num-size`. The data-table tokens stay 13.5px / 11.5px
     for prose-y customer / order tables. */
}
.matrix-cell-yoy {
  display: block; font-family: var(--font-mono); margin-top: 1px;
  font-size: var(--type-matrix-yoy-size); font-weight: 700;
}
.matrix-yoy-up   { color: var(--success-deep); }
.matrix-yoy-down { color: var(--cherry-deep); }
.matrix-yoy-flat { color: var(--ink-3); }
/* Open-orders chip inside a now/future matrix cell — the open portion of the
   month's value, as a small amber pill (B2 open treatment, inline variant).
   Canonized from the mockup's page-local `.pm-open-note`. Sits after the
   value on the same line. */
.matrix-open-chip {
  display: inline-block; margin-left: 5px; padding: 1px 6px;
  border-radius: var(--radius-pill);
  background: color-mix(in srgb, var(--open-edge) 18%, var(--cream));
  color: var(--warning-deep);
  font-family: var(--font-mono-heavy); font-weight: 700;
  font-size: 9.5px; letter-spacing: 0; white-space: nowrap;
  vertical-align: 1px;
}
/* Heatmap — tint depth follows |YoY|, discrete steps (3 up, 2 down). */
.matrix-heat-pos-1 { background: color-mix(in srgb, var(--success) 8%, white); }
.matrix-heat-pos-2 { background: color-mix(in srgb, var(--success) 18%, white); }
.matrix-heat-pos-3 { background: color-mix(in srgb, var(--success) 30%, white); }
.matrix-heat-neg-1 { background: color-mix(in srgb, var(--cherry) 9%, white); }
.matrix-heat-neg-2 { background: color-mix(in srgb, var(--cherry) 20%, white); }
/* Whitespace / opportunity heat — pink. Third sibling of the matrix heat scale:
   pos = YoY up (green), neg = YoY down (cherry), OPP = uncovered peer
   opportunity (pink) for coverage / whitespace matrices. Canonized 2026-06 ·
   Client deep-dive pass (brand x category whitespace). Reference mockup:
   brand-mockups/client.html (Actions tab). Pairs with the `.tt` cell tooltip
   (peers selling N of M · avg peer value · est. opportunity). */
.matrix-heat-opp-1 { background: color-mix(in srgb, var(--pink) 8%, white); }
.matrix-heat-opp-2 { background: color-mix(in srgb, var(--pink) 14%, white); }
.matrix-heat-opp-3 { background: color-mix(in srgb, var(--pink) 22%, white); }

/* --- Month-state model · consolidating surfaces (Sellout · incoming months,
   Andrea 2026-07-08) ------------------------------------------------------
   Lo stato del mese sell-out è di prima classe: un mese NON-final non mostra
   mai YoY; il partial è SOLID-in-hatch GRIGIO (data-incoming — l'hatch amber
   resta esclusivo degli open orders, RULES §OPEN). Due superfici canon:
   (1) `.month-chip--*` — chip mono-heavy sotto il `th` del mese (SOLO la
       parola-stato: PARTIAL / IN MONTH / INCOMING). Deriva da `.pm-partial`.
   (2) tinta PER-CELLA del mese non-final (la lettura è per-retailer, non a
       colonna): `.col-await` (neutro, nessun dato) · `.col-partial` (giallo,
       "so far" — dato sotto la norma del cliente) · `.col-mtd` (info, mese a
       finestra aperta) · `.col-gap` (warning forte, out-of-order). Una cella
       "landed" (dato nella norma → cliente consolidato) NON porta tinta: è
       bianca con YoY come un mese final. Ref: sellout-incoming-states.html. */
.month-chip {
  display: block; font-family: var(--font-mono-heavy);
  font-size: 8.5px; font-weight: 700; letter-spacing: 0.06em;
  text-transform: uppercase; margin-top: 2px;
}
.month-chip--partial { color: var(--warning-deep); }
.month-chip--mtd     { color: var(--info-deep); }
.month-chip--await   { color: var(--ink-3); }
.month-chip--gap     { color: var(--warning-deep); }
.col-await   { background: color-mix(in srgb, var(--ink-3) 4%, white); }
.col-partial { background: color-mix(in srgb, var(--warning) 7%, white); }
.col-mtd     { background: color-mix(in srgb, var(--info) 6%, white); }
.col-gap     { background: color-mix(in srgb, var(--warning) 14%, white); }
/* le note di cella non-final ("so far" / "MTD · d1–N" / "awaiting") sono
   quiet; il gap è un flag warning. Il valore resta ink — è vero venduto. */
.col-await .matrix-cell-yoy,
.col-partial .matrix-cell-yoy,
.col-mtd .matrix-cell-yoy { color: var(--ink-3); font-weight: 500; }
.col-gap .matrix-cell-yoy { color: var(--warning-deep); font-weight: 700; }

/* --- F-Code.8 · Matrix Trend column (Sales review 2026-05-31)
   Leading column after the dim col, BEFORE the period columns. Carries
   the row-level YoY headline + a 6-mo sparkline so the row scans in 1s
   without reading every cell. Used by Sales · Compare Category × Month
   and Sales · Countries Country × Category. Replaces per-cell heatmap
   tint when paired with M1 grid (the YoY chip per cell becomes the
   chrome carrier, no cell-bg tint needed).
   Header is .matrix-th-trend, cell is .matrix-td-trend. Cell composes
   .matrix-cell-trend-val (headline YoY mono-heavy) + .matrix-cell-trend-spark
   (inline 68x14 SVG polyline). Sentiment modifier on the val. */
.matrix-th-trend,
.matrix-td-trend {
  background: color-mix(in srgb, var(--pink-tint) 35%, white);
  border-left: 1px solid var(--hairline);
  border-right: 1px solid var(--hairline);
  min-width: 86px;
  text-align: left;
  padding-left: 14px;
}
.matrix-cell-trend-val {
  display: block;
  font-family: var(--font-mono-heavy);
  font-size: 12px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.matrix-cell-trend-val--up   { color: var(--success-deep); }
.matrix-cell-trend-val--down { color: var(--cherry-deep); }
.matrix-cell-trend-val--flat { color: var(--ink-2); }
.matrix-cell-trend-spark { display: block; margin-top: 3px; }

/* --- Matrix laptop tier (≤1680px) --------------------------------
   The intrinsic matrix (12 month columns + now-col widened by the
   inline open chip) measures ~1265px: on a 1536 laptop (= 1920 @125%
   scaling) with the sidebar expanded the panel offers ~1200px → the
   table scrolls internally, the TREND column slides under the sticky
   dim column (+/− signs eaten) and TOTAL is cut off (Andrea 2026-07-06
   "spacca su laptop"). Same approach as the app-side #brand-ti-grid /
   .page-budget laptop compactions: tighter cell padding, 14px values
   and the open chip on its own line (now-col drops ~154px → ~92px).
   Measured in browser: 1265px → ~1040px, fits 1366–1536 without a
   horizontal bar; large monitors (≥1681px) keep the comfy sizes above. */
@media (max-width: 1680px) {
  .matrix-table th, .matrix-table td {
    padding-left: 7px; padding-right: 7px;
  }
  .matrix-th-dim { min-width: 132px; }
  .matrix-th-month { min-width: 70px; }
  .matrix-th-trend, .matrix-td-trend { min-width: 78px; padding-left: 10px; }
  .matrix-cell-val { font-size: 14px; }
  /* Open chip on its own line INSIDE the cell (stays right-aligned via
     margin-left:auto — the td is text-align:right) → the now-col no
     longer outgrows a normal month column. */
  .matrix-open-chip {
    display: block; margin-left: auto; margin-top: 2px; width: fit-content;
  }
}

/* --- Sticky dim+TREND pair (modifier .matrix-table--sticky-pair) ------
   Compare / Countries M1 matrix (the builders with a leading TREND
   column). When the table scrolls horizontally (Customer dim, narrow
   laptops) the reading anchor must stay put: without this the TREND
   column slides UNDER the sticky dim column and gets chopped. A second
   sticky column needs a KNOWN left offset, but `width`/`max-width` on a
   <td> do NOT cap an auto-layout table column (the dim column swallowed
   all the leftover space, and long nowrap customer names push min-content
   past any width). So the cap lives on an INNER block `.matrix-dim-clip`
   (same pattern as the Budget ag-pin column): fixed clip width + fixed
   cell padding ⇒ the column's min-content == max-content == 180px exactly
   in every regime, and TREND docks at left:180px. Long names ellipsize —
   the full name ships in the cell `title`. Scoped to the modifier so the
   hug-width matrices sharing the canon (sell-through matrix, assortment
   mix, retailer, client actions) keep their content-driven dim width.
   Squeeze regime (table overflows → columns at min-content): dim is
   exactly 180 and TREND pins flush next to it. Stretch regime (table
   fills the panel, no scroll): sticky never engages — no conflict.
   Padding is re-pinned here so the ≤1680px laptop tier (7px) cannot
   desync the 180px math. The trend/dim opaque backgrounds (pink-tint
   mix / paper) already hide the month columns scrolling underneath. */
.matrix-table--sticky-pair .matrix-th-dim,
.matrix-table--sticky-pair .matrix-td-dim {
  padding-left: 12px; padding-right: 12px;
}
.matrix-table--sticky-pair .matrix-dim-clip {
  display: block; width: 155px;          /* 12 + 155 + 12 + 1px border = 180 */
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.matrix-table--sticky-pair .matrix-td-sub-indent { padding-left: 30px; }
.matrix-table--sticky-pair .matrix-td-sub-indent .matrix-dim-clip {
  width: 137px;                          /* 30 + 137 + 12 + 1px border = 180 */
}
.matrix-table--sticky-pair .matrix-th-trend,
.matrix-table--sticky-pair .matrix-td-trend {
  position: sticky; left: 180px;
}
.matrix-table--sticky-pair .matrix-th-trend { z-index: 3; }
.matrix-table--sticky-pair .matrix-td-trend { z-index: 1; }

/* ============ MARKET SHARE BAR (Part F · Brand V3) ============
   Per-category horizontal bar showing Markwins share vs Top 3 competitor
   ticks. NOT a "market share %" claim (the brief explicitly forbids that
   wording — see docs/circana-data-semantics.md) — this is a visual that
   compares relative rank position in Circana. Composes only existing
   tokens. Used by brand-overview.html Section 4 — Market Position. */
.market-share-bar { display: flex; flex-direction: column; gap: 10px; }
.market-share-bar__row {
  display: grid; grid-template-columns: 110px 1fr 260px; gap: 12px;
  align-items: center;
}
.market-share-bar__cat {
  font-family: var(--font-sans); font-weight: 500; font-size: 12.5px;
  color: var(--ink);
}
.market-share-bar__track {
  position: relative; height: 14px;
  background: var(--surface); border-radius: var(--radius-pill);
  overflow: hidden;
}
.market-share-bar__mark {
  height: 100%; background: var(--pink);
  border-radius: var(--radius-pill);
}
.market-share-bar__tick {
  position: absolute; top: -2px; width: 2px; height: 18px;
  background: var(--ink-3); transform: translateX(-1px); border-radius: 1px;
}
.market-share-bar__tick--top { background: var(--ink); }
.market-share-bar__meta {
  font-family: var(--font-mono); font-size: 11.5px;
  color: var(--ink-2); font-variant-numeric: tabular-nums;
  text-align: right;
}
.market-share-bar__meta strong {
  font-family: var(--font-mono-heavy); font-weight: 700; color: var(--ink);
}

/* ============ WHITE SPACE TILE (Part F · Brand V3) ============
   Tiered sub-category tile for the White Space Map. IN tiles (Markwins
   present, neutral white) vs OUT tiles (white space, opportunity-sized
   via --small/--medium/--large modifiers). Tier colours derive from
   --warning / --c5 / --cherry via color-mix; tier text colours via
   --warning-deep / --coral-deep / --cherry-deep. The "hot" tier upgrades
   a small market with growth ≥+20% AND a competitor NPD signal to
   --large — see RULES.md (brief §6.1). Used by brand-market.html
   Section 2. */
.white-space-tile-grid {
  display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px;
}
.white-space-tile {
  padding: 14px 16px; border-radius: var(--radius-md);
  border: 1px solid var(--hairline);
  display: flex; flex-direction: column; gap: 6px;
  min-height: 96px; background: var(--paper);
}
.white-space-tile__name {
  font-family: var(--font-sans); font-weight: 600; font-size: 13.5px;
  color: var(--ink);
}
.white-space-tile__status {
  font-family: var(--font-mono-heavy); font-weight: 700; font-size: 9.5px;
  letter-spacing: 0.18em; text-transform: uppercase;
  color: var(--ink-3);
  display: flex; align-items: center; gap: 5px;
}
.white-space-tile__status::before {
  content: ''; width: 6px; height: 6px; border-radius: 50%;
  background: currentColor; flex: 0 0 6px; display: inline-block;
}
.white-space-tile__stat {
  font-family: var(--font-sans); font-size: 11.5px;
  color: var(--ink-2); line-height: 1.5;
  margin-top: auto;
}
.white-space-tile__stat strong { color: var(--ink); font-weight: 600; }

/* IN tile — Markwins present, neutral */
.white-space-tile--in .white-space-tile__status { color: var(--success-deep); }

/* OUT tile — tier-coloured by opportunity */
.white-space-tile--small {
  background: color-mix(in srgb, var(--warning) 12%, white);
  border-color: color-mix(in srgb, var(--warning) 28%, white);
}
.white-space-tile--small .white-space-tile__status { color: var(--warning-deep); }
.white-space-tile--medium {
  background: color-mix(in srgb, var(--c5) 20%, white);
  border-color: color-mix(in srgb, var(--c5) 38%, white);
}
.white-space-tile--medium .white-space-tile__status { color: var(--coral-deep); }
.white-space-tile--large {
  background: color-mix(in srgb, var(--cherry) 14%, white);
  border-color: color-mix(in srgb, var(--cherry) 30%, white);
}
.white-space-tile--large .white-space-tile__status { color: var(--cherry-deep); }

/* Legend strip */
.white-space-tile-legend {
  display: flex; gap: 18px; margin-top: 14px;
  padding-top: 12px; border-top: 1px solid var(--hairline);
  font-family: var(--font-sans); font-size: 11.5px;
  color: var(--ink-2); flex-wrap: wrap;
}
.white-space-tile-legend__key {
  display: inline-flex; align-items: center; gap: 7px;
}
.white-space-tile-legend__key::before {
  content: ''; width: 14px; height: 14px; border-radius: 3px;
  border: 1px solid var(--hairline); flex: 0 0 14px;
}
.white-space-tile-legend__key--in::before { background: var(--paper); }
.white-space-tile-legend__key--small::before {
  background: color-mix(in srgb, var(--warning) 12%, white);
  border-color: color-mix(in srgb, var(--warning) 28%, white);
}
.white-space-tile-legend__key--medium::before {
  background: color-mix(in srgb, var(--c5) 20%, white);
  border-color: color-mix(in srgb, var(--c5) 38%, white);
}
.white-space-tile-legend__key--large::before {
  background: color-mix(in srgb, var(--cherry) 14%, white);
  border-color: color-mix(in srgb, var(--cherry) 30%, white);
}

/* ============ INFO-ICON TOOLTIP (TODO_COWORK A2) ============
   Static, CSS-only tooltip — no JS. Put .tooltip-trigger on any element
   that also carries a data-tooltip="..." attribute; on hover or keyboard
   focus it shows a dark ink bubble with a caret, reusing the Plotly
   tooltip identity (§6). .kpi-info-icon is the common trigger: a sized +
   coloured wrapper for the brand info glyph next to a KPI label —
   <span class="kpi-info-icon tooltip-trigger" data-tooltip="..." tabindex="0">
     <svg><use href="icons-sprite.svg#i-info"></use></svg>
   </span> */
.tooltip-trigger { position: relative; cursor: help; }
.tooltip-trigger::after {
  content: attr(data-tooltip);
  position: absolute; bottom: calc(100% + 9px); left: 50%; transform: translateX(-50%);
  background: var(--ink); color: white;
  font-family: var(--font-mono); font-size: 11px; line-height: 1.5;
  font-weight: 400; letter-spacing: 0; text-transform: none;
  padding: 8px 11px; border-radius: var(--radius-sm);
  width: max-content; max-width: 230px; text-align: left;
  box-shadow: var(--shadow-menu);
  opacity: 0; visibility: hidden; transition: opacity var(--motion-base);
  z-index: 50; pointer-events: none;
}
.tooltip-trigger::before {
  content: ''; position: absolute; bottom: calc(100% + 4px); left: 50%;
  transform: translateX(-50%) rotate(45deg);
  width: 9px; height: 9px; background: var(--ink);
  opacity: 0; visibility: hidden; transition: opacity var(--motion-base);
  z-index: 50; pointer-events: none;
}
.tooltip-trigger:hover::after, .tooltip-trigger:hover::before,
.tooltip-trigger:focus-visible::after, .tooltip-trigger:focus-visible::before {
  opacity: 1; visibility: visible;
}
@media (prefers-reduced-motion: reduce) {
  .tooltip-trigger::after, .tooltip-trigger::before { transition: none; }
}
.kpi-info-icon {
  display: inline-flex; align-items: center; justify-content: center;
  width: 14px; height: 14px; flex: 0 0 14px;
  /* margin-left: auto pins the glyph to the far right of the eyebrow flex
     row regardless of label length — the KPI card info-icon is always
     top-right (F-Code.10 canon). */
  color: var(--ink-3); margin-left: auto;
}
.kpi-info-icon svg { width: 100%; height: 100%; display: block; }
.kpi-info-icon:hover { color: var(--ink); }

/* --- KPI info-icon · rich tooltip (§07) -------------------------------
   The canonical KPI info-icon tooltip is a STRUCTURED dark bubble (eyebrow
   + prose + optional divider/rows + provenance foot), not flat text. CSS
   `::after` can only render `attr(data-tooltip)` (one string), so the rich
   variant renders an actual `.kpi-info-tip` child (HTML) revealed on hover
   / focus. Same dark identity as the Plotly HOVERLABEL_DARK + the `.tt`
   mockup family. Use via `kpi_info_tip()` (components/kpi_header.py). */
.kpi-info-icon--rich { position: relative; }
.kpi-info-tip {
  /* Open BELOW the icon (the content area has room; opening above runs
     under the top bar) and float above all chrome (z-index) so it never
     renders under the sidebar. right:0 keeps it inside the content column
     — the icon sits at the card's right, so it expands left within the
     card area, away from the sidebar. */
  position: absolute; top: calc(100% + 9px); right: 0;
  /* z-index sopra la sidebar/drawer (fixed): rete di sicurezza — anche se un
     tooltip dovesse sovrapporsi alla chrome, va SOPRA, mai sotto il menu
     (feedback Andrea 2026-06-05). La direzione d'apertura (left/right) resta
     il fix primario per non sovrapporsi proprio. */
  display: none; z-index: 1300;
  background: var(--ink); color: white;
  border-radius: var(--radius-sm); padding: 10px 14px;
  width: max-content; max-width: 300px; white-space: normal; text-align: left;
  box-shadow: var(--shadow-menu);
  font-family: var(--font-mono); font-size: 11.5px; line-height: 1.6;
  /* Pin normal weight: the tip is nested inside the bold .kpi-card__eyebrow,
     so without this the body inherits 700 → faux-bold (DM Mono ships 400/500
     only) → heavy & blurry. Children that need weight set it explicitly
     (.tt-eye 700, .tt-row__val 500). Matches the standalone canonical .tt. */
  font-weight: 400;
  letter-spacing: 0; text-transform: none; cursor: default;
}
.kpi-info-icon--rich:hover .kpi-info-tip,
.kpi-info-icon--rich:focus-within .kpi-info-tip { display: block; }
/* The leftmost card's tooltip opens to the RIGHT (into the content) so it
   never slides under the sidebar; every other card opens left (right:0),
   staying inside the content column. */
.kpi-header > *:first-child .kpi-info-tip { left: 0; right: auto; }
/* Projection panel: le KPI stanno nella colonna stretta SINISTRA, vicino alla
   sidebar → i loro tooltip si aprono a DESTRA (dentro il pannello, mai sotto la
   sidebar) e verso l'ALTO (`bottom`): il Projection è l'ultimo blocco della
   pagina, aprendo verso il basso un tooltip lungo allungava la pagina e
   generava una scrollbar (feedback Andrea 2026-06-05). Contenuto tenuto corto. */
.projection__kpi-lbl .kpi-info-tip {
  left: 0; right: auto;
  top: auto; bottom: calc(100% + 9px);
}
.kpi-info-tip .tt-eye {
  font-family: var(--font-mono-heavy); font-weight: 700; font-size: 10px;
  letter-spacing: 0.14em; text-transform: uppercase; color: var(--pink-light);
  margin-bottom: 6px;
}
.kpi-info-tip .tt-body { color: rgba(255,255,255,0.9); }
.kpi-info-tip .tt-body strong { color: white; font-weight: 600; }
.kpi-info-tip .tt-divider { border-top: 1px solid rgba(255,255,255,0.18); margin: 7px 0; }
.kpi-info-tip .tt-row { display: flex; justify-content: space-between; gap: 18px; }
.kpi-info-tip .tt-row__lbl { color: rgba(255,255,255,0.65); }
.kpi-info-tip .tt-row__val {
  color: white; font-weight: 500; font-variant-numeric: tabular-nums;
  font-family: var(--font-mono-heavy);
}
.kpi-info-tip .tt-foot {
  font-family: var(--font-mono); font-size: 10.5px;
  color: rgba(255,255,255,0.55); margin-top: 4px;
}

/* ============ INTERACTIVE GRID (Style round · B4) ============
   The ag-grid look — sortable header, column menu, pinned column, row
   states, resize, footer. Production renders ag-grid with the
   `ag-theme-atlas` theme (ships in Atlas); this CSS is the visual target
   the theme params bind to, and the look static demos use directly.
   Cell content stays canonical .td-* / .status-pill / .sku-* / .delta-*.
   The .col-open column is the canonical B2 class. */
.atlas-grid-scroll { overflow-x: auto; }
.atlas-grid {
  width: 100%; border-collapse: separate; border-spacing: 0;
  font-family: var(--font-sans);
}
/* --- header --- */
.atlas-grid thead th {
  background: white; padding: 0;
  border-bottom: 1.5px solid var(--ink-3);
  position: sticky; top: 0; z-index: 2;
}
.ag-col {
  display: flex; align-items: center; gap: 6px;
  padding: 10px 12px; cursor: pointer;
  font-family: var(--font-mono-heavy); font-size: 10px; font-weight: 700;
  letter-spacing: 0.1em; text-transform: uppercase; color: var(--ink-3);
  white-space: nowrap; position: relative;
  transition: background var(--motion-base);
}
.ag-col.is-num { justify-content: flex-end; }
.ag-col:hover { background: var(--pink-tint); color: var(--ink-2); }
.ag-col__sort { color: var(--pink); font-size: 9px; opacity: 0; }
.ag-col:hover .ag-col__sort { opacity: 0.4; }
.ag-col.is-sorted { color: var(--ink-2); box-shadow: inset 0 -2px 0 var(--pink); }
.ag-col.is-sorted .ag-col__sort { opacity: 1; }
.ag-col__menu {
  margin-left: auto; width: 16px; height: 16px; flex: 0 0 16px;
  color: var(--ink-3); opacity: 0;
  display: inline-flex; align-items: center; justify-content: center;
}
.ag-col:hover .ag-col__menu { opacity: 1; }
.ag-col__menu:hover { color: var(--ink); }
.ag-col__resize {
  position: absolute; top: 6px; right: -1px; width: 5px; bottom: 6px;
  cursor: col-resize; border-right: 1px solid var(--hairline);
}
.ag-col__resize:hover { border-right: 2px solid var(--pink-light); }
/* --- body --- */
.atlas-grid tbody td {
  padding: 8px 12px; border-bottom: 1px solid var(--hairline);
  font-size: 13.5px; vertical-align: middle; background: white;
}
.ag-row { cursor: pointer; }
.ag-row:hover td { background: color-mix(in srgb, var(--surface) 70%, white); }
.ag-row.is-selected td { background: var(--pink-tint); }
.ag-row.is-selected td:first-child { box-shadow: inset 3px 0 0 var(--pink); }
/* Alert-pinned row — sorted to the top as an alert (AT RISK budget
   customers). Mirrors the .is-selected edge, in --cherry. */
.ag-row--alert td:first-child { box-shadow: inset 3px 0 0 var(--cherry); }
.atlas-grid .num { text-align: right; font-variant-numeric: tabular-nums; }
.ag-rank { font-family: var(--font-mono); font-size: 11px; color: var(--ink-3); text-align: center; }
.ag-prod { display: flex; align-items: center; gap: 11px; }  /* thumb + the canonical .td-sku-name identity (B7) */
.ag-gross { font-family: var(--font-sans); font-weight: 600; font-size: 13.5px; font-variant-numeric: tabular-nums; }
/* --- pinned column (# + Product) on horizontal scroll --- */
.ag-pin { position: sticky; left: 0; z-index: 1; }
.ag-pin--rank { left: 0; }
.ag-pin--prod { left: 44px; }
.atlas-grid.is-scrolled .ag-pin--prod::after {
  content: ''; position: absolute; top: 0; right: 0; bottom: -1px; width: 8px;
  transform: translateX(100%);
  background: linear-gradient(90deg, rgba(10,10,15,0.07), transparent);
  pointer-events: none;
}
/* --- Open USD column — canonical .col-open (B2); grid-specific tints --- */
.atlas-grid .ag-col.col-open { background: color-mix(in srgb, var(--open-edge) 32%, var(--cream)); }
.atlas-grid td.col-open .num { font-weight: 600; }
/* --- footer --- */
.ag-foot {
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  padding: 12px 4px 2px;
}
.ag-foot .rows-lbl {
  font-family: var(--font-mono-heavy); font-size: 9px; font-weight: 700;
  letter-spacing: 0.12em; text-transform: uppercase; color: var(--ink-3);
}
/* --- skeleton row --- */
.ag-sk { height: 9px; border-radius: var(--radius-sm); }

/* ============ SHAPE BAR (Style round · B6a) ============
   Horizontal proportion bar — a total split into a few labelled, clickable
   segments. Open Orders uses it for the month book by invoiceability
   (potential --success vs open --open-edge); reusable for any
   "whole, split N ways" read. */
.shape-bar { display: flex; height: 46px; border-radius: var(--radius-sm); overflow: hidden; gap: 2px; }
.shape-bar__seg { display: flex; flex-direction: column; justify-content: center; padding: 0 14px; cursor: pointer; color: #fff; transition: filter var(--motion-base); }
.shape-bar__seg:hover { filter: brightness(1.06); }
/* Segment value + label. BEM names — must NOT reuse the bare .sk class:
   .sk is the skeleton-shimmer primitive, and the collision leaked a white
   shimmer background onto the label. nowrap + ellipsis so a label can
   never wrap and break the fixed-height bar. */
.shape-bar__val { display: block; max-width: 100%; font-family: var(--font-sans); font-size: 14px; font-weight: 600; font-variant-numeric: tabular-nums; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.shape-bar__lbl { display: block; max-width: 100%; font-family: var(--font-mono-heavy); font-size: 8.5px; font-weight: 700; letter-spacing: 0.08em; opacity: 0.94; margin-top: 1px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.shape-bar__seg--ok   { background: var(--success); }
.shape-bar__seg--soon { background: var(--warning); }
.shape-bar__seg--late { background: var(--cherry-deep); }
/* "No request date" segment — a data-quality gap, not a health state;
   rendered grey to read as neutral / non-evaluable. */
.shape-bar__seg--none { background: var(--ink-3); }
/* Open-book segment — the unfulfilled remainder of the book stays in the
   cross-Atlas OPEN canon (--open-edge amber, never grey, never a bespoke
   colour). Text shifts to --warning-deep for contrast on the light amber.
   Pairs with __seg--ok exactly like .bdg-achv pairs fill + open share
   (Open Orders potential-invoiced redesign, CODE-PING §4). */
.shape-bar__seg--open { background: var(--open-edge); color: var(--warning-deep); }
/* Coverable segment (Open Orders v2, validata 2026-06-12) — stock
   disponibile ma non ancora allocato: stessa famiglia del potential
   ("a un giro di allocazione di distanza"), MAI teal (riservato alla
   provenienza esterna). Testo ink per contrasto sul verde chiaro. */
.shape-bar__seg--cov { background: color-mix(in srgb, var(--success) 38%, white); color: var(--ink); }
.shape-bar__seg--cov .shape-bar__lbl { color: var(--ink-2); }
.shape-bar__key { display: flex; flex-wrap: wrap; gap: 6px 22px; margin-top: 12px; font-size: 11.5px; color: var(--ink-2); }
.shape-bar__key span { display: inline-flex; align-items: center; gap: 7px; }
.shape-bar__key strong { color: var(--ink); font-weight: 600; }
.shape-bar__key .dot { width: 9px; height: 9px; border-radius: 50%; flex: 0 0 9px; }

/* ============ WK-SPLIT (Open Orders · month-by-week) ============
   Weekly distribution of the current month's open book. Uniform CSS
   columns (NOT Plotly — micro-viz da panel): vertical bar filled
   bottom-up with the potential share (--success), the open remainder
   (--open-edge, canon OPEN — mai grigio) and, v2 only, the coverable
   share (color-mix success 38%). The request week is DISTRIBUTION only,
   never a deadline judgment: `--now` marks the current week as
   orientation (pink label), `--nd` is the "No date" data-gap bucket
   (all ink-3, always the LAST column, never merged elsewhere).
   Anatomy: .wk-split > __col[--now|--nd] > __val (week total, sans 600
   tabular) + __sub ("$X pot.", mono success-deep) + __bar (height ∝
   week total) > __seg--pot / __seg--cov / __seg--rem + __wk (mono
   "W24 · now" + small date range). */
.wk-split { display: grid; grid-auto-flow: column; grid-auto-columns: 1fr; gap: 14px; align-items: end; padding: 6px 2px 0; }
.wk-split__col { display: flex; flex-direction: column; align-items: stretch; min-width: 0; }
.wk-split__val { font-family: var(--font-sans); font-weight: 600; font-size: 15px; color: var(--ink); font-variant-numeric: tabular-nums; text-align: center; }
.wk-split__sub { font-family: var(--font-mono); font-size: 10px; color: var(--success-deep); text-align: center; margin: 1px 0 7px; }
.wk-split__bar { display: flex; flex-direction: column-reverse; height: 110px; border-radius: 4px; overflow: hidden; }
.wk-split__seg { width: 100%; }
.wk-split__seg--pot { background: var(--success); }
.wk-split__seg--cov { background: color-mix(in srgb, var(--success) 38%, white); }
.wk-split__seg--rem { background: var(--open-edge); } /* canon OPEN — il residuo è book aperto */
.wk-split__wk { margin-top: 8px; font-family: var(--font-mono); font-size: 11px; color: var(--ink-2); text-align: center; white-space: nowrap; }
.wk-split__wk small { display: block; color: var(--ink-3); font-size: 9.5px; margin-top: 1px; }
.wk-split__col--now .wk-split__wk { color: var(--pink-deep); font-weight: 700; }
.wk-split__col--nd .wk-split__val, .wk-split__col--nd .wk-split__wk { color: var(--ink-3); }
.wk-split__col--nd .wk-split__sub { color: var(--ink-3); }

/* ============ PROGRESSION BAR (Budget / Modal V2) ============
   ACTUAL (solid --pink) + OPEN (solid --open-edge) = the PROJECTED whole,
   against a PLAN / BUDGET marker. Thin, no text inscribed — the 3 KPI
   cards above carry the numbers; a legend + one-line read decode it.
   Used in the SKU modal and the Customer modal. */
.prog-bar__scale {
  display: flex; justify-content: space-between;
  font-family: var(--font-mono); font-size: 10px;
  color: var(--ink-3); margin-bottom: 6px;
}
.prog-bar__track {
  position: relative; display: flex; height: 13px;
  border-radius: var(--radius-pill); overflow: hidden; background: var(--hairline);
}
.prog-bar__seg { height: 100%; }
.prog-bar__seg--actual { background: var(--pink); }   /* pipe / actual (pink) */
.prog-bar__seg--repl   { background: var(--info); }   /* replenishment (teal) */
.prog-bar__seg--open   { background: var(--open-edge); }  /* open orders (amber) */
.prog-bar__marker {
  position: absolute; top: 0; bottom: 0; width: 2px;
  background: var(--ink); z-index: 2;
}
.prog-bar__foot {
  display: flex; align-items: center; justify-content: space-between;
  gap: 14px; margin-top: 9px; flex-wrap: wrap;
}
.prog-bar__key { display: flex; gap: 14px; font-family: var(--font-mono); font-size: 10.5px; color: var(--ink-2); }
.prog-bar__key span { display: inline-flex; align-items: center; gap: 6px; }
.prog-bar__key i { width: 11px; height: 11px; border-radius: 3px; flex: 0 0 11px; }
.prog-bar__key i.plan { width: 2px; height: 13px; border-radius: 1px; }
.prog-bar__read { font-size: 11.5px; color: var(--ink-2); }
.prog-bar__read b { font-family: var(--font-mono-heavy); font-weight: 700; color: var(--ink); }
.prog-bar__read .good { color: var(--success-deep); }

/* ============ BUDGET ACHIEVEMENT CELL ============
   Achievement-% cell for the Budget × Customer table + brand-split tables.
   __fill = Actual/Budget, tier-coloured (ramp mirrors .abc-card-a/b/c/d
   green->amber, tier D escalated to --cherry). __open = the open order
   book segment (--open-edge), so ACTUAL + OPEN reads in-row. */
.bdg-achv { display: flex; flex-direction: column; gap: 5px; min-width: 112px; }
.bdg-achv__head { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
.bdg-achv__pct {
  font-family: var(--font-sans); font-weight: 600; font-size: 13.5px;
  font-variant-numeric: tabular-nums; color: var(--ink);
}
.bdg-achv__cap { font-family: var(--font-mono); font-size: 10px; letter-spacing: 0.02em; color: var(--ink-3); }
.bdg-achv__track {
  position: relative; height: 6px; border-radius: var(--radius-pill);
  background: var(--hairline); overflow: hidden;
}
.bdg-achv__fill { position: absolute; left: 0; top: 0; bottom: 0; }
.bdg-achv__fill--a { background: var(--success); }                                    /* >=95% */
.bdg-achv__fill--b { background: color-mix(in srgb, var(--success) 58%, var(--paper)); } /* 80-94% */
.bdg-achv__fill--c { background: var(--warning); }                                    /* 50-79% */
.bdg-achv__fill--d { background: var(--cherry); }                                     /* <50%  */
.bdg-achv__open { position: absolute; top: 0; bottom: 0; background: var(--open-edge); }

/* ============ PIPELINE PROGRESS · inline (Part F · Brand V3) ============
   Mini progress bar that sits inside a .data-table cell, showing Actual
   (pink) + Open Orders (--open-edge amber) against a Forecast track.
   Canonical OPEN treatment extension to inline bars — see RULES.md
   "OPEN treatment · inline bars". The numerical line above the bar
   carries the explicit breakdown ("X.XK + Y.YK open · pct of WW.WK").
   Used by brand-innovations.html SKU Launch Tracker · Pipeline
   progress column. Sibling of .bdg-achv (both 6px inline cell bars,
   different use cases: pipe-progress = unit volume; bdg-achv = budget %). */
.pipe-progress { display: flex; flex-direction: column; gap: 4px; }
.pipe-progress__line {
  font-family: var(--font-mono); font-size: 11.5px;
  color: var(--ink-2); font-variant-numeric: tabular-nums;
}
.pipe-progress__line strong { color: var(--ink); font-weight: 600; }
.pipe-progress__pct {
  color: var(--pink-text);
  font-family: var(--font-mono-heavy); font-weight: 700;
}
.pipe-progress__open {
  color: var(--warning-deep);
  font-family: var(--font-mono-heavy); font-weight: 700;
}
.pipe-progress__denom { color: var(--ink-3); }
.pipe-progress__bar {
  position: relative; height: 6px;
  background: var(--surface);
  border-radius: var(--radius-pill);
  margin: 4px 0;
  overflow: hidden;
  max-width: 200px;
  display: flex;
}
.pipe-progress__bar-fill { height: 100%; background: var(--pink); }
.pipe-progress__bar-open { height: 100%; background: var(--open-edge); }

/* ============ ACHIEVEMENT BAR · with target + Open (Part F · Brand V3) ============
   Larger horizontal bar inside a .data-table cell: Actual (pink) + Open
   (--open-edge) up to (or past) a vertical target marker. Stretch band
   (--pink-light) extends past 100% when actual exceeds plan. Below 50%
   the actual fill switches to --cherry (canonical warn). Target marker
   sits at 100% of plan. Used by brand-innovations.html Innovation Reach ·
   Country scorecard. Sibling of .prog-bar (both 13-14px ACTUAL+OPEN+
   target bars, different use cases: prog-bar = modal SKU/Customer
   volume vs PLAN; ach-cell = country scorecard achievement %). */
.ach-cell {
  position: relative;
  height: 14px;
  width: 100%;
  max-width: 280px;
  background: var(--surface);
  border-radius: var(--radius-pill);
  overflow: visible;
}
.ach-cell__fill {
  position: absolute; left: 0; top: 0; height: 100%;
  background: var(--pink);
  border-radius: var(--radius-pill);
}
.ach-cell__open {
  position: absolute; top: 0; height: 100%;
  background: var(--open-edge);
}
.ach-cell__fill--stretch {
  background: var(--pink-light);
  border-radius: 0;
  /* position via inline left/width — past the 100% target */
}
.ach-cell__fill--warn { background: var(--cherry); }
.ach-cell__target {
  position: absolute; top: -2px; bottom: -2px;
  width: 2px; background: var(--ink);
  border-radius: 1px;
}

/* ============ ORDER-BOOK EXPLORER (Style round · B6b) ============
   Master-detail block: a ranked master list (each row a labelled share bar)
   + a detail pane. Lens / filters / search above it are canonical
   (.segmented, .search-wrap); the detail line-items are a canonical
   .data-table; each pane gets its own .paginator. */
/* DEPRECATED — use .atlas-grid + .drawer (RULES §Open Orders A3). The whole
   .explorer / .explorer-* family (incl. .explorer--fixed) is superseded; kept
   here only until the app is verified free of it, then removed. */
.explorer { border: 1px solid var(--hairline); border-radius: var(--radius-lg); overflow: hidden; }
.explorer-bar { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; padding: 12px 14px; border-bottom: 1px solid var(--hairline); background: #faf8f9; }
.explorer-body { display: grid; grid-template-columns: 38% 62%; }
.explorer-master { border-right: 1px solid var(--hairline); display: flex; flex-direction: column; }
.explorer-detail { display: flex; flex-direction: column; }
.master-head { padding: 10px 16px; font-family: var(--font-mono-heavy); font-size: 9.5px; font-weight: 700; letter-spacing: 0.14em; text-transform: uppercase; color: var(--ink-3); display: flex; justify-content: space-between; border-bottom: 1px solid var(--hairline); }
.master-row { display: grid; grid-template-columns: 18px 1fr auto; gap: 10px; align-items: center; padding: 11px 16px; cursor: pointer; border-bottom: 1px solid var(--hairline); }
.master-row:hover { background: #faf8f9; }
.master-row.is-selected { background: var(--pink-tint); box-shadow: inset 3px 0 0 var(--pink); }
.master-row.is-selected .master-row__name { color: var(--pink-deep); font-weight: 600; }
.master-row__rank { font-family: var(--font-mono-heavy); font-size: 10px; font-weight: 700; color: var(--ink-3); }
.master-row__name { font-size: 13px; font-weight: 500; color: var(--ink); margin-bottom: 5px; }
.master-row__bar { height: 5px; background: #f0edf0; border-radius: 3px; overflow: hidden; }
.master-row__bar i { display: block; height: 100%; background: var(--pink); border-radius: 3px; }
.master-row__meta { text-align: right; white-space: nowrap; }
.master-row__val { font-family: var(--font-sans); font-size: 13.5px; font-weight: 600; color: var(--ink); font-variant-numeric: tabular-nums; }
.master-row__lines { font-family: var(--font-mono); font-size: 10px; color: var(--ink-3); margin-top: 2px; }
.od-chip { display: inline-block; font-family: var(--font-mono-heavy); font-size: 9px; font-weight: 700; color: var(--cherry-deep); background: #fee2e2; padding: 1px 6px; border-radius: var(--radius-pill); margin-top: 3px; }
.detail-head { padding: 14px 18px; border-bottom: 1px solid var(--hairline); display: flex; align-items: flex-start; justify-content: space-between; gap: 14px; }
.detail-id .nm { font-size: 16px; font-weight: 600; color: var(--ink); letter-spacing: -0.01em; }
.detail-id .sub { font-family: var(--font-mono); font-size: 11px; color: var(--ink-3); margin-top: 3px; }
.detail-stats { display: flex; gap: 18px; }
.detail-stat { text-align: right; }
.detail-stat .k { font-family: var(--font-mono-heavy); font-size: 8.5px; font-weight: 700; letter-spacing: 0.14em; text-transform: uppercase; color: var(--ink-3); }
.detail-stat .v { font-family: var(--font-sans); font-size: 15px; font-weight: 600; color: var(--ink); font-variant-numeric: tabular-nums; margin-top: 2px; }
.detail-stat .v--risk { color: var(--cherry-deep); }
.explorer-foot { margin-top: auto; padding: 9px 14px; border-top: 1px solid var(--hairline); display: flex; align-items: center; gap: 10px; justify-content: flex-end; }

/* ============ INSIGHT LINE — Sales v2 (row, not card) ============
   The canonical insight surface (supersedes .insight-callout). An insight is
   a ROW, never a card: a hairline-framed container of rows, each = pill(layer)
   + sentence with <b>numbers</b> + meta (rule id · age) + CTA, with an
   expandable "why" (human formula + source numbers + refresh timestamp). Max
   3 rows + an "All insights (N) →" expander; empty state is one calm row.
   Component: components/insight_line.py. The ENGINE (insight_engine.py) is a
   CODE module; this is the canonical row it paints. */
.insight-lines {
  border: 1px solid var(--hairline); border-radius: var(--radius-lg);
  background: var(--paper); overflow: hidden;
}
.insight-line {
  /* pill vertically CENTERED on the row (Andrea 2026-06-10, Item pass) */
  display: flex; align-items: center; gap: 10px;
  padding: 11px 14px; border-bottom: 1px solid var(--hairline);
}
.insight-line:last-child { border-bottom: 0; }
.insight-line__pill { flex: 0 0 auto; margin-top: 0; }   /* a .status-pill */
.insight-line__main { flex: 1 1 auto; min-width: 0; }
.insight-line__text {
  font-family: var(--font-sans); font-size: 13.5px; line-height: 1.45;
  color: var(--ink-2);
}
.insight-line__text b {
  font-weight: 600; color: var(--ink); font-variant-numeric: tabular-nums;
}
/* sub — VISIBLE subtitle (source/calc context, mono quiet). Canonized with
   the Item pass (Andrea 2026-06-10): no hidden "why" — the formula + source
   numbers live in plain sight under the sentence. */
.insight-line__sub {
  margin-top: 3px; font-family: var(--font-mono); font-size: 10.5px;
  line-height: 1.5; color: var(--ink-3);
}
/* why — LEGACY (<details> variant, superseded by .insight-line__sub). */
.insight-line__why { margin-top: 4px; }
.insight-line__why-toggle {
  cursor: pointer; list-style: none; display: inline-flex; align-items: center;
  gap: 4px; font-family: var(--font-mono); font-size: 10.5px; color: var(--ink-3);
}
.insight-line__why-toggle::-webkit-details-marker { display: none; }
.insight-line__why-toggle::before { content: "▸"; font-size: 9px; }
.insight-line__why[open] .insight-line__why-toggle::before { content: "▾"; }
.insight-line__why-toggle:hover { color: var(--ink-2); }
.insight-line__why-body {
  margin-top: 5px; font-family: var(--font-mono); font-size: 10.5px;
  line-height: 1.55; color: var(--ink-3);
}
.insight-line__meta {
  flex: 0 0 auto; display: flex; align-items: center; gap: 12px;
  font-family: var(--font-mono); font-size: 10.5px; color: var(--ink-3);
  white-space: nowrap;
}
.insight-line__cta {
  font-family: var(--font-mono-heavy); font-weight: 700; font-size: 10.5px;
  letter-spacing: 0.04em; color: var(--pink-text); text-decoration: none;
}
.insight-line__cta:hover { color: var(--pink-deep); }
.insight-lines__more {
  display: block; padding: 9px 14px; text-align: center;
  font-family: var(--font-mono-heavy); font-weight: 700; font-size: 10.5px;
  letter-spacing: 0.04em; color: var(--pink-text); text-decoration: none;
  border-top: 1px solid var(--hairline); background: var(--surface);
}
.insight-lines__more:hover { color: var(--pink-deep); }
/* Expander disclosure (All insights N) — native <details>/<summary>, zero JS */
.insight-lines__expand { display: block; }
.insight-lines__expand > summary.insight-lines__more {
  cursor: pointer; list-style: none; user-select: none;
}
.insight-lines__expand > summary.insight-lines__more::-webkit-details-marker { display: none; }
.insight-lines__expand > summary.insight-lines__more::marker { content: ""; }
.insight-lines__expand[open] > summary.insight-lines__more { color: var(--pink-deep); }
/* Le righe extra hanno .insight-line { display:flex }, che vince sulla UA-rule
   di <details> chiuso → nasconderle esplicitamente quando non-open. */
.insight-lines__expand:not([open]) > .insight-line { display: none; }
.insight-lines__empty {
  display: flex; align-items: center; gap: 8px; padding: 13px 14px;
  font-family: var(--font-sans); font-size: 13px; color: var(--ink-3);
}
.insight-lines__empty::before {
  content: ""; width: 7px; height: 7px; border-radius: 50%;
  background: var(--success); flex: 0 0 7px;
}
@media (max-width: 720px) {
  .insight-line { flex-wrap: wrap; }
  .insight-line__meta { width: 100%; padding-left: 33px; }
}

/* ============ FIGURE STRIP · "By the numbers" sidebar (Part F · Brand V3) ============
   Right-half "By the numbers" partner of an insight in .insight-pair.
   Variants — ticker (mono numerics + hairlines), bare, spark,
   timeline (canonical .timeline-style dot+rail). Combo-to-figure
   pairing locked per RULES.md "Editorial rotation":
     --masthead → --ticker
     --quote    → --bare
     --sans     → --timeline */
.figure-strip {
  background: var(--paper); border: 1px solid var(--hairline);
  border-radius: var(--radius-lg); box-sizing: border-box;
}
.figure-strip__eye {
  font-family: var(--font-mono-heavy); font-weight: 700;
  font-size: 10px; letter-spacing: 0.22em;
  text-transform: uppercase; color: var(--pink-deep);
}
.figure-strip__lbl {
  font-family: var(--font-sans); font-size: 12px;
  line-height: 1.5; color: var(--ink-2);
}
.figure-strip__lbl strong { color: var(--ink); font-weight: 600; }

/* Ticker variant — mono numerics + hairlines between rows */
.figure-strip--ticker { padding: 14px 20px; display: flex; flex-direction: column; }
.figure-strip--ticker .figure-strip__eye {
  padding-bottom: 10px; border-bottom: 1px solid var(--hairline);
}
.figure-strip--ticker .figure-strip__list {
  flex: 1; display: flex; flex-direction: column; justify-content: space-between;
}
.figure-strip--ticker .figure-strip__row {
  display: grid; grid-template-columns: 70px 1fr; gap: 14px;
  align-items: start; padding: 12px 0;
  border-bottom: 1px solid var(--hairline);
}
.figure-strip--ticker .figure-strip__row:last-child { border-bottom: 0; }
.figure-strip--ticker .figure-strip__num {
  font-family: var(--font-mono-heavy); font-weight: 700;
  font-size: 18px; line-height: 1.5; color: var(--ink);
  font-variant-numeric: tabular-nums; text-align: right;
}

/* Bare list variant — no card chrome, pink-text numerics */
.figure-strip--bare { padding: 4px 6px; display: flex; flex-direction: column; justify-content: center; background: transparent; border: 0; border-radius: 0; }
.figure-strip--bare .figure-strip__eye { margin-bottom: 18px; }
.figure-strip--bare .figure-strip__list { display: flex; flex-direction: column; gap: 16px; }
.figure-strip--bare .figure-strip__row {
  display: grid; grid-template-columns: 80px 1fr; gap: 14px; align-items: start;
}
.figure-strip--bare .figure-strip__num {
  font-family: var(--font-sans); font-weight: 600; font-size: 22px;
  line-height: 1.4; color: var(--pink-text);
  font-variant-numeric: tabular-nums; letter-spacing: -0.02em;
  text-align: right;
}

/* Timeline variant — dot+rail */
.figure-strip--timeline { padding: 18px 20px; display: flex; flex-direction: column; }
.figure-strip--timeline .figure-strip__eye {
  padding-bottom: 12px; border-bottom: 1px solid var(--hairline);
}
.figure-strip--timeline .figure-strip__list {
  flex: 1; display: flex; flex-direction: column; justify-content: space-between;
  position: relative; padding: 14px 0 6px;
}
.figure-strip--timeline .figure-strip__rail {
  position: absolute; left: 5px; top: 18px; bottom: 18px;
  width: 1px; background: var(--hairline);
}
.figure-strip--timeline .figure-strip__row {
  display: grid; grid-template-columns: 11px 80px 1fr; gap: 10px; align-items: start;
}
.figure-strip--timeline .figure-strip__dot {
  width: 11px; height: 11px; border-radius: 50%;
  background: var(--pink); border: 2px solid var(--paper);
  box-sizing: border-box; margin-top: 4px; display: inline-block;
}
.figure-strip--timeline .figure-strip__num {
  font-family: var(--font-sans); font-weight: 600; font-size: 17px;
  line-height: 1.5; color: var(--ink);
  font-variant-numeric: tabular-nums; text-align: right;
}

/* ============================================================
   INSIGHT v2 — diff + figure-strip spark/chart
   (Stock pass · Andrea approved 2026-06)
   ============================================================ */

/* .insight-diff — "what's changed": flussi di transizione status→status.
   Companion default: .figure-strip--spark. Vive in .insight-pair. */
.insight-diff { border: 1px solid var(--hairline); border-left: 3px solid var(--cherry); border-radius: var(--radius-md); background: var(--paper); padding: 17px 19px; display: flex; flex-direction: column; }
.insight-diff__head { font-family: var(--font-display); font-weight: 560; font-size: 21px; line-height: 1.18; letter-spacing: -0.012em; color: var(--ink); margin: 8px 0 0; }
.insight-diff__stand { font-family: var(--font-sans); font-size: 12.5px; color: var(--ink-2); line-height: 1.55; margin: 6px 0 12px; }
.insight-diff__stand b { color: var(--ink); font-weight: 600; }
.insight-diff__row { display: grid; grid-template-columns: 1fr auto auto; gap: 12px; align-items: center; padding: 8px 0; border-top: 1px solid var(--hairline); }
.insight-diff__flow { display: inline-flex; align-items: center; gap: 8px; }
.insight-diff__arrow { color: var(--ink-3); font-family: var(--font-mono); }
.insight-diff__n { font-family: var(--font-mono); font-weight: 700; font-size: 14px; min-width: 30px; text-align: right; }
.insight-diff__lbl { font-family: var(--font-sans); font-size: 11.5px; color: var(--ink-3); }
.insight-diff__foot { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-top: auto; padding-top: 14px; }
.insight-diff__src { font-family: var(--font-mono); font-size: 10px; color: var(--ink-3); }

/* .figure-strip--spark — sparkline per metrica. Companion per insight ALTI
   (ratio, diff): il __list è grid che riempie l'altezza, niente ballooning. */
.figure-strip--spark { padding: 14px 20px; display: flex; flex-direction: column; }
.figure-strip--spark .figure-strip__eye { padding-bottom: 10px; border-bottom: 1px solid var(--hairline); }
.figure-strip--spark .figure-strip__list { flex: 1; display: grid; grid-auto-rows: 1fr; }
.figure-strip--spark .figure-strip__row { display: grid; grid-template-columns: 58px 1fr 88px; gap: 12px; align-items: center; border-bottom: 1px solid var(--hairline); }
.figure-strip--spark .figure-strip__row:last-child { border-bottom: 0; }
.figure-strip--spark .figure-strip__num { font-family: var(--font-mono-heavy); font-weight: 700; font-size: 18px; color: var(--ink); text-align: right; font-variant-numeric: tabular-nums; }
.figure-strip--spark .figure-strip__lbl { font-family: var(--font-sans); font-size: 12px; color: var(--ink-2); }
.figure-strip--spark .figure-strip__lbl strong { color: var(--ink); font-weight: 600; }
.figure-strip--spark .figure-strip__spark { width: 88px; height: 24px; display: block; }

/* .figure-strip--chart — numeri compatti + un grafico nel footer.
   Alternativa a --spark quando serve una singola viz d'insieme invece di N trend. */
.figure-strip--chart { padding: 14px 20px; display: flex; flex-direction: column; }
.figure-strip--chart .figure-strip__eye { padding-bottom: 10px; border-bottom: 1px solid var(--hairline); }
.figure-strip--chart .figure-strip__list { display: flex; flex-direction: column; }
.figure-strip--chart .figure-strip__row { display: grid; grid-template-columns: 70px 1fr; gap: 14px; align-items: baseline; padding: 9px 0; border-bottom: 1px solid var(--hairline); }
.figure-strip--chart .figure-strip__num { font-family: var(--font-mono-heavy); font-weight: 700; font-size: 17px; color: var(--ink); text-align: right; }
.figure-strip--chart .figure-strip__lbl { font-family: var(--font-sans); font-size: 12px; color: var(--ink-2); }
.figure-strip--chart .figure-strip__chart { flex: 1; min-height: 64px; margin-top: 12px; display: flex; flex-direction: column; }

/* ============ TABLE CELL TYPOGRAPHY (TODO_COWORK A3) ============
   The .td-* cell family — ONE naming convention, extends the existing
   .td-num / .td-num__delta. Apply to <td> content inside .kpi-table /
   .data-table. Atlas migrates its old .kpi-cell-* call sites to these. */
.td-dim   { text-align: left; font-family: var(--font-sans); font-weight: 600; color: var(--ink); } /* was 500 — row identifier carries the row's identity, must read first */
.td-dim-caps { text-align: left; font-family: var(--font-sans); font-weight: 500; color: var(--ink); text-transform: uppercase; letter-spacing: 0.04em; } /* for CATEGORICAL CODE columns (brand abbr WNW, category FACE/LIP, subcategory HIGHLIGHT). Atlas: always uppercase. For proper nouns (Georgia, France) use plain .td-dim sentence-case. */
.td-value { text-align: right; font-family: var(--font-sans); font-weight: 500; font-size: 13.5px; font-variant-numeric: tabular-nums; color: var(--ink); }
.td-sku   { font-family: var(--font-mono); font-weight: 600; font-size: 13px; color: var(--ink); }
.td-desc  {
  font-family: var(--font-sans); font-size: 12.5px; color: var(--ink-2);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 220px;
}
.td-name  {
  font-family: var(--font-sans); font-weight: 500; font-size: 13px;
  color: var(--ink); line-height: 1.3;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.td-light { font-family: var(--font-sans); font-weight: 400; font-size: 12.5px; color: var(--ink-3); }
.td-mono  { font-family: var(--font-mono); font-size: 12px; color: var(--ink-2); }

/* ============ PAGINATOR (TODO_COWORK B5) ============
   Two sizes, one family. Rule of placement is by TABLE-WIDTH CONTEXT,
   not by page count:
   .paginator          full — numbered pages + .paginator-info row count.
                       Use on FULL-WIDTH tables (taking the entire row).
                       Even at 2 pages, it gives proper "Showing X-Y of N".
   .paginator-sm       compact — Back / .paginator-pos "1 / N" / Next only.
                       Use on SIDE-BY-SIDE / PAIRED tables (Best/Worst, any
                       table sharing a row with another). Even at 100 pages,
                       there's no room for numbered buttons in those layouts.
   The current page is ink-filled, NOT pink (pink = brand-surface / primary
   action; "you are here" is a neutral state). */
.paginator { display: inline-flex; align-items: center; gap: 4px; }
.paginator-btn {
  font-family: var(--font-sans); font-size: 13px;
  min-width: 30px; height: 30px; padding: 0 10px;
  border: 1px solid var(--hairline); background: white; color: var(--ink);
  border-radius: var(--radius-sm); cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  transition: background 120ms, border-color 120ms;
}
.paginator-btn:hover { border-color: var(--ink-3); background: #f3f1f7; }
.paginator-btn.is-disabled, .paginator-btn[disabled] {
  color: var(--ink-3); cursor: not-allowed; background: white; border-color: var(--hairline);
}
.paginator-num {
  font-family: var(--font-sans); font-size: 13px;
  min-width: 30px; height: 30px;
  border: 1px solid transparent; background: transparent; color: var(--ink-2);
  border-radius: var(--radius-sm); cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
}
.paginator-num:hover { background: #f3f1f7; color: var(--ink); }
.paginator-num.is-current {
  /* Option C: pink-deep bold + underline, no fill. Drops the heavy ink-on-current.
     Hover on other num/btn uses neutral #f3f1f7, never pink — pink reserved for "you are here". */
  color: var(--pink-deep); font-weight: 700; background: transparent;
  border-bottom: 2px solid var(--pink-deep); border-radius: 0;
  height: 28px;
}
.paginator-gap { font-family: var(--font-sans); font-size: 13px; color: var(--ink-3); padding: 0 2px; }
.paginator-info {
  font-family: var(--font-mono-heavy); font-weight: 700;
  font-size: 10px; letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--ink-3);
}
.paginator-pos { font-family: var(--font-sans); font-size: 13px; color: var(--ink-2); padding: 0 10px; }
.paginator-sm .paginator-btn { font-size: 12.5px; height: 28px; padding: 0 9px; }

/* Paginator row — canonical layout for the strip below a table that holds
   the paginator. The paginator is ALWAYS right-aligned (never left, never
   centered — left-aligned reads as "this is in the wrong place"). Optional
   .paginator-info sits on the left as a status label; flex pushes the
   paginator to the right. */
.paginator-row {
  display: flex; align-items: center; justify-content: flex-end;
  gap: 14px; flex-wrap: wrap;
  margin-top: 12px;
}
.paginator-row .paginator-info { margin-right: auto; }  /* if present, info goes left, paginator stays right */

/* ============ SECTION TITLES (TODO_COWORK C2) ============
   Canonical headers for app pages and panel blocks where a semantic <h2>
   would interfere with page heading hierarchy. For brand-book pages keep
   using <h1>/<h2>/<h3> from brand.css. The subtitle pattern is dual-use:
   inline (right of the title, table/panel header) OR stacked below the
   title (page header with context line). */
.section-title {
  font-family: var(--font-sans); font-weight: 600;
  font-size: 20px; line-height: 1.2; letter-spacing: -0.01em;
  color: var(--ink);
}
.section-subtitle {
  font-family: var(--font-sans); font-weight: 400;
  font-size: 13px; color: var(--ink-3);
}
/* Small mono eyebrow that sits ABOVE the .kpi-header on a Sales-tab masthead
   (Shell v3 — Option 3 KPI-first stack). The big h1 section title is gone:
   the section masthead is bar -> KPI header (led by this eyebrow) -> tabs ->
   content. Tiny by design — the sidebar already says "where you are"; the
   eyebrow is just the labelled handle. Reuses mono-heavy for the 700 weight
   slot. */
.section-eyebrow {
  font-family: var(--font-mono-heavy); font-weight: 700;
  font-size: 10px; letter-spacing: 0.22em; text-transform: uppercase;
  color: var(--ink-3);
}
/* ============ ACTIVITY TIMELINE (Admin panel · issue #107 / #A1) ============
   Vertical chronological feed — one `.timeline` wraps N `.timeline-event`
   rows. Each event = a RAIL (a hairline connector carrying a tone dot) + a
   BODY (kind eyebrow · relative time · title · optional drill-down detail).
   Built only from existing tokens: the connector is `--hairline` (the
   canonical grid/connector colour — no `timeline-line-color` token is
   needed), the dot carries the tone. Tone modifier colours the DOT ONLY;
   body text stays `--ink`, so the feed reads as one quiet column with
   colour used sparingly as signal:
     --ok      green  — run / deploy / upload succeeded
     --info    teal   — routine informational event (file arrived)
     --warn    amber  — slow / skipped / degraded
     --error   red    — failed run, deploy, or ingestion
     --neutral gray   — unclassified / system note
   Use ONLY for a time-ordered event feed (admin activity, audit log, an
   SKU's change history). A list that is NOT time-ordered is a table. */
.timeline { display: flex; flex-direction: column; }
/* Truncated mode — when the timeline ships more events than fit on screen
   the wrapper gets `.timeline--collapsed`, hiding every event past the
   nth-of-type set. The clientside toggle in pages/admin.py removes the
   class to reveal the full list. The default nth value is 8; pages can
   override with their own selector (e.g. `.timeline--collapsed.timeline--limit-5`). */
.timeline--collapsed .timeline-event:nth-of-type(n+9) { display: none; }
.timeline__more {
  margin-top: 8px;
  align-self: flex-start;
  font-family: var(--font-mono); font-size: 11.5px;
  background: transparent; border: 0; padding: 4px 0;
  color: var(--pink-deep); cursor: pointer;
}
.timeline__more:hover { text-decoration: underline; }

.timeline-event { display: grid; grid-template-columns: 18px 1fr; gap: 12px; }
/* Rail — the connector runs the full row height; the dot is punched out of
   it with a `--paper` ring so the line never shows through the dot. */
.timeline-event__rail { position: relative; display: flex; justify-content: center; }
.timeline-event__rail::before {
  content: ''; position: absolute; top: 0; bottom: 0;
  width: 2px; background: var(--hairline);
}
/* First event: connector starts AT the dot. Last event: it stops at the
   dot. A lone event drops the connector entirely — never a dangling line. */
.timeline-event:first-child .timeline-event__rail::before { top: 6px; }
.timeline-event:last-child  .timeline-event__rail::before { bottom: 6px; }
.timeline-event:only-child  .timeline-event__rail::before { display: none; }
.timeline-event__dot {
  position: relative; z-index: 1; margin-top: 1px;
  width: 11px; height: 11px; border-radius: var(--radius-pill);
  background: var(--ink-3); box-shadow: 0 0 0 3px var(--paper);
}
.timeline-event--ok      .timeline-event__dot { background: var(--success); }
.timeline-event--info    .timeline-event__dot { background: var(--info); }
.timeline-event--warn    .timeline-event__dot { background: var(--warning); }
.timeline-event--error   .timeline-event__dot { background: var(--error); }
.timeline-event--neutral .timeline-event__dot { background: var(--ink-3); }

/* Body — bottom padding gives the inter-event rhythm the rail spans. */
.timeline-event__body { min-width: 0; padding-bottom: var(--space-5); }
.timeline-event:last-child .timeline-event__body { padding-bottom: 0; }
.timeline-event__head { display: flex; align-items: baseline; gap: 8px; }
.timeline-event__kind {
  font-family: var(--font-mono-heavy); font-weight: 700;
  font-size: 9.5px; letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--ink-3);
}
.timeline-event__time {
  margin-left: auto; flex: 0 0 auto;
  font-family: var(--font-mono); font-size: 11px; color: var(--ink-3);
}
.timeline-event__title {
  margin-top: 3px;
  font-family: var(--font-sans); font-weight: 500; font-size: 13.5px;
  line-height: 1.45; color: var(--ink);
}
/* A mono fragment inside the title — a folder path, an id, a run number. */
.timeline-event__title .mono { font-family: var(--font-mono); font-size: 12px; color: var(--ink-2); }

/* Drill-down — `.timeline-event--clickable` marks an event that opens; the
   whole body is the hit target. `.is-open` rotates the chevron and the
   detail card is rendered below. */
.timeline-event--clickable .timeline-event__body { cursor: pointer; }
.timeline-event__expand {
  display: inline-flex; align-items: center; gap: 4px; margin-top: 6px;
  padding: 0; border: 0; background: transparent; cursor: pointer;
  font-family: var(--font-mono-heavy); font-weight: 700;
  font-size: 9.5px; letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--pink-deep);
}
.timeline-event__expand .chev { transition: transform var(--motion-medium) var(--motion-ease-out); }
.timeline-event.is-open .timeline-event__expand .chev { transform: rotate(90deg); }
/* Detail panel collapsed by default — visible only when the parent
   .timeline-event has .is-open (toggled by the clientside delegate
   on .timeline-event__expand clicks in pages/admin.py). Without this
   discriminator the panel renders open on every event and the
   Activity timeline section grows unbounded. */
.timeline-event__detail { display: none; }
.timeline-event.is-open .timeline-event__detail {
  display: block;
  margin-top: 8px;
  border: 1px solid var(--hairline); border-radius: var(--radius-md);
  background: var(--paper); padding: 10px 14px;
}
.timeline-event__detail-row {
  display: flex; gap: 12px; padding: 3px 0;
  font-family: var(--font-mono); font-size: 11.5px; line-height: 1.5;
}
.timeline-event__detail-row dt {
  flex: 0 0 92px; margin: 0; color: var(--ink-3);
  font-size: 10px; letter-spacing: 0.06em; text-transform: uppercase;
}
.timeline-event__detail-row dd { margin: 0; min-width: 0; color: var(--ink); word-break: break-word; }
@media (prefers-reduced-motion: reduce) {
  .timeline-event__expand .chev { transition: none; }
}

/* ============ SPARKLINE (Admin panel · issue #107 / #A1) ============
   Inline 7-bar micro bar-chart — an at-a-glance trend with no axes, sized
   for a table cell (row-count over the last 7 days, Data Lake state). Bar
   heights are DATA: set them inline (`style="height:NN%"`). A near-zero bar
   in `--hairline` (`.sparkline__bar--muted`) flags a skipped / no-update
   day; the most recent day takes `--last` for a quiet "you are here"
   emphasis. Neutral by design — the row's `.status-pill` carries the
   sentiment, the sparkline carries only the shape; never tint it to
   duplicate the pill. Built from existing tokens; no new token. */
.sparkline {
  display: inline-flex; align-items: flex-end; gap: 2px;
  width: 64px; height: 26px;
}
.sparkline__bar { flex: 1 1 0; min-height: 2px; background: var(--ink-3); border-radius: 1px; }
.sparkline__bar--muted { background: var(--hairline); }
.sparkline__bar--last  { background: var(--ink-2); }

/* ============ RUN STRIP (Admin · Pipelines · issue #107 / #A1) ============
   Inline status-square history — the last N automation run outcomes for one
   workflow / flow, oldest left, newest right. Sibling of `.sparkline`: same
   "micro-viz in a table cell" family, but squares-for-STATUS, not bars-for-
   magnitude. Cell tone uses the timeline dot's vocabulary
   (`--ok / --fail / --skip / --running`); a bare `--hairline` cell is a slot
   with no run. Use for a CI / pipeline / flow run history; for a numeric
   trend use `.sparkline`. Built from existing tokens; no new token. */
.run-strip { display: inline-flex; align-items: center; gap: 3px; }
.run-strip__cell { width: 12px; height: 12px; border-radius: 2px; background: var(--hairline); }
.run-strip__cell--ok      { background: var(--success); }
.run-strip__cell--fail    { background: var(--error); }
.run-strip__cell--skip    { background: var(--ink-3); }
.run-strip__cell--running { background: var(--warning); }

/* ============ KV STRIP (Admin · status rows · issue #107 v2) ============
   Horizontal labelled mini-KPI strip — a row of N quiet labelled values
   inside a `.panel`. Sibling of `.kpi-row` but denser: smaller eyebrow,
   smaller value, no card chrome of its own (the wrapping `.panel` carries
   the border). Use when N≥3 short facts share one read (R2 backup specs:
   bucket / last-backup / size / retention · Circana monthly: period + 4
   country totals · ingest-worker telemetry: req / p95 / errors / writes).
   An optional trailing `.status-pill` aligns right inside the strip.
   The `--lead` modifier marks an opening item that's wider and
   visually separated — for the "headline + N satellites" composition.
   Built from existing tokens; no new token. */
.kv-strip {
  display: flex; align-items: stretch; gap: var(--space-4);
  padding: var(--space-3) var(--space-4);
}
.kv-strip__item { display: flex; flex-direction: column; justify-content: center; gap: 3px; min-width: 0; flex: 1; }
.kv-strip__item--lead {
  flex: 0 0 auto; min-width: 200px;
  padding-right: var(--space-4);
  border-right: 1px solid var(--hairline);
}
.kv-strip__k {
  font-family: var(--font-mono-heavy); font-weight: 700;
  font-size: 9.5px; letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--ink-3);
}
.kv-strip__v {
  font-family: var(--font-sans); font-weight: 600;
  font-size: 15px; color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.kv-strip__v--lg {
  /* Larger value variant — used on the `--lead` item so the headline
     reads prominent. Wordmark family per the masthead hierarchy. */
  font-family: var(--font-wordmark); font-weight: 400;
  font-size: 22px; line-height: 1.1;
}
.kv-strip__v.mono { font-family: var(--font-mono); font-size: 12.5px; font-weight: 500; }
.kv-strip__sub {
  font-family: var(--font-mono); font-size: 10.5px; color: var(--ink-3);
}
.kv-strip > .status-pill { margin-left: auto; align-self: center; }
@media (max-width: 768px) {
  .kv-strip { flex-direction: column; }
  .kv-strip__item--lead { border-right: 0; border-bottom: 1px solid var(--hairline); padding-right: 0; padding-bottom: var(--space-3); min-width: 0; }
}

/* ============ ACTION TOOLBAR (Admin · quick actions · issue #107 v2) ============
   Compact horizontal action row — N inline action slots separated by a
   vertical hairline. Replaces a tile-grid of quick actions when each one
   is small (one button + optional select + an "every-action audit-logged"
   caption): same actions, ~70% less vertical space.
   Each `.action-toolbar__item` = a button row (`.action-toolbar__row`) +
   an optional last-run caption (`.action-toolbar__last`). Rule 02
   (cherry-for-final-only) still applies: only the final action — typically
   build / save / send — gets `.btn-primary`; the rest are `.btn-secondary`
   / `.btn-ghost`. Destructive (`.btn-destructive`) appears only inside the
   confirm modal that the trigger opens, never on the toolbar itself.
   Built from existing tokens; no new token. */
.action-toolbar {
  display: flex; align-items: stretch; gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--hairline); border-radius: var(--radius-lg);
  background: var(--paper);
}
.action-toolbar__item { display: flex; flex-direction: column; gap: 4px; min-width: 0; flex: 1; }
.action-toolbar__row { display: flex; align-items: center; gap: 8px; }
.action-toolbar__last {
  font-family: var(--font-mono); font-size: 10px; color: var(--ink-3);
}
.action-toolbar__sep { width: 1px; background: var(--hairline); margin: 2px 0; }
@media (max-width: 900px) {
  .action-toolbar { flex-wrap: wrap; }
  .action-toolbar__sep { display: none; }
}

/* ================================================================
   PART C · CUSTOMER HEADER + DATA-TABLE GROUP/BRAND-EDGE + EXPLORER--FIXED
   The Open Orders design pass produced four patterns that didn't exist
   in the canon. Tracked in PART-C-CANONIZATION.md. Canonized 2026-05-24.
   ================================================================ */

/* .cust-header — the rich detail-header for an explorer whose detail is
   one CUSTOMER (Open Orders direction B). Three zones: identity (logo or
   monogram+name) · hairline divider · main (open total + brand bar). */
.cust-header { display: flex; gap: 16px; align-items: stretch; padding: 15px 18px; border-bottom: 1px solid var(--hairline); }
.cust-header__id { flex: 0 0 150px; display: flex; align-items: center; justify-content: center; gap: 10px; }
.cust-header__divider { flex: 0 0 1px; background: var(--hairline); }
.cust-header__main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; justify-content: center; }
.cust-header__summary { display: flex; align-items: baseline; gap: 9px; margin-bottom: 9px; flex-wrap: wrap; }
.cust-header__amount { font-family: var(--font-sans); font-size: 23px; font-weight: 600; letter-spacing: -0.015em; color: var(--ink); font-variant-numeric: tabular-nums; }
.cust-header__meta { font-family: var(--font-mono); font-size: 10.5px; color: var(--ink-3); }
.cust-header__meta b { color: var(--cherry-deep); font-weight: 500; }

/* Customer identity — either a real logo image (img.cust-logo) or the
   monogram fallback (.cust-monogram + .cust-name). The logo library is
   a consumer-app deliverable: assets/customer-logos/{custno}.svg, with
   monogram fallback when the file is missing. */
img.cust-logo { height: 19px; width: auto; max-width: 130px; object-fit: contain; display: block; }
.cust-monogram { display: inline-flex; align-items: center; justify-content: center; width: 32px; height: 32px; flex: 0 0 32px; border-radius: var(--radius-md); background: var(--pink-tint); border: 1px solid var(--pink-light); color: var(--pink-text); font-family: var(--font-sans); font-weight: 700; font-size: 14px; }
.cust-name { font-family: var(--font-sans); font-size: 15px; font-weight: 600; color: var(--ink); }

/* .dt-group — a group-header <tr> inside .data-table: opens a section in
   the table (e.g. an SKU table grouped by order_no — the group row
   carries order id + request date + status pill + value). */
.dt-group td { padding: 10px 14px 9px; border-top: 2px solid var(--hairline); }
.dt-group:first-child td { border-top: 0; }
.dt-group__row { display: flex; align-items: center; gap: 11px; flex-wrap: wrap; }
.dt-group__id { font-family: var(--font-mono-heavy); font-size: 11px; font-weight: 700; letter-spacing: 0.05em; color: var(--ink); }
.dt-group__date { font-family: var(--font-mono); font-size: 10.5px; color: var(--ink-3); }
.dt-group__date strong { color: var(--ink-2); font-weight: 500; }
.dt-group__val { font-family: var(--font-sans); font-size: 12px; font-weight: 600; color: var(--ink); font-variant-numeric: tabular-nums; }

/* .dt-brand-edge — a 3px left edge on a SKU row tying it to a brand;
   colour is data-driven via the --brand-edge custom property, set inline
   per row (the chart palette c1-c10 provides the brand colours, matched
   to the .cust-header brand bar). */
.dt-brand-edge td:first-child { box-shadow: inset 3px 0 0 var(--brand-edge, transparent); }

/* DEPRECATED — use .atlas-grid + .drawer (RULES §Open Orders A3). */
/* .explorer--fixed — explorer modifier: the body is a fixed-height
   rectangle; master and detail scroll internally via .explorer-scroll.
   Solves the ragged page-split when the two panes have unequal content. */
.explorer--fixed .explorer-body { height: 444px; }
.explorer--fixed .explorer-scroll { flex: 1 1 auto; overflow-y: auto; min-height: 0; }
.explorer--fixed .master-head { flex: 0 0 auto; }

/* ============ SALES REVIEW · F-Code.9 family (2026-05-31) ============
   Components introduced by the Sales · Overview + Compare + Countries
   redesign. All compose existing tokens — no new color / type / radius
   needed. Used by:
     - .dim-switch     : Sales · Compare page-identity dim switcher
                         (Category / Sub-category / License / Brand)
     - .snapshot       : 3-up mini KPI strip (Compare top mover / drag /
                         mix gainer · Overview digest cards) — data-driven,
                         NOT an insight (see .insight-line / .insight-pair)
     - .chartmode      : compact mode toggle for chart panel-head top-right
                         (Stacked / Cumulative / Achievement · $ / YoY ·
                         Week / Month / Year)
     - .corefilter     : Compare "Core only" toggle (hide dissolving cats
                         Skincare / Nail) — chip-style mini-toggle
     - .drillcrumb     : Compare drill state breadcrumb (← All categories ›
                         FACE) — appears between dim-switch and content
   ====================================================================== */

/* --- .dim-switch · page-identity segmented (Compare) -------------------
   Sits below the page head, defines what the page is slicing.
   Different from .tabs (sub-tab nav) and from .segmented (in-panel mode):
   bigger affordance, eyebrow label on the left, ink-dark active state. */
.dim-switch-row {
  display: flex; align-items: center; gap: 16px;
  padding: 8px 0 16px;
  border-bottom: 1px solid var(--hairline);
  margin-bottom: 14px;
}
.dim-switch-label {
  font-family: var(--font-mono-heavy); font-size: 10px; font-weight: 700;
  letter-spacing: 0.22em; text-transform: uppercase;
  color: var(--ink-3); white-space: nowrap;
}
.dim-switch {
  display: inline-flex; padding: 3px;
  border: 1px solid var(--hairline);
  border-radius: var(--radius-md);
  background: var(--paper);
}
.dim-switch__btn {
  font-family: var(--font-sans); font-size: 12.5px; font-weight: 500;
  padding: 7px 14px; border: 0; background: transparent;
  color: var(--ink-2); cursor: pointer;
  border-radius: 6px;
  transition: background var(--motion-base) var(--motion-ease-out), color var(--motion-base);
  letter-spacing: -0.005em;
}
.dim-switch__btn:hover {
  background: var(--pink-tint); color: var(--ink);
}
.dim-switch__btn.is-active {
  background: var(--ink); color: var(--paper);
  font-weight: 600;
}

/* --- .corefilter · Core / All chip toggle (Compare) --------------------
   Sits to the right of .dim-switch (margin-left: auto), small mini-toggle
   to hide dissolving categories. Disabled state when in drilled view. */
.corefilter {
  margin-left: auto;
  display: inline-flex; align-items: center; gap: 8px;
  font-family: var(--font-mono); font-size: 11px; color: var(--ink-2);
}
.corefilter__hint { font-style: italic; color: var(--ink-3); }
.corefilter__toggle {
  display: inline-flex; padding: 2px;
  border: 1px solid var(--hairline);
  border-radius: var(--radius-md);
  background: var(--paper);
}
.corefilter__btn {
  font-family: var(--font-mono-heavy); font-size: 10px; font-weight: 700;
  letter-spacing: 0.12em; text-transform: uppercase;
  padding: 5px 10px; border: 0; background: transparent;
  color: var(--ink-3); cursor: pointer;
  border-radius: 5px;
}
.corefilter__btn.is-active {
  background: var(--pink-tint); color: var(--pink-deep);
}
.corefilter__btn:disabled { opacity: 0.4; cursor: not-allowed; }

/* --- .drillcrumb · drill state breadcrumb (Compare D2) ----------------
   Appears between dim-switch row and content when user drills into a dim
   value. Shows back link + path + drill metadata. Pink-tint bg signals
   "you are inside a drill". */
.drillcrumb {
  display: flex; align-items: center; gap: 10px;
  padding: 10px 14px;
  background: color-mix(in srgb, var(--pink-tint) 28%, white);
  border: 1px solid var(--pink-light);
  border-radius: var(--radius-md);
  margin-bottom: 14px;
}
.drillcrumb__back {
  font-family: var(--font-mono-heavy); font-size: 10px; font-weight: 700;
  letter-spacing: 0.18em; text-transform: uppercase;
  color: var(--pink-deep); cursor: pointer; text-decoration: none;
}
.drillcrumb__back:hover { text-decoration: underline; }
.drillcrumb__sep { color: var(--ink-3); opacity: 0.6; }
.drillcrumb__current {
  font-family: var(--font-sans); font-size: 14px; font-weight: 600;
  color: var(--ink); letter-spacing: -0.01em;
}
.drillcrumb__meta {
  margin-left: auto;
  font-family: var(--font-mono); font-size: 11px; font-weight: 500;
  color: var(--ink-2);
}

/* --- .snapshot · 3-up mini KPI strip (Compare + Overview digest) -----
   Data-driven 3-up panel sitting above a chart. Different from .insight-
   editorial (that's narrative); from .kpi-row (that's headline KPIs);
   from .hero-kpi-card (that's sentiment-bordered). Snapshot is "what
   moved most" in 3 quick reads — top mover / biggest drag / mix gainer
   pattern. All items composed inline with a pink left border (no card
   chrome, lives inside a panel). */
.snapshot {
  display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px;
  padding: 14px 16px;
  background: color-mix(in srgb, var(--pink-tint) 28%, white);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-md);
}
.snapshot__item {
  display: flex; flex-direction: column; gap: 4px;
  padding-left: 14px;
  border-left: 2px solid var(--pink);
}
.snapshot__item:first-child { padding-left: 0; border-left: 0; }
.snapshot__eye {
  font-family: var(--font-mono-heavy); font-size: 9.5px; font-weight: 700;
  letter-spacing: 0.22em; text-transform: uppercase;
  color: var(--ink-3);
}
.snapshot__headline {
  font-family: var(--font-sans); font-weight: 600; font-size: 15px;
  color: var(--ink);
  display: inline-flex; align-items: baseline; gap: 6px;
}
.snapshot__headline .y {
  font-family: var(--font-mono); font-weight: 700; font-size: 12px;
}
.snapshot__headline .y--up   { color: var(--success-deep); }
.snapshot__headline .y--down { color: var(--cherry-deep);  }
.snapshot__headline .y--flat { color: var(--ink-3); }
.snapshot__meta {
  font-family: var(--font-mono); font-size: 10.5px;
  color: var(--ink-3);
}

/* --- .chartmode · compact mode toggle in chart panel-head -------------
   Sits top-right of a chart's panel-head. Stacked when multiple mode
   axes (granularity + display mode) coexist. Different from .dim-switch
   (page identity) and from .segmented (in-panel filter). Compact mono
   uppercase buttons, ink-dark active state. */
.chartmode {
  display: inline-flex; padding: 2px;
  border: 1px solid var(--hairline);
  border-radius: var(--radius-md);
  background: var(--paper);
}
.chartmode__btn {
  font-family: var(--font-mono-heavy); font-size: 10px; font-weight: 700;
  letter-spacing: 0.12em; text-transform: uppercase;
  padding: 5px 10px; border: 0; background: transparent;
  color: var(--ink-3); cursor: pointer;
  border-radius: 5px;
}
.chartmode__btn:hover { color: var(--ink); }
.chartmode__btn.is-active {
  background: var(--ink); color: var(--paper);
}
.chartmode-set { display: flex; gap: 6px; align-items: center; }
/* When multiple .chartmode siblings coexist, .chartmode-set wraps them
   for consistent gap. Sales · Overview hero uses 2 sets: [Week|Month|Year]
   + [Stacked|Cumul|Achievement]; .metric-select sits separately on the
   chart title (left side), not inside .chartmode-set. */

/* --- .digest-card · mini-snapshot card pointing at a deep sub-tab -----
   F-Code.9g (Sales review 2026-05-31). Used in 3-up grid on Sales ·
   Overview to give a "where to dig" digest — Categories pulse, Countries
   pulse, Pipeline pulse — each card has a title + CTA to the deep tab +
   3 rows of labeled metrics. Different from .snapshot (which is the
   inline 3-up STRIP on Compare/Overview · top mover / drag / mix gainer
   — same dim, 3 facets); .digest-card is a CARD with header + CTA
   pointing to a different page, each card a different sub-tab digest.
   Composes plain table-style rows; no new tokens. */
.digest-card {
  background: var(--paper);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-md);
  padding: 14px 16px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.digest-card__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
  padding-bottom: 8px;
  border-bottom: 1px solid var(--hairline);
}
.digest-card__title {
  font-family: var(--font-mono-heavy);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--ink);
}
.digest-card__cta {
  font-family: var(--font-mono-heavy);
  font-size: 9.5px;
  font-weight: 700;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--pink-deep);
  cursor: pointer;
  text-decoration: none;
}
.digest-card__cta:hover { text-decoration: underline; }
.digest-card__row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
  padding: 4px 0;
}
.digest-card__row-lbl {
  font-family: var(--font-mono-heavy);
  font-size: 8.5px;
  font-weight: 700;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.digest-card__row-val {
  font-family: var(--font-sans);
  font-size: 13px;
  font-weight: 600;
  color: var(--ink);
}
.digest-card__row-val .y {
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 700;
  margin-left: 4px;
}
.digest-card__row-val .y--up   { color: var(--success-deep); }
.digest-card__row-val .y--down { color: var(--cherry-deep); }
.digest-card__row-val .y--flat { color: var(--ink-3); }

/* --- .projection · EOM/EOQ deterministic projection panel -------------
   F-Code.9h (Sales review 2026-05-31). Used on Sales · Overview to
   show end-of-month / end-of-quarter projection alongside a trajectory
   chart. Deterministic arithmetic (MTD + open ETA + budget pace at
   remaining days) — NO ML forecast. 2-column layout: KPI list on left
   + trajectory chart on right. The KPI rows have a label with a quiet
   italic hint subtitle + a right-aligned value with a delta below.
   Composes existing tokens — no new color / type. */
.projection {
  display: grid;
  grid-template-columns: 1.2fr 1fr;
  gap: 14px;
}
/* Sales v2 (feedback 2026-06-05): un solo pannello — header (titolo + model-card
   a destra) + griglia "3 numeri | chart GRANDE". Il chart (1fr) è molto più largo
   della colonna numeri (max 300px), come il mockup; i box interni sono senza
   bordo perché vivono dentro un `.panel`. */
.projection__head-row {
  display: flex; align-items: center; gap: 10px; margin-bottom: 14px;
}
.projection__modelcard {
  margin-left: auto; font-family: var(--font-mono);
  font-size: 10.5px; color: var(--ink-3); text-align: right;
}
.projection__grid {
  display: grid; grid-template-columns: minmax(0, 300px) 1fr;
  gap: 22px; align-items: center;
}
@media (max-width: 880px) { .projection__grid { grid-template-columns: 1fr; } }
.projection__kpis {
  display: flex;
  flex-direction: column;
  gap: 18px;
}
.projection__head {
  font-family: var(--font-sans);
  font-size: 14px;
  font-weight: 600;
  color: var(--ink);
  padding-bottom: 8px;
  border-bottom: 1px solid var(--hairline);
}
.projection__kpi {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 14px;
  align-items: baseline;
}
.projection__kpi-lbl {
  font-family: var(--font-mono-heavy);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.projection__kpi-hint {
  font-family: var(--font-sans);
  font-size: 10.5px;
  color: var(--ink-3);
  display: block;
  text-transform: none;
  letter-spacing: 0;
  margin-top: 2px;
  font-weight: 400;
  font-style: italic;
}
.projection__kpi-val {
  font-family: var(--font-sans);
  font-size: 18px;
  font-weight: 600;
  color: var(--ink);
  text-align: right;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.01em;
}
.projection__kpi-delta {
  display: block;
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 700;
  margin-top: 2px;
}
.projection__kpi-delta--up   { color: var(--success-deep); }
.projection__kpi-delta--down { color: var(--cherry-deep); }
.projection__traj { min-width: 0; }
.projection__traj-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
  margin-bottom: 6px;
}
.projection__traj-title {
  font-family: var(--font-sans);
  font-size: 14px;
  font-weight: 600;
  color: var(--ink);
}
.projection__traj-meta {
  font-family: var(--font-mono);
  font-size: 10.5px;
  color: var(--ink-3);
}
.projection__traj-foot {
  font-family: var(--font-mono);
  font-size: 10px;
  color: var(--ink-3);
  margin-top: 6px;
}


/* ============ INSIGHT PAIR · insight (ratio/diff/dark) + .figure-strip ============
   2-col host pairing a canonical insight (left — .insight-ratio / .insight-diff /
   .insight-dark / .insight-viz, the narrative) with a .figure-strip "By the
   numbers" (right, the hard numbers). The post-chart insight strip on Sales /
   Compare / Stock (and any tab). Insight is display-only inside the pair. */
.insight-pair {
  display: grid; grid-template-columns: 1.4fr 1fr;
  gap: 16px; align-items: stretch;
}
.insight-pair .figure-strip { height: 100%; box-sizing: border-box; }
@media (max-width: 880px) { .insight-pair { grid-template-columns: 1fr; } }

/* ============ INSIGHT — TICKER / DARK / RATIO (Stock pass · approved 2026-06) ============
   Headline voice = --font-display (Fraunces, roman 500-560 + italic for emphasis);
   body = --font-sans; figures = --font-mono. Tone via .is-down / .is-warn / .is-up.
   RULE (RULES.md): an insight never runs alone horizontally — it sits inside
   .insight-pair (insight + .figure-strip "by the numbers"). The ONLY standalone
   full-width exception is .insight-ticker. Half-width cap = --space-insight-max;
   inside .insight-pair the insight fills its column. On 27"+ the column caps and
   two halves sit 2-up rather than one stretched strip.
   Dark variant: light text via color-mix on --paper/--ink (no literals). */

/* shared bits */
.insight-kicker { font-family: var(--font-mono-heavy); font-weight: 700; font-size: 9.5px; letter-spacing: 0.2em; text-transform: uppercase; color: var(--ink-3); }
.insight-cta    { font-family: var(--font-mono-heavy); font-weight: 700; font-size: 10px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--pink-deep); cursor: pointer; white-space: nowrap; }
.insight-body   { font-family: var(--font-sans); font-size: 12.5px; color: var(--ink-2); line-height: 1.6; }
.insight-body b { color: var(--ink); font-weight: 600; }

/* TICKER — the standalone exception (one line, full-width, dot = tone) */
.insight-ticker { display: flex; align-items: center; gap: 12px; padding: 11px 14px; border: 1px solid var(--hairline); border-radius: var(--radius-md); background: var(--paper); }
.insight-ticker__dot { width: 8px; height: 8px; border-radius: var(--radius-pill); flex: 0 0 8px; background: var(--ink-3); }
.insight-ticker.is-down .insight-ticker__dot { background: var(--cherry); }
.insight-ticker.is-warn .insight-ticker__dot { background: var(--warning); }
.insight-ticker.is-up   .insight-ticker__dot { background: var(--success); }
.insight-ticker__eye { font-family: var(--font-mono-heavy); font-weight: 700; font-size: 10px; letter-spacing: 0.18em; text-transform: uppercase; color: var(--ink-3); white-space: nowrap; }
.insight-ticker__txt { font-family: var(--font-sans); font-size: 13px; color: var(--ink); }
.insight-ticker__txt b { font-weight: 600; }
.insight-ticker .insight-cta { margin-left: auto; }

/* RATIO — disproportion bars (the form is the insight). Pairs with .figure-strip. */
.insight-ratio { border: 1px solid var(--hairline); border-left: 3px solid var(--cherry); border-radius: var(--radius-md); background: var(--paper); padding: 17px 19px; display: flex; flex-direction: column; max-width: var(--space-insight-max); }
.insight-pair .insight-ratio { max-width: none; }
.insight-ratio.is-warn { border-left-color: var(--warning); }
.insight-ratio__head { font-family: var(--font-display); font-weight: 560; font-size: 22px; line-height: 1.16; letter-spacing: -0.014em; color: var(--ink); margin: 8px 0 18px; }
.insight-ratio__head i { font-style: italic; }
.insight-ratio__row { display: grid; grid-template-columns: 116px 1fr; gap: 12px; align-items: center; margin-bottom: 12px; }
.insight-ratio__lab { font-family: var(--font-mono-heavy); font-weight: 700; font-size: 9.5px; letter-spacing: 0.08em; text-transform: uppercase; color: var(--ink-2); text-align: right; line-height: 1.4; }
.insight-ratio__lab small { display: block; color: var(--ink-3); font-weight: 500; }
.insight-ratio__fill { height: 34px; border-radius: var(--radius-sm); display: flex; align-items: center; padding-left: 12px; }
.insight-ratio__fill--base { background: var(--ink-2); }
.insight-ratio__fill--peak { background: var(--cherry); }
.insight-ratio__pct { font-family: var(--font-display); font-weight: 600; font-size: 25px; line-height: 1; color: var(--paper); letter-spacing: -0.02em; }
.insight-ratio__foot { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-top: auto; padding-top: 14px; }
.insight-ratio__src { font-family: var(--font-mono); font-size: 10px; color: var(--ink-3); }

/* DARK STATEMENT — ink panel, "stop and read". Pairs with .figure-strip.
   Light text uses color-mix on --paper over --ink — token-based, no literals. */
.insight-dark { background: var(--ink); border-radius: var(--radius-md); padding: 19px 21px; display: flex; flex-direction: column; height: 100%; box-sizing: border-box; max-width: var(--space-insight-max); }
.insight-pair .insight-dark { max-width: none; }
.insight-dark .insight-kicker { color: var(--pink-light); }
.insight-dark__head { font-family: var(--font-display); font-weight: 500; font-size: 24px; line-height: 1.18; letter-spacing: -0.01em; color: var(--paper); margin-top: 11px; }
.insight-dark__head i { font-style: italic; color: color-mix(in srgb, var(--pink-light) 82%, var(--paper)); }
.insight-dark__body { font-family: var(--font-sans); font-size: 12.5px; line-height: 1.62; color: color-mix(in srgb, var(--paper) 78%, var(--ink)); margin-top: 12px; }
.insight-dark__body b { color: var(--paper); font-weight: 600; }
.insight-dark__data { font-family: var(--font-mono); font-size: 10.5px; color: color-mix(in srgb, var(--paper) 52%, var(--ink)); margin-top: 12px; letter-spacing: 0.02em; }
.insight-dark__foot { display: flex; justify-content: space-between; align-items: center; margin-top: auto; padding-top: 14px; border-top: 1px solid color-mix(in srgb, var(--paper) 14%, transparent); }
.insight-dark__src { font-family: var(--font-mono); font-size: 10px; color: color-mix(in srgb, var(--paper) 45%, var(--ink)); }
.insight-dark .insight-cta { color: var(--pink-light); }

/* VIZ — headline + embedded chart ("show + tell"). The chart slot .insight-viz__chart
   holds ANY viz (sparkline by default, but also area / mini-bars / etc.). Pairs with
   .figure-strip. .insight-ratio is the ratio-specialised cousin; this is the general one. */
.insight-viz { border: 1px solid var(--hairline); border-left: 3px solid var(--cherry); border-radius: var(--radius-md); background: var(--paper); padding: 17px 19px; display: flex; flex-direction: column; max-width: var(--space-insight-max); }
.insight-pair .insight-viz { max-width: none; }
.insight-viz.is-warn { border-left-color: var(--warning); }
.insight-viz.is-up   { border-left-color: var(--success); }
.insight-viz__head { font-family: var(--font-display); font-weight: 560; font-size: 22px; line-height: 1.16; letter-spacing: -0.012em; color: var(--ink); margin: 8px 0 0; }
.insight-viz__head i { font-style: italic; }
.insight-viz__chart { width: 100%; margin: 14px 0 0; display: block; }
.insight-viz__foot { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-top: auto; padding-top: 14px; }
.insight-viz__src { font-family: var(--font-mono); font-size: 10px; color: var(--ink-3); }

/* PAIR REVERSE — figure-strip a sinistra, insight a destra. Per ALTERNARE la
   direzione della coppia pagina-su-pagina e dare ritmo all'impaginato (DOM invariato,
   solo riassegnazione di colonna). DEFAULT: una sola coppia insight per vista — non
   impilare due blocchi pesanti uno sopra l'altro (usa il ticker per un 2° segnale leggero). */
.insight-pair--reverse { grid-template-columns: 1fr 1.4fr; }
.insight-pair--reverse > :first-child { grid-column: 2; }
.insight-pair--reverse > :last-child  { grid-column: 1; }
@media (max-width: 880px) {
  .insight-pair--reverse { grid-template-columns: 1fr; }
  .insight-pair--reverse > :first-child,
  .insight-pair--reverse > :last-child { grid-column: auto; }
}

/* ============ MERCHANDISING BOARD — .mz-* (Merch pass · Andrea 2026-06-04) ============
   Tray-level planogram for Brand · Merchandising. A single 12-col grid (.mz-board)
   on which trays (.mz-tray) and graphic zones (.mz-graphic) are positioned via
   grid-column / grid-row — rowspan for a multi-shelf hotspot. The geometry is
   data-driven from MERCH.xlsx (silver/merch); never hand-positioned. A tray is a
   clickable ENTITY — drill = the canonical .drawer (640px progression-first), the
   same family as SKU / Client / Order (Modal review unificata). Performance tint
   (.mz-tray.h-*) reads index-vs-unit on success / cherry, coherent with .matrix-heat.
   Photo view (.mz-photo) overlays the same geometry on the POG asset — no manual
   hotspot mapping. KPI strips inside the drawer stack in .mz-kvbox (hairline wrapper
   around canonical .kv-strip). Reference: brand-mockups/merch.html.
   Built from existing tokens; no new token. */
.mz-controls { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin: 14px 0 16px; }
.mz-controls .spacer { flex: 1; }

/* board (sinistra) + drawer (destra). Il drawer prende PIÙ spazio del board
   (la tabella prodotti respira, niente scroll orizzontale); il board resta
   contenuto invece di stirarsi. Il drawer ha un min (680px) per far stare la
   tabella prodotti senza overflow. Sotto 1280px le due colonne si impilano. */
.mz-layout { display: grid; grid-template-columns: minmax(0, 1fr) minmax(680px, 1.2fr); gap: 18px; align-items: start; margin-top: 16px; }
/* margin-bottom: 0 — il margine default del .panel sul board lo disallineava dal
   drawer. align-items:start (il board resta alla sua altezza naturale, COSTANTE
   = stesso planogram per ogni tray) + il drawer capato a quell'altezza via JS
   (assets/merch.js syncDrawerHeight) → board e drawer alti uguali e la sezione
   NON cambia altezza cambiando tray (no stretch/shrink). */
.mz-layout > * { min-width: 0; margin-bottom: 0; }
@media (max-width: 1280px) { .mz-layout { grid-template-columns: 1fr; } }

/* the wall — one 12-col grid; trays place themselves with grid-area (rowspan for the hotspot) */
.mz-board { display: grid; grid-template-columns: repeat(12, 1fr); gap: 6px 7px; padding: 12px; background: color-mix(in srgb, var(--ink) 4%, var(--paper)); border: 1px solid var(--hairline); border-radius: var(--radius-lg); }
.mz-graphic { border-radius: var(--radius-md); padding: 16px 14px; text-align: center; background: var(--surface); border: 1px solid color-mix(in srgb, var(--pink) 18%, var(--paper)); z-index: 1; }
.mz-graphic--footer { background: var(--ink); border-color: var(--ink); }
.mz-graphic--hotspot { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px; cursor: pointer; font-family: var(--font-sans); transition: transform 140ms cubic-bezier(0.23,1,0.32,1), box-shadow 140ms cubic-bezier(0.23,1,0.32,1), border-color 140ms ease; }
@media (hover: hover) and (pointer: fine) {
  .mz-graphic--hotspot:hover { transform: translateY(-1px); box-shadow: var(--shadow-card-hover); border-color: var(--pink); }
}
.mz-graphic--hotspot:active { transform: scale(0.97); }
.mz-graphic--hotspot:focus-visible { outline: 2px solid var(--pink); outline-offset: 2px; }
.mz-graphic--hotspot.is-selected { border-color: var(--pink); box-shadow: inset 0 0 0 1px var(--pink); }
.mz-graphic .wm { font-family: var(--font-wordmark); font-size: 18px; color: var(--ink); letter-spacing: 0.01em; }
.mz-graphic--footer .wm { color: var(--paper); }
.mz-graphic .gl { font-family: var(--font-mono); font-size: 8.5px; letter-spacing: 0.16em; text-transform: uppercase; color: var(--ink-3); margin-top: 3px; }
.mz-graphic--footer .gl { color: color-mix(in srgb, var(--paper) 45%, var(--ink)); }

.mz-rail { grid-column: 1 / -1; height: 4px; border-radius: 2px; background: color-mix(in srgb, var(--ink) 10%, var(--paper)); z-index: 0; margin: -1px 2px; }

.mz-tray { position: relative; z-index: 1; text-align: left; font-family: var(--font-sans); background: var(--paper); border: 1px solid var(--hairline); border-radius: var(--radius-md); padding: 8px 9px 9px; cursor: pointer; transition: transform 140ms cubic-bezier(0.23,1,0.32,1), box-shadow 140ms cubic-bezier(0.23,1,0.32,1), border-color 140ms ease; }
@media (hover: hover) and (pointer: fine) {
  .mz-tray:hover { transform: translateY(-1px); box-shadow: var(--shadow-card-hover); border-color: var(--pink-light); }
}
.mz-tray:active { transform: scale(0.97); }
.mz-tray:focus-visible { outline: 2px solid var(--pink); outline-offset: 2px; }
.mz-tray.is-selected { border-color: var(--pink); box-shadow: inset 0 0 0 1px var(--pink); } /* pink edge = selected (B10) */
.mz-tray__top { display: flex; align-items: baseline; justify-content: space-between; gap: 6px; }
.mz-tray__id { font-family: var(--font-mono-heavy); font-weight: 700; font-size: 9px; letter-spacing: 0.1em; color: var(--pink-deep); background: var(--pink-tint); padding: 1px 5px; border-radius: 4px; }
.mz-tray__fac { font-family: var(--font-mono); font-size: 9.5px; color: var(--ink-3); font-variant-numeric: tabular-nums; }
.mz-tray__name { font-family: var(--font-sans); font-weight: 500; font-size: 11px; line-height: 1.25; color: var(--ink); margin-top: 4px; }
.mz-tray__val { font-family: var(--font-mono); font-size: 9.5px; color: var(--ink-2); margin-top: 3px; font-variant-numeric: tabular-nums; min-height: 13px; }
.mz-tray__facings { display: flex; gap: 2px; margin-top: 6px; }
.mz-tray__facings i { flex: 1 1 0; max-width: 8px; height: 10px; border-radius: 2px; background: var(--surface); border: 1px solid color-mix(in srgb, var(--pink) 14%, var(--paper)); }
.mz-tray__facings .more { font-family: var(--font-mono); font-size: 8.5px; color: var(--ink-3); align-self: center; padding-left: 2px; }

/* performance tint — index vs unit average (coherent with .matrix-heat: green above / cherry below) */
.mz-tray.h-p2 { background: color-mix(in srgb, var(--success) 17%, var(--paper)); }
.mz-tray.h-p1 { background: color-mix(in srgb, var(--success) 8%, var(--paper)); }
.mz-tray.h-n1 { background: color-mix(in srgb, var(--cherry) 7%, var(--paper)); }
.mz-tray.h-n2 { background: color-mix(in srgb, var(--cherry) 15%, var(--paper)); }

/* photo view — POG asset (background-image set inline) + hotspot overlay on the same geometry */
.mz-photo { position: relative; border-radius: var(--radius-lg); overflow: hidden; border: 1px solid var(--hairline); background-size: cover; background-position: center; }
.mz-photo__note { position: absolute; inset: auto 0 0 0; display: flex; align-items: center; justify-content: center; gap: 6px; pointer-events: none; text-align: center; padding: 8px 12px; background: color-mix(in srgb, var(--ink) 62%, transparent); z-index: 2; }
.mz-photo__note .t1 { font-family: var(--font-mono-heavy); font-weight: 700; font-size: 10px; letter-spacing: 0.18em; text-transform: uppercase; color: var(--paper); }
.mz-photo__note .t2 { font-family: var(--font-mono); font-size: 10.5px; color: color-mix(in srgb, var(--paper) 70%, transparent); }
.mz-photo .mz-board { background: transparent; border: 0; }
.mz-photo .mz-tray { background: color-mix(in srgb, var(--paper) 35%, transparent); border: 1.5px dashed color-mix(in srgb, var(--pink) 55%, var(--paper)); }
.mz-photo .mz-tray .mz-tray__name, .mz-photo .mz-tray .mz-tray__facings, .mz-photo .mz-tray .mz-tray__val { display: none; }
.mz-photo .mz-graphic { opacity: 0.45; }

/* drawer host — the board's right pane; riempie la sua colonna (niente cap di
   larghezza), altezza = contenuto del drawer (nessuna scrollbar interna). */
.mz-drawerhost { display: flex; overflow: hidden; }
.mz-drawerhost .drawer { flex: 1; display: flex; flex-direction: column; border: 1px solid var(--hairline); border-radius: var(--radius-lg); overflow: hidden; max-height: none; }
.mz-drawerhost .d-body { flex: 1; overflow: auto; transition: opacity 160ms ease, filter 160ms ease; }
.mz-drawerhost .d-foot { margin-top: auto; }
.mz-drawerhost.is-swapping .d-body { opacity: 0.45; filter: blur(2px); }
/* Tabelle del drawer: table-layout:fixed + colgroup → la cella clippa e NON si
   allarga mai. Ellipsis hardening: i nomi lunghi vanno in "…", non spingono la
   colonna (td overflow:hidden + span block/min-width:0/text-overflow:ellipsis
   dentro la cella flex .mz-prodcell). */
.mz-drawerhost .kpi-table { table-layout: fixed; width: 100%; }
.mz-drawerhost .kpi-table td { overflow: hidden; }
.mz-drawerhost .kpi-table .sku-thumb { flex: 0 0 36px; }
/* .mz-prodcell — foto prodotto (.sku-thumb 36px, pattern Best/Worst) + nome SKU
   in una cella flex; il nome assorbe e va in ellipsis, la foto resta fissa. */
.mz-prodcell { display: flex; align-items: center; gap: 10px; min-width: 0; max-width: 100%; }
.mz-prodcell .td-sku-name { min-width: 0; flex: 1 1 0; overflow: hidden; }
.mz-drawerhost .td-sku-name { min-width: 0; }
.mz-drawerhost .td-sku-name span { display: block; max-width: 100%; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.mz-drawerhost .td-sku-name .sku-code { display: inline-block; }

/* stacked KPI strips inside the drawer — hairline wrapper around canonical .kv-strip */
.mz-kvbox { border: 1px solid var(--hairline); border-radius: var(--radius-md); }
.mz-kvbox .kv-strip + .kv-strip { border-top: 1px solid var(--hairline); }

/* pagination footer row — info left, .paginator-sm right (drawer products + tray league) */
.mz-paginrow { display: flex; align-items: center; justify-content: space-between; gap: 10px; flex-wrap: wrap; margin-top: 10px; }
.mz-paginrow .mz-paginrow__info { font-family: var(--font-mono); font-size: 11px; color: var(--ink-3); }

/* league index bar — mini proportion bar in the $/facing league table */
.mz-idx { display: inline-flex; align-items: center; gap: 8px; justify-content: flex-end; }
.mz-idx__bar { width: 64px; height: 6px; border-radius: 3px; background: color-mix(in srgb, var(--ink) 7%, var(--paper)); overflow: hidden; }
.mz-idx__bar i { display: block; height: 100%; border-radius: 3px; background: var(--pink); }
.mz-idx__bar i.neg { background: var(--cherry); }

/* tray league — la riga del tray selezionato porta il pink edge B10, lo STESSO
   marker di selezione del board (.mz-tray.is-selected): selezione condivisa
   board ↔ league, sempre visibile anche senza click sulla CTA del drawer. */
.mz-league tbody tr.is-selected td { background: color-mix(in srgb, var(--pink) 5%, var(--paper)); }
.mz-league tbody tr.is-selected td:first-child { box-shadow: inset 3px 0 0 var(--pink); }

/* lifecycle tag chips — famiglia .mz-tag (colonna Tag della products table del
   drawer; derivata dallo Status 2026 assortment + conversion kit in fase di
   build silver, NON a mano). Sostituisce .mz-new. Tooltip su ogni chip (title=). */
.mz-tag { font-family: var(--font-mono-heavy); font-weight: 700; font-size: 8px; letter-spacing: 0.08em; text-transform: uppercase; padding: 1px 6px; border-radius: var(--radius-pill); white-space: nowrap; cursor: help; }
.mz-tag--new   { color: var(--pink-deep); background: var(--pink-tint); }                                    /* NEW 26 (kit) / NEW 25 (2nd wave) */
.mz-tag--h     { color: var(--info-deep); background: color-mix(in srgb, var(--info) 14%, var(--paper)); }   /* H-CODE — nuova formula, codice H attivo */
.mz-tag--hpend { color: var(--warning-deep); background: var(--cream); border: 1px solid var(--open-edge); } /* H-PEND — codice H non ancora attivo */
.mz-tag--del   { color: var(--cherry-deep); background: color-mix(in srgb, var(--cherry) 10%, var(--paper)); } /* DEL — phase-out */

@media (prefers-reduced-motion: reduce) {
  .mz-tray, .mz-graphic--hotspot, .mz-drawerhost .d-body { transition: none; }
  .mz-tray:hover, .mz-tray:active { transform: none; }
}

/* --- .pm-lens — priority-queue lens chip (Stock · Control tower). Tags each row
   of the cross-cutting worklist (`.atlas-grid`) with the pressure it belongs to
   and doubles as a jump to that tab (`data-goto`). One tone per lens — the lens's
   own deep ink on a faint wash of its family: `--ful` Fulfilment / can't ship
   (info) · `--age` Ageing / won't sell (warning) · `--inc` Incoming / just landed
   (success). The row's Issue stays a SEPARATE trailing `.status-pill`, never
   merged into the lens. Mono-heavy uppercase pill. Canonized from
   brand-mockups/stock-redesign-B.html (Control Tower B v3 · Andrea 2026-06-22). --- */
.pm-lens { font-family: var(--font-mono-heavy); font-size: 9px; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; padding: 2px 7px; border-radius: var(--radius-pill); white-space: nowrap; cursor: pointer; }
.pm-lens--ful { color: var(--info-deep); background: color-mix(in srgb, var(--info) 14%, white); }       /* Fulfilment — can't ship */
.pm-lens--age { color: var(--warning-deep); background: color-mix(in srgb, var(--warning) 16%, white); } /* Ageing — won't sell */
.pm-lens--inc { color: var(--success-deep); background: color-mix(in srgb, var(--success) 16%, white); } /* Incoming — just landed */

/* ============================================================
   LAUNCH LANE (Brand · Innovations V11 · this-month conversion)
   Pattern composto: corsia per-brand con barra Pipeline + Replenishment
   (.prog-bar canon, repl = teal .prog-bar__seg--repl, open = amber, marker =
   pace ink) + chip motion (pipeline pink / replenishment teal) + chip brand-
   identità tinted (accent brand) + stato "data incoming" per-brand. Click =
   filtra la sezione sul brand (mirror del chip top-bar). Canonizzato da
   brand-mockups/innovations-redesign-proposals.html (Andrea 2026-06-24).
   convenzione colori: pipe/actual = pink · repl = teal · open = amber · pace = ink. --- */
.pm-lanes { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 16px; }
.pm-lanes--focus { grid-template-columns: 1fr; }   /* un brand filtrato = full-width */
@media (max-width: 980px) { .pm-lanes { grid-template-columns: 1fr; } }
.pm-lane { border: 1px solid var(--hairline); border-radius: var(--radius-md); padding: 12px 14px; background: var(--paper); animation: atlasFade .3s ease; }
.pm-lane--clickable { cursor: pointer; width: 100%; text-align: left; font: inherit; color: inherit; transition: box-shadow .15s ease; }
.pm-lane--clickable:hover { box-shadow: 0 4px 16px rgba(10, 10, 15, 0.08); }
.pm-lanehd { display: flex; align-items: center; gap: 8px; margin-bottom: 4px; }
.pm-lane__gap { margin-left: auto; font-family: var(--font-mono); font-size: 11px; color: var(--cherry-deep); font-weight: 600; }
.pm-barlbl { font-family: var(--font-mono); font-weight: 700; font-size: 9px; letter-spacing: 0.14em; text-transform: uppercase; color: var(--ink-2); margin: 12px 0 5px; }
.pm-barlbl--repl { color: var(--info-deep); }
.pm-mini { margin-top: 12px; font-family: var(--font-mono); font-size: 11px; color: var(--ink-2); line-height: 1.5; }
.pm-mini b { color: var(--cherry-deep); }
.pm-motion { font-family: var(--font-mono); font-weight: 700; font-size: 9px; letter-spacing: 0.1em; text-transform: uppercase; padding: 2px 7px; border-radius: var(--radius-pill); white-space: nowrap; }
.pm-motion--pipe { background: var(--pink-tint); color: var(--pink-text); }
.pm-motion--repl { background: color-mix(in srgb, var(--info) 18%, white); color: var(--info-deep); }
.pm-brandchip { font-family: var(--font-mono); font-weight: 700; font-size: 10px; letter-spacing: 0.12em; text-transform: uppercase; padding: 3px 9px; border-radius: var(--radius-pill); white-space: nowrap; }
.pm-brandchip--wnw { background: color-mix(in srgb, var(--plum) 14%, white); color: var(--plum); }
.pm-brandchip--pf { background: color-mix(in srgb, var(--pink) 16%, white); color: var(--pink-deep); }
.pm-brandchip--lsmk { background: color-mix(in srgb, var(--c9) 16%, white); color: var(--c9); }
.pm-await { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 7px; text-align: center; padding: 30px 14px; border: 1px dashed var(--hairline); border-radius: var(--radius-md); margin-top: 6px; }
.pm-await__title { font-family: var(--font-sans); font-weight: 600; font-size: 13px; color: var(--ink-2); }
.pm-await__sub { font-family: var(--font-mono); font-size: 10.5px; color: var(--ink-3); line-height: 1.5; max-width: 240px; }
.kpi-card.is-await .kpi-card__value { color: var(--ink-3); }        /* awaiting strip */
.kpi-card.is-filtered .kpi-card__clock { color: var(--pink-deep); } /* filtered-state signal */
@keyframes atlasFade { from { opacity: .35; transform: translateY(5px); } to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) { .pm-lane { animation: none; } }

/* ============================================================
   MOBILE COMPANION (spec v1 canon · 2026-06-18)
   Pattern canonizzati dal prototipo (collaudo iPhone, sprint S1-S8 +
   rework spec v1). Sorgente vivente: brand-mockups/atlas-mobile-spec-v1.html
   (mappa IA: atlas-mobile-IA-tree.html) · regole: RULES.md §"Mobile
   companion" · spec implementazione: ATLAS-MOBILE-HANDOFF.md · pagina
   book: mobile.html. Baseline Wave C archiviata in
   brand-mockups/_archive/mobile-superseded/atlas-mobile-wavec.html.
   Breakpoint: i token --bp-* sono la FONTE DOCUMENTALE; le @media
   usano i valori allineati (CSS non valuta var() dentro @media):
     phone < 640px · tablet-portrait < 1024px · tablet-lg < 1366px
   e in Python/Dash: from brand_tokens import BP_PHONE, BP_TABLET.
   ============================================================ */

/* --- .m-tabbar — tab-bar flottante GLASS (5 slot: Today Search Inbox
   Scan Account). Il vetro è SOLO chrome: il contenuto resta carta
   (Rule 1). Shrink allo scroll giù via .is-compact (pattern IG). --- */
.m-tabbar { position: absolute; left: 12px; right: 12px; bottom: 10px; height: 58px; border-radius: 29px; border: 1px solid transparent; display: flex; z-index: 55; overflow: hidden;
  background: linear-gradient(var(--glass-bg), var(--glass-bg-deep)) padding-box,
              linear-gradient(165deg, var(--glass-edge-hi), rgba(255,255,255,0.25) 40%, var(--glass-edge-lo)) border-box;
  -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
  backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
  box-shadow: 0 12px 34px rgba(10,10,15,0.13), 0 2px 6px rgba(10,10,15,0.06), inset 0 1px 0 rgba(255,255,255,0.65);
  transition: left .28s ease, right .28s ease, height .28s ease, border-radius .28s ease; }
.m-tabbar::before { content: ""; position: absolute; inset: 0; border-radius: inherit; pointer-events: none;
  background: radial-gradient(120% 90% at 18% -25%, rgba(255,255,255,0.55), rgba(255,255,255,0) 55%); } /* speculare */
.m-tabbar.is-compact { height: 46px; left: 36px; right: 36px; border-radius: 23px; }
.m-tab { flex: 1; min-width: 44px; display: flex; align-items: center; justify-content: center; color: var(--ink-3); cursor: pointer; }
.m-tab.is-active { color: var(--pink-deep); }
.m-tab svg { width: 24px; height: 24px; transition: transform .16s cubic-bezier(.34,1.56,.64,1); }
.m-tab:active svg { transform: scale(.82); }
.m-tabbar.is-compact .m-tab svg { width: 21px; height: 21px; }

/* --- .record-card — la riga-griglia come card touch (sotto --bp-phone
   ogni .data-table/.atlas-grid densa diventa uno stack di queste; mai
   scroll orizzontale come unica via). Eredita pill/sku-code/sentiment
   dal canon tabella; --open = edge ambra book aperto (OPEN treatment). --- */
.record-card { position: relative; border: 1px solid var(--hairline); border-radius: var(--radius-lg); background: var(--paper); padding: 11px 12px; display: flex; flex-direction: column; gap: 6px; cursor: pointer; min-height: 60px; overflow: hidden; transition: transform .18s ease, background .15s ease; }
.record-card:active { background: var(--pink-tint); transform: scale(.984); }
.record-card--open::before { content: ""; position: absolute; left: -1px; top: 10px; bottom: 10px; width: 3px; border-radius: 2px; background: var(--open-edge); }
.record-card__top { display: flex; align-items: center; gap: 8px; }
.record-card__name { font-family: var(--font-sans); font-weight: 600; font-size: 13.5px; color: var(--ink); flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.record-card__kv { display: flex; gap: 16px; align-items: flex-end; }
.record-card__kv .k { font-family: var(--font-mono-heavy); font-weight: 700; font-size: 8.5px; letter-spacing: 0.14em; text-transform: uppercase; color: var(--ink-3); display: block; margin-bottom: 1px; }
.record-card__kv .v { font-family: var(--font-sans); font-weight: 600; font-size: 13px; color: var(--ink); font-variant-numeric: tabular-nums; }
.record-card__kv .v.is-open { color: var(--warning-deep); }
.record-card__kv .v.is-up { color: var(--success-deep); }
.record-card__kv .v.is-down { color: var(--cherry-deep); }

/* --- .novelty-tag — l'etichetta di stato del novelty engine (Today
   brief + Inbox): NEW / WORSE / RESOLVED. Il tono segue lo STATO del
   segnale, non il sentiment del dato. --- */
.novelty-tag { display: inline-flex; font-family: var(--font-mono-heavy); font-weight: 700; font-size: 8.5px; letter-spacing: 0.12em; text-transform: uppercase; border-radius: 4px; padding: 2px 6px; }
.novelty-tag--new      { color: var(--cherry-deep);  background: var(--pink-tint); }
.novelty-tag--worse    { color: var(--warning-deep); background: var(--cream); }
.novelty-tag--resolved { color: var(--success-deep); background: var(--success-tint); }

/* --- .m-nav — barra di navigazione iOS large-title. Collassata (height 0) in
   cima: nessuno spazio vuoto, il titolo grande vive IN PAGINA. Si RIVELA allo
   scroll come bar sticky compatta (.is-open) con hairline (.is-scrolled).
   Identità combo: Today = wordmark `atlas` centrato, NIENTE ✦ a destra; ogni
   altra vista = titolo centrato + ✦ mark a destra. In drill il back è inline
   accanto al titolo grande (.m-iback, scrolla via); allo scroll subentra il
   back dello slot della .m-nav. Chrome carta (Rule 1) — MAI glass (il glass è
   SOLO la tab-bar, R-mob.2). Toggle .is-open/.is-scrolled = scroll listener
   (Dash: assets/m_nav.js). --- */
.m-nav { position: relative; flex: 0 0 auto; z-index: 6; display: flex; align-items: center; height: 0; overflow: hidden; padding: 0 8px; background: var(--paper); border-bottom: 1px solid transparent; transition: height var(--motion-pane) var(--motion-ease-standard), border-color var(--motion-base) ease; }
.m-nav.is-open { height: 46px; }
.m-nav.is-scrolled { border-bottom-color: var(--hairline); }
.m-nav__slot { flex: 0 0 44px; display: flex; align-items: center; }
.m-nav__slot--right { justify-content: flex-end; }
.m-nav__back { width: 40px; height: 40px; display: inline-flex; align-items: center; justify-content: center; color: var(--ink-2); background: none; border: none; cursor: pointer; }
.m-nav__center { flex: 1; min-width: 0; display: flex; align-items: center; justify-content: center; opacity: 0; transition: opacity var(--motion-slow) ease; }
.m-nav.is-scrolled .m-nav__center { opacity: 1; }
.m-nav__title { font-family: var(--font-sans); font-weight: 600; font-size: 15px; color: var(--ink); max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.m-nav__lockup { display: inline-flex; align-items: center; gap: 6px; }
.m-nav__word { font-family: var(--font-wordmark); font-weight: 400; font-size: 18px; color: var(--pink-deep); line-height: 1; }
.m-nav__mark svg { width: 22px; height: 22px; display: block; }
/* Back INLINE col titolo grande in pagina (drill): nessuna riga riservata, sta
   accanto al titolo e scrolla via con esso. */
.m-iback { width: 40px; height: 40px; margin-left: -8px; flex: 0 0 40px; display: inline-flex; align-items: center; justify-content: center; color: var(--ink-2); background: none; border: none; cursor: pointer; }

/* --- .m-segmented — il segmented MOBILE: stesso look del canon .segmented
   (track --surface, pill attiva carta) ma con THUMB SCORREVOLE animato — la
   pill bianca TRASLA sotto il segmento attivo invece di accendersi sul posto
   (spring = --motion-spring, overshoot morbido). Scroll-x se i segmenti sforano.
   Il thumb è posizionato misurando left/width del bottone attivo e scrivendoli
   inline (Dash: assets/m_nav.js · mockup: useLayoutEffect). --sm = taglia
   secondaria (range sotto un grafico). --- */
.m-segmented { position: relative; display: inline-flex; max-width: 100%; gap: 2px; padding: 3px; border-radius: var(--radius-md); background: var(--surface); overflow-x: auto; -webkit-overflow-scrolling: touch; scrollbar-width: none; }
.m-segmented::-webkit-scrollbar { display: none; }
.m-segmented__thumb { position: absolute; top: 3px; bottom: 3px; left: 0; z-index: 0; border-radius: var(--radius-sm); background: var(--paper); box-shadow: 0 1px 2px rgba(10,10,15,0.08); transition: transform var(--motion-pane) var(--motion-spring), width var(--motion-pane) var(--motion-spring); }
.m-segmented button { position: relative; z-index: 1; min-height: 36px; padding: 7px 13px; border: 0; background: transparent; border-radius: var(--radius-sm); cursor: pointer; white-space: nowrap; font-family: var(--font-sans); font-weight: 500; font-size: 12.5px; color: var(--ink-2); transition: color var(--motion-slow) ease; }
.m-segmented button.is-on { color: var(--ink); }
.m-segmented--sm button { min-height: 28px; padding: 4px 10px; font-size: 11.5px; }

/* --- swipe-to-action (.m-swipe) — la riga del brief Today che si TRASCINA
   lateralmente per un'azione: swipe → = Read (la card resta muted fino a
   domani), ← = Inbox (archivia, con Undo via toast), tap = apre il record.
   Touch events + touch-action:pan-y + lock d'asse (coerente con la gesture canon
   R-mob.7; i pointer event muoiono in pointercancel su iOS). __bg = layer azione
   svelato dal drag (tinta soft → piena oltre soglia con .is-armed), __fg = la
   card che trasla. Cap 10 card. --- */
.m-swipe { position: relative; overflow: hidden; }
.m-swipe__bg { position: absolute; inset: 0; display: flex; align-items: center; justify-content: space-between; padding: 0 18px; }
.m-swipe__action { display: inline-flex; align-items: center; gap: 7px; font-family: var(--font-mono-heavy); font-weight: 700; font-size: 11px; letter-spacing: 0.07em; text-transform: uppercase; opacity: 0; transition: opacity var(--motion-fast) ease; }
.m-swipe__action--read { color: var(--success-deep); }
.m-swipe__action--inbox { color: var(--cherry-deep); }
.m-swipe__bg[data-dir="read"] { background: var(--success-tint); }
.m-swipe__bg[data-dir="read"] .m-swipe__action--read { opacity: 1; }
.m-swipe__bg[data-dir="inbox"] { background: var(--cherry-tint); }
.m-swipe__bg[data-dir="inbox"] .m-swipe__action--inbox { opacity: 1; }
.m-swipe__bg.is-armed[data-dir="read"] { background: var(--success); }
.m-swipe__bg.is-armed[data-dir="read"] .m-swipe__action--read { color: var(--paper); }
.m-swipe__bg.is-armed[data-dir="inbox"] { background: var(--cherry); }
.m-swipe__bg.is-armed[data-dir="inbox"] .m-swipe__action--inbox { color: var(--paper); }
.m-swipe__fg { position: relative; background: var(--paper); touch-action: pan-y; -webkit-user-select: none; user-select: none; cursor: grab; }

/* --- .m-notif — la card-segnale del brief Today (dentro .m-swipe__fg): punto
   di severità + .novelty-tag (canon) + soggetto + sorgente + una frase con la
   cifra in tono + chevron. .is-read = stato muted post-swipe (resta fino a
   domani: dot nascosto, testo e tag attenuati). --- */
.m-notif { display: flex; align-items: flex-start; gap: 11px; padding: 14px 16px; background: var(--paper); }
.m-notif__dot { width: 9px; height: 9px; flex: 0 0 9px; margin-top: 5px; border-radius: 50%; }
.m-notif__dot--down { background: var(--cherry); }
.m-notif__dot--warn { background: var(--warning); }
.m-notif__dot--up { background: var(--success); }
.m-notif__body { flex: 1; min-width: 0; }
.m-notif__l1 { display: flex; align-items: center; gap: 8px; margin-bottom: 3px; }
.m-notif__subj { flex: 0 1 auto; min-width: 0; font-family: var(--font-sans); font-weight: 600; font-size: 13.5px; color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.m-notif__src { margin-left: auto; flex: 0 0 auto; font-family: var(--font-mono); font-size: 9px; color: var(--ink-3); }
.m-notif__l2 { font-family: var(--font-sans); font-size: 12.5px; line-height: 1.4; color: var(--ink-2); }
.m-notif__l2 b { font-variant-numeric: tabular-nums; }
.m-notif__l2 b.is-down { color: var(--cherry-deep); }
.m-notif__l2 b.is-warn { color: var(--warning-deep); }
.m-notif__l2 b.is-up { color: var(--success-deep); }
.m-notif__chev { align-self: center; flex: 0 0 auto; font-family: var(--font-sans); font-size: 18px; color: var(--ink-3); }
.m-swipe__fg.is-read .m-notif__dot { visibility: hidden; }
.m-swipe__fg.is-read .m-notif__subj { color: var(--ink-3); font-weight: 500; }
.m-swipe__fg.is-read .m-notif__l2,
.m-swipe__fg.is-read .m-notif__l2 b { color: var(--ink-3); }
.m-swipe__fg.is-read .novelty-tag { opacity: 0.5; }

/* --- inline-expand card — una list/competitor card che si ARRICCHISCE
   IN-PAGINA (accordion, una aperta per volta) invece di aprire il `.drawer`.
   Reveal GPU-cheap: `grid-template-rows: 0fr → 1fr` sul `.gf-card__more` con
   `overflow:hidden` sul clip → altezza-auto fluida. Il `.drawer` resta per il
   detail dei NOSTRI prodotti (SKU/Client/Order). `.gf-card-actions` ospita
   prezzo + `.gf-card__add` (＋/✓ NPD) nel footer scheda, MAI overlay.
   Mockup canon: brand-mockups/brand-categories-gap-finder-card-expand.html.
   Toggle/keyboard = assets/gap_card_expand.js + keyboard_click.js (RULES.md). --- */
.gf-card-actions { display: inline-flex; align-items: center; gap: 10px; }
.gf-card__add { height: 24px; padding: 0 11px; border-radius: 999px; border: 1px solid var(--pink-light); background: var(--paper); color: var(--pink-deep); font-family: var(--font-mono); font-size: 10px; letter-spacing: 0.04em; cursor: pointer; display: inline-flex; align-items: center; gap: 5px; white-space: nowrap; transition: background var(--motion-base) var(--motion-ease-out), border-color var(--motion-base) var(--motion-ease-out), transform var(--motion-base) var(--motion-ease-out); }
.gf-card__add:hover { background: var(--pink-tint); }
.gf-card__add:active { transform: scale(0.94); }
.gf-card__add:focus-visible { outline: 2px solid var(--pink); outline-offset: 2px; }
.gf-card__add.is-in { background: var(--pink); border-color: var(--pink); color: var(--paper); }
/* Reveal: grid-rows 0fr↔1fr dà l'altezza-auto (end-state affidabile ovunque).
   La TRANSITION è solo su opacity: animare `grid-template-rows` fra fr non è
   affidabile (l'fr interpola contro 0 spazio libero e resta bloccato a 0 in
   alcuni build Chromium) → l'altezza fa snap e l'opacity fa fade. Pulito e
   robusto cross-browser. */
.gf-card__more { display: grid; grid-template-rows: 0fr; opacity: 0; transition: opacity var(--motion-medium) var(--motion-ease-out); }
.gf-card__more > .gf-card__more-clip { overflow: hidden; min-height: 0; }
.gf-card.is-expanded .gf-card__more { grid-template-rows: 1fr; opacity: 1; }
.gf-card__more-inner { padding-top: 11px; margin-top: 9px; border-top: 1px solid var(--hairline); display: flex; flex-direction: column; gap: 11px; }
.gf-card.is-expanded { border-color: var(--pink); box-shadow: 0 0 0 1px var(--pink), var(--shadow-card-hover); }

@media (prefers-reduced-motion: reduce) {
  .m-tabbar, .m-tab svg, .record-card { transition: none; }
  .record-card:active { transform: none; }
  .gf-card__more, .gf-card__add { transition: none; }
  .m-nav, .m-nav__center, .m-segmented__thumb, .m-segmented button, .m-swipe__action { transition: none; }
}

/* ===== AUTH SURFACES (login · magic-link · check-email) =====
   Brand-surface screens (eccezione Rule 1). Desktop = editorial su --paper;
   phone (≤ --bp-phone) = glass dai token --glass-*. Primary = .btn-primary pink
   (mai cherry, Rule 2). Mobile legibilità: title 25px, sub 15.5px, input 16px
   (no iOS focus-zoom), target ≥52px. Wordmark Caprasimo SOLO qui (logotype). */
.auth { min-height: 100dvh; display: flex; flex-direction: column; background: var(--paper); position: relative; }
.auth__scroll { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 32px; text-align: center; }
.auth__stack { width: 416px; max-width: 100%; position: relative; }
.auth__stack > * + * { margin-top: 16px; }

/* desktop ≥641px: blocco delineato KEYLINE su bianco (border hairline + ombra
   leggera + keyline pink 3px in alto). Niente fondo crema/pink — l'accento è la
   keyline, non un fill. Mobile resta glass (vedi @media max-width:640px). */
@media (min-width: 641px) {
  .auth__stack { background: var(--paper); border: 1px solid var(--hairline);
      border-radius: var(--radius-xl); box-shadow: var(--shadow-card-hover);
      padding: 40px; overflow: hidden; }
  .auth__stack::before { content: ""; position: absolute; top: 0; left: 0; right: 0;
      height: 3px; background: var(--pink); }   /* keyline = accento, rispetta i corner via overflow:hidden */
}

.auth__wm { display: inline-flex; align-items: center; gap: 10px; font-family: var(--font-wordmark); color: var(--pink); line-height: 1; font-size: 40px; }
.auth__wm svg { width: 26px; height: 26px; color: var(--cherry); }   /* le 2 sparkle = cherry */

.auth__mark { width: 72px; height: 72px; border-radius: 50%; display: grid; place-items: center; margin: 0 auto 22px;
  background: color-mix(in srgb, var(--pink) 12%, white); color: var(--pink-text); }
.auth__mark svg { width: 32px; height: 32px; fill: none; stroke: currentColor; stroke-width: 1.9; stroke-linecap: round; stroke-linejoin: round; }
.auth__mark--warn { background: color-mix(in srgb, var(--warning) 16%, white); color: var(--warning-deep); }   /* link scaduto/usato */

.auth__eyebrow { font-family: var(--font-mono-heavy); font-size: 10.5px; font-weight: 700; letter-spacing: 0.28em; text-transform: uppercase; color: var(--ink-3-text); }
.auth__title { font-family: var(--font-sans); font-weight: 600; font-size: 26px; letter-spacing: -0.02em; color: var(--ink); margin: 0; }
.auth__accent { font-family: var(--font-display); font-style: italic; font-weight: 500; color: var(--pink-text); }
.auth__sub { font-size: 14.5px; line-height: 1.6; color: var(--ink-2); margin: 0; }
.auth__sub b { color: var(--ink); font-weight: 600; }
.auth__meta { font-family: var(--font-mono); font-size: 12px; color: var(--ink-3-text); margin: 0; }
.auth__foot { font-family: var(--font-mono); font-size: 11px; color: var(--ink-3-text); }
.auth__form { width: 100%; display: flex; flex-direction: column; gap: 14px; text-align: left; }
.auth__actions { display: flex; flex-direction: column; gap: 12px; width: 100%; }
.auth__link { font-family: var(--font-sans); font-size: 13px; font-weight: 600; color: var(--pink-text); background: none; border: 0; cursor: pointer; padding: 6px; text-decoration: none; }
.auth__link:hover { color: var(--pink-deep); text-decoration: underline; }
.auth__link:disabled { color: var(--ink-3); cursor: not-allowed; text-decoration: none; }
.auth__btn-block { width: 100%; }   /* su .btn .btn-primary .btn-lg */

/* phone ≤ --bp-phone (640px): glass treatment + legibilità */
@media (max-width: 640px) {
  .auth { background:
      radial-gradient(120% 90% at 12% 0%, color-mix(in srgb, var(--pink-light) 55%, transparent), transparent 60%),
      radial-gradient(110% 80% at 100% 100%, color-mix(in srgb, var(--cherry) 22%, transparent), transparent 55%),
      var(--surface); }
  .auth__scroll { padding: 26px 22px; }
  .auth__stack { width: 100%; border-radius: var(--radius-xl);
      background: var(--glass-bg);
      backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
      -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
      border: 1px solid var(--glass-edge-hi);
      box-shadow: 0 18px 50px var(--glass-edge-lo), inset 0 1px 0 var(--glass-edge-hi);
      padding: 30px 24px; }
  .auth__wm { font-size: 38px; } .auth__wm svg { width: 25px; height: 25px; }
  .auth__title { font-size: 25px; }
  .auth__sub { font-size: 15.5px; }
  .auth__mark { background: color-mix(in srgb, white 55%, transparent); border: 1px solid var(--glass-edge-hi); }
  .auth .input { font-size: 16px; padding: 14px; background: color-mix(in srgb, white 70%, transparent); }
  .auth .btn-lg { padding: 16px 22px; font-size: 16px; }
}

/* ============ SELL-OUT LENS (Fase E2 · 2026-07-05) ============
   Censimento W2-5: le lenti sell-out (C1 Client · C2 Top Items · C4 Sales
   Overview · D2 Compare · D1b Assortment · D3b Innovations) avevano forkato
   page-local per lente lo stesso trio di componenti (banner coverage,
   toggle switch-lente, legenda scala colore) — `.pm-ti-cov`/`.pm-inn-cov`/
   `.pm-cov`, `.pm-*-sotoggle`/`.pm-*-swlbl`, `.pm-leg`/`.pm-st-legend`.
   Promossi a canon (single source, comporre da qui — non ridefinire in app).

   - `.covbar`         — banner "base ristretta ai clienti coperti" (info-edge
                         a sinistra). `.covbar .k` = eyebrow mono; `.covbar b`
                         = numeri in evidenza.
   - `.lens-toggle`    — riga inline "label + switch" della lente sell-out;
                         `.lens-toggle__label` = etichetta mono-heavy pink;
                         `.lens-toggle--static` = variante non-cliccabile.
   - `.scale-legend`   — legenda scala-colore sotto una matrice/heatmap (la
                         tint È la metrica). `.scale-legend i` = swatch colore.
     Gli helper testo quiet/incoming della legenda (`.pm-quiet`/`.pm-inc`)
     restano utility app-side (override legittimo host=app).
   ============================================================ */
.covbar {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 10px 14px; border: 1px solid var(--hairline);
  border-left: 3px solid var(--info);
  background: color-mix(in srgb, var(--info) 5%, white);
  border-radius: var(--radius-md); margin-bottom: 12px;
  font-family: var(--font-sans); font-size: 12.5px; color: var(--ink-2);
}
.covbar b { color: var(--ink); font-weight: 600; }
.covbar .k {
  font-family: var(--font-mono-heavy); font-size: 9.5px; font-weight: 700;
  letter-spacing: 0.14em; text-transform: uppercase; color: var(--info-deep);
  flex-shrink: 0;
}

.lens-toggle {
  display: inline-flex; align-items: center; gap: 8px; cursor: pointer;
  flex: 0 0 auto;
}
.lens-toggle--static { cursor: default; }
.lens-toggle__label {
  font-family: var(--font-mono-heavy); font-weight: 700; font-size: 11px;
  letter-spacing: 0.14em; text-transform: uppercase; color: var(--pink-deep);
}

.scale-legend {
  display: flex; gap: 16px; flex-wrap: wrap; margin-top: 10px;
  font-family: var(--font-mono); font-size: 10.5px; color: var(--ink-2);
}
.scale-legend i {
  display: inline-block; width: 12px; height: 10px; border-radius: 2px;
  vertical-align: -1px;
}
