/* ============================================================================
   runo — Vaadin theme
   ----------------------------------------------------------------------------
   Drop this folder into your Vaadin project at:
     src/main/frontend/themes/runo/

   Then activate it from your Application class:
     @Theme(value = "runo")
     class RunoApplication : AppShellConfigurator

   What this file does:
     1. Imports the runo design tokens (tokens.css)
     2. Maps them onto Lumo CSS custom properties so every Vaadin
        component (Grid, Button, Dialog, MenuBar…) inherits the look
        without any per-component overrides
     3. Adds a thin component layer for things Lumo does poorly
        (sidebar nav, eyebrow labels, serif page titles)

   Theme switching:
     - Light/dark:  document.documentElement.dataset.theme   = "light" | "dark"
     - Accent:      document.documentElement.dataset.accent  = "indigo" | "ochre" | "moss" | "plum"
     - Density:     document.documentElement.dataset.density = "compact" | "medium" | "spacious"
     From Vaadin/Kotlin:
       UI.getCurrent().element.executeJs("document.documentElement.dataset.theme = $0", "dark")
   ============================================================================ */

/* Web fonts are loaded as parallel <link>s in the page <head> (see VaadinConfig.configurePage).
   A CSS @import here serialized + render-blocked them: download this theme CSS → discover the
   @import → fetch Google's CSS → fetch the woff2, all before first paint. The head <link> is
   discovered during initial HTML parse and fetched in parallel with the app bundle instead. */
@import url("./tokens.css");

/* ============================================================================
   1. Map runo tokens → Lumo properties
   ============================================================================ */
html {
  /* base — Lumo */
  --lumo-font-family: var(--font-sans);
  --lumo-font-size:   14px;
  --lumo-line-height-m: 1.5;

  /* size scale */
  --lumo-size-xs: 24px;
  --lumo-size-s:  28px;
  --lumo-size-m:  32px;
  --lumo-size-l:  40px;
  --lumo-size-xl: 48px;

  /* spacing */
  --lumo-space-xs: var(--s-1);
  --lumo-space-s:  var(--s-2);
  --lumo-space-m:  var(--s-4);
  --lumo-space-l:  var(--s-6);
  --lumo-space-xl: var(--s-8);

  /* radii */
  --lumo-border-radius-s: var(--r-1);
  --lumo-border-radius-m: var(--r-2);
  --lumo-border-radius-l: var(--r-3);

  /* color: text, the base colour and the contrast scale are mapped in 1a (light)
     and 1b (dark) below, not here: lumo-aura-shim.css sets them on :root, which
     out-specifies this html selector, so a mapping here never applied. */

  /* color: surface */
  --lumo-tint-5pct:         color-mix(in oklch, var(--surface) 95%, var(--ink));
  --lumo-tint-10pct:        color-mix(in oklch, var(--surface) 90%, var(--ink));
  --lumo-tint-20pct:        color-mix(in oklch, var(--surface) 80%, var(--ink));
  --lumo-shade-5pct:        var(--bg-elev);
  --lumo-shade-10pct:       var(--bg-sunk);
  --lumo-shade-20pct:       var(--line-soft);
  --lumo-shade-30pct:       var(--line);
  --lumo-shade-40pct:       var(--line-strong);
  --lumo-shade-50pct:       var(--ink-mute);

  /* color: primary (accent)
     !important is load-bearing: lumo-aura-shim.css redefines these on :root
     (pointing at Vaadin's pink --vaadin-user-color-0). :root out-specifies our
     html selector, so without !important the shim's pink wins for every
     primary-derived control — tabs, checkboxes, grid selection, active nav,
     and the default button label (Lumo colours it with --lumo-primary-text-color). */
  --lumo-primary-text-color:    var(--accent-ink) !important;
  --lumo-primary-color:         var(--accent) !important;
  --lumo-primary-color-50pct:   color-mix(in oklch, var(--accent) 50%, transparent) !important;
  --lumo-primary-color-10pct:   var(--accent-soft) !important;
  --lumo-primary-contrast-color: white !important;

  /* color: signals */
  --lumo-error-text-color:      var(--signal-red);
  --lumo-error-color:            var(--signal-red);
  --lumo-error-color-50pct:      color-mix(in oklch, var(--signal-red) 50%, transparent);
  --lumo-error-color-10pct:      color-mix(in oklch, var(--signal-red) 14%, transparent);

  --lumo-success-text-color:    var(--signal-green);
  --lumo-success-color:          var(--signal-green);
  --lumo-success-color-50pct:    color-mix(in oklch, var(--signal-green) 50%, transparent);
  --lumo-success-color-10pct:    color-mix(in oklch, var(--signal-green) 14%, transparent);

  --lumo-warning-text-color:    var(--signal-amber);
  --lumo-warning-color:          var(--signal-amber);
  --lumo-warning-color-10pct:    color-mix(in oklch, var(--signal-amber) 14%, transparent);

  /* shadows */
  --lumo-box-shadow-xs: var(--shadow-1);
  --lumo-box-shadow-s:  var(--shadow-1);
  --lumo-box-shadow-m:  var(--shadow-2);
  --lumo-box-shadow-l:  var(--shadow-3);
  --lumo-box-shadow-xl: var(--shadow-3);

  /* link / focus ring */
  --lumo-clickable-cursor: pointer;
}

/* ============================================================================
   1a. Light-mode Lumo text, base and contrast — beat the Aura :root shim
   ----------------------------------------------------------------------------
   lumo-aura-shim.css sets these on :root to var(--vaadin-*), and Lumo 25 points
   the same --vaadin-* back at them (vaadin-lumo-styles props/color.css:
   --vaadin-text-color: var(--lumo-body-text-color), and so on). The two halves
   form a cycle, and a cycle computes to nothing: in light mode the text colours
   and --lumo-contrast came out transparent and --lumo-base-color empty, so every
   h1-h6 without a colour of its own was invisible (12.10.2026: the Claims H2 on
   /complaints, the partner pages' headings). :root out-specifies the html block
   above, so the mapping that used to sit there never applied either.

   Fixed the way the primary colours above and block 1b below are: the runo
   tokens with !important. A fallback inside the shim would not do it: the
   reference itself closes the cycle, and --vaadin-text-color is registered as a
   <color> (component-base style-props.js), so it always has a value and the
   fallback never fires. Scoped to every page but [data-theme="dark"], so dark
   mode keeps exactly what 1b gives it. Keep 1a and 1b in step if you edit them.

   20pct and 50pct are what Lumo paints the unchecked checkbox and radio box
   and the grid tree toggle with. Mapped to --line-soft / --ink-mute they would
   all but erase those controls on --surface, so the controls take their own
   --line-control edge and --ink-3 toggle in section 2 ("Controls read as
   controls") instead of leaning on the scale.
   ============================================================================ */
html:not([data-theme="dark"]) {
  --lumo-header-text-color:    var(--ink) !important;
  --lumo-body-text-color:      var(--ink) !important;
  --lumo-secondary-text-color: var(--ink-3) !important;
  --lumo-tertiary-text-color:  var(--ink-4) !important;
  --lumo-disabled-text-color:  var(--ink-mute) !important;

  --lumo-base-color: var(--surface) !important;

  --lumo-contrast:       var(--ink) !important;
  --lumo-contrast-5pct:  var(--bg-elev) !important;
  --lumo-contrast-10pct: var(--bg-sunk) !important;
  --lumo-contrast-20pct: var(--line-soft) !important;
  --lumo-contrast-30pct: var(--line) !important;
  --lumo-contrast-40pct: var(--line-strong) !important;
  --lumo-contrast-50pct: var(--ink-mute) !important;
  --lumo-contrast-60pct: var(--ink-4) !important;
  --lumo-contrast-70pct: var(--ink-3) !important;
  --lumo-contrast-80pct: var(--ink-2) !important;
  --lumo-contrast-90pct: var(--ink) !important;
}

/* ============================================================================
   1b. Dark-mode Lumo override — beat the Aura :root shim
   ----------------------------------------------------------------------------
   Vaadin's lumo-aura-shim redefines a subset of Lumo colour vars on :root with
   fixed light-mode (black-based) values. `:root` out-specifies the `html`
   mapping above (the same reason --lumo-primary-* up there carry !important), so
   in dark mode those vars stay BLACK — headings + tertiary text turn black on a
   dark surface (invisible), and contrast-derived text/borders vanish.

   We can't !important the html block without nudging light mode, so re-assert the
   runo tokens here, scoped to [data-theme="dark"] + !important. This touches
   ONLY dark mode — light mode has its own block, 1a — and makes our tokens win
   deterministically regardless of shim load order. The text, base and contrast
   lines are the same as 1a's (the base colour and --lumo-contrast sit in the
   same cycle, and without them dark grids painted no background); the shade
   lines mirror the html block. Keep them in sync if you edit them.
   (--lumo-tint-* is intentionally left to Lumo: its white-based tint already
   reads correctly as a light overlay on the dark surface.)
   ============================================================================ */
[data-theme="dark"] {
  --lumo-header-text-color:    var(--ink) !important;
  --lumo-body-text-color:      var(--ink) !important;
  --lumo-secondary-text-color: var(--ink-3) !important;
  --lumo-tertiary-text-color:  var(--ink-4) !important;
  --lumo-disabled-text-color:  var(--ink-mute) !important;

  --lumo-base-color: var(--surface) !important;

  --lumo-contrast:       var(--ink) !important;
  --lumo-contrast-5pct:  var(--bg-elev) !important;
  --lumo-contrast-10pct: var(--bg-sunk) !important;
  --lumo-contrast-20pct: var(--line-soft) !important;
  --lumo-contrast-30pct: var(--line) !important;
  --lumo-contrast-40pct: var(--line-strong) !important;
  --lumo-contrast-50pct: var(--ink-mute) !important;
  --lumo-contrast-60pct: var(--ink-4) !important;
  --lumo-contrast-70pct: var(--ink-3) !important;
  --lumo-contrast-80pct: var(--ink-2) !important;
  --lumo-contrast-90pct: var(--ink) !important;

  --lumo-shade-5pct:  var(--bg-elev) !important;
  --lumo-shade-10pct: var(--bg-sunk) !important;
  --lumo-shade-20pct: var(--line-soft) !important;
  --lumo-shade-30pct: var(--line) !important;
  --lumo-shade-40pct: var(--line-strong) !important;
  --lumo-shade-50pct: var(--ink-mute) !important;
}

/* ============================================================================
   2. Body / page chrome
   ============================================================================ */
body {
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font-sans);
  font-feature-settings: "ss01", "cv11";
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

::selection { background: var(--accent-soft); color: var(--accent-ink); }

/* AppLayout: a navbar and the page. The drawer is gone (11.10.2026) - the Map of Runo is the menu -
   so no width is held back for it and the page starts at the left edge. */
vaadin-app-layout {
  --vaadin-app-layout-drawer-width: 0px;
}
vaadin-app-layout::part(drawer) {
  display: none;
}
vaadin-app-layout::part(navbar) {
  background: var(--bg);
  border-bottom: 1px solid var(--line-soft);
  height: 52px;
}

/* ============================================================================
   3. Components
   ============================================================================ */

/* Buttons — match the runo system. Default (no-theme) = surface fill + ink
   text; without this Lumo paints the label with --lumo-primary-text-color (now
   accent), so default buttons read indigo. One primary style only — ink-black;
   indigo is never a button colour. */
vaadin-button {
  font-weight: 500;
  letter-spacing: 0;
}
/* Any button that isn't a colour variant (incl. size-only themes like `small`)
   gets ink text. Lumo otherwise paints the label with --lumo-primary-text-color
   (now accent) — so `theme="small"` buttons read indigo without this. */
vaadin-button:not([theme~="primary"]):not([theme~="tertiary"]):not([theme~="contrast"]):not([theme~="error"]):not([theme~="success"]):not([disabled]) {
  color: var(--ink);
}
/* Disabled must look disabled. The light-DOM ink rule above used to beat Lumo's
   :host([disabled]) colour, so a disabled button looked exactly like a live one
   and silently ignored the click (29.9.2026). */
vaadin-button[disabled]:not([theme~="primary"]) { color: var(--ink-mute); }
vaadin-button[theme~="primary"][disabled] { background: var(--bg-sunk); color: var(--ink-3); border: 1px solid var(--line); }
vaadin-button[theme~="primary"] {
  background: var(--ink);
  color: var(--bg);
}
vaadin-button[theme~="primary"]:hover {
  background: oklch(from var(--ink) calc(l + 0.05) c h);
}
vaadin-button[theme~="tertiary"] {
  color: var(--ink-2);
}
vaadin-button[theme~="tertiary"]:hover {
  background: var(--bg-elev);
  color: var(--ink);
}

/* Grid — tighter, with hairline rows. Compact vertical padding (--s-1 = 4px)
   keeps admin tables dense so more rows fit per screen; horizontal stays --s-3. */
vaadin-grid {
  --vaadin-grid-cell-padding: var(--s-1) var(--s-3);
  font-size: 13px;
}
vaadin-grid::part(header-cell) {
  background: var(--bg-sunk);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-3);
}
vaadin-grid::part(row):hover {
  background: var(--bg-elev);
}

/* Due-date traffic-light cells (Tasks board, top grid).
   Driven by Grid#setPartNameGenerator on the Due column — a 4-step escalation:
     due-2d       (soft amber  — 2 days to go)
     due-1d       (deeper amber — 1 day to go)
     due-today    (soft red    — due today)
     due-overdue  (solid red, inverted text — past due)
   Token-based so the cells flip with the theme — the old hardcoded pastel hex
   (#FFE9CC/#FFB870/#FFC2C2) stayed bright in dark mode and the grid's light ink
   became unreadable on them. `!important` is needed because the row :hover rule
   above sets `background` on the row part and would otherwise win the cascade. */
vaadin-grid::part(cell due-2d)      { background: var(--signal-amber-soft) !important; }
vaadin-grid::part(cell due-1d)      { background: color-mix(in oklch, var(--signal-amber) 35%, var(--surface)) !important; }
vaadin-grid::part(cell due-today)   { background: var(--signal-red-soft) !important; }
vaadin-grid::part(cell due-overdue) { background: var(--signal-red) !important; color: var(--surface) !important; }

/* Sales WIP deep-link target — row matched by ?highlight=<id> on the route.
   Set by Grid#setPartNameGenerator at row level, so the "highlighted-row"
   part lands on every cell in the matched row. `!important` wins over the
   Lumo row-stripe and the row :hover rule above.

   Placed AFTER the due-* rules so a row that's BOTH highlighted AND overdue
   shows the accent tint rather than the red overdue background — the
   highlight is transient (one-shot deep-link from personal Tasks WIP, gone
   on next nav) while the overdue cue is recoverable from the visible date
   text. Prioritising the highlight keeps the deep-link target findable. */
vaadin-grid::part(cell highlighted-row) {
    background: var(--accent-soft) !important;
}

/* Cards (Vaadin 25 Card or div with class .card) */
vaadin-card,
.runo-card {
  background: var(--surface);
  border: 1px solid var(--line-soft);
  border-radius: var(--r-3);
  box-shadow: var(--shadow-1);
}

/* ----------------------------------------------------------------------------
   Overlays — dialog, popover, menu, date-picker, etc.

   Background note: dropping `lumoImports` from theme.json (Vaadin 25 fail-fast
   change) also dropped Lumo's default opaque background on every overlay-based
   web component. Without the rules below, dialogs and popovers render with a
   transparent overlay and text from the page underneath bleeds through.

   We set `--vaadin-overlay-background` (the Vaadin 25 contract for ALL overlay
   surfaces) plus an explicit `::part(overlay)` background for safety, plus a
   backdrop scrim to dim the page behind modal dialogs.
   -------------------------------------------------------------------------- */

html {
  /* --overlay-bg (NOT --surface) breaks the resolution cycle that made
     dropdowns/date-pickers render transparent inside .wip-paper-grid. */
  --vaadin-overlay-background: var(--overlay-bg);
  --vaadin-overlay-background-color: var(--overlay-bg);
  --vaadin-overlay-color: var(--ink);
  --vaadin-overlay-border-radius: var(--r-4);
  --vaadin-overlay-box-shadow: var(--shadow-3);
  --vaadin-overlay-border: 1px solid var(--line-soft);
  --vaadin-dialog-padding: var(--s-5);
  /* Aura primitives: the page-elevated container colour is what dialog +
     popover overlays inherit when no overlay-specific token is set. */
  --vaadin-background-container: var(--surface);
  --vaadin-background-container-strong: var(--bg-elev);
}

/* Overlay context re-assertion. A Select / ComboBox / picker overlay inherits
   custom properties from its host. When that host sits inside a
   .wip-paper-grid it carries the grid's COMPACT context, which the open
   overlay would inherit and render wrong:
     - --surface / --lumo-base-color are transparent (so the closed editor
       blends into the paper) -> the open panel + items would go see-through;
     - --lumo-size-m / --lumo-size-s are shrunk (so the closed editor fits the
       ~50px row) -> the open dropdown items would be cramped, overflow their
       box and overlap the rows behind.
   Re-assert the NORMAL context here so an open dropdown matches every other
   dropdown in the app: an opaque, root-locked surface and the app-wide
   --lumo-size-* values. Outside a paper grid these equal what the tokens
   already had — a no-op. The re-set cascades into the overlay parts, list-box
   and items; the closed editor, sitting outside the overlay, keeps the
   grid's compact context. */
vaadin-select-overlay,
vaadin-combo-box-overlay,
vaadin-date-picker-overlay,
vaadin-time-picker-overlay {
  --surface: var(--overlay-bg);
  --lumo-base-color: var(--overlay-bg);
  --lumo-size-m: 32px;
  --lumo-size-s: 28px;
}

/* Generic overlay base — covers menu / select / date-picker / time-picker /
   combo-box / context-menu / etc. !important wins the cascade against any
   internal Aura defaults that set their own `background` on ::part(overlay). */
vaadin-overlay::part(overlay),
vaadin-dialog-overlay::part(overlay),
vaadin-popover-overlay::part(overlay),
vaadin-menu-bar-overlay::part(overlay),
vaadin-context-menu-overlay::part(overlay),
vaadin-select-overlay::part(overlay),
vaadin-combo-box-overlay::part(overlay),
vaadin-date-picker-overlay::part(overlay),
vaadin-time-picker-overlay::part(overlay) {
  background: var(--overlay-bg) !important;
  color: var(--ink);
  border: 1px solid var(--line-soft);
}
/* Belt-and-braces: also force the host element opaque, in case the overlay
   draws its background on the host rather than the `::part(overlay)`
   pseudo-element. */
vaadin-overlay,
vaadin-dialog-overlay,
vaadin-popover-overlay,
vaadin-menu-bar-overlay,
vaadin-context-menu-overlay,
vaadin-select-overlay,
vaadin-combo-box-overlay,
vaadin-date-picker-overlay,
vaadin-time-picker-overlay {
  background: var(--overlay-bg);
  color: var(--ink);
}

/* Dialog — taller radius, deeper shadow */
vaadin-dialog-overlay::part(overlay) {
  border-radius: var(--r-4);
  box-shadow: var(--shadow-3);
}
vaadin-dialog-overlay::part(content),
vaadin-dialog-overlay::part(header),
vaadin-dialog-overlay::part(footer),
vaadin-dialog-overlay::part(header-content),
vaadin-dialog-overlay::part(footer-content) {
  background: var(--surface) !important;
  color: var(--ink);
}
/* Dialog flow-level descendants — when a Dialog's slotted content sits in
   an unstyled slot, the slot itself can render transparent. Force every
   direct child of the dialog overlay to inherit an opaque surface. */
vaadin-dialog-overlay > * {
  background: var(--surface);
}
/* Backdrop scrim — dims the page behind modal dialogs so the dialog reads
   as foreground. Modeless overlays (popover, menu) intentionally keep no
   backdrop. */
vaadin-dialog-overlay::part(backdrop) {
  background: color-mix(in oklch, var(--ink) 28%, transparent);
}

/* Popover — modeless, smaller radius + softer shadow */
vaadin-popover-overlay::part(overlay) {
  border-radius: var(--r-3);
  box-shadow: var(--shadow-2);
}
vaadin-popover-overlay::part(content) {
  background: var(--overlay-bg);
  color: var(--ink);
}

/* Select / combo-box / list dropdowns — the bulk rule above paints the
   ::part(overlay) wrapper, but the items live inside ::part(content) and
   the inner vaadin-select-list-box / vaadin-list-box. Without an opaque
   background on those, the dropdown items appear to bleed through the
   row text behind them. Mirror the popover treatment. !important wins
   against Lumo's internal `transparent` defaults. */
vaadin-select-overlay::part(content),
vaadin-combo-box-overlay::part(content),
vaadin-context-menu-overlay::part(content),
vaadin-menu-bar-overlay::part(content),
vaadin-select-list-box,
vaadin-list-box {
  background: var(--overlay-bg) !important;
  color: var(--ink);
}
/* Vaadin 25 does NOT create a vaadin-*-overlay wrapper for these panels
   (confirmed live), so the bare list-box / picker IS the visible surface and
   needs its own border/radius/shadow — otherwise it's a flat rectangle. The
   opaque fill is --overlay-bg (NOT --surface, which is transparent inside a
   .wip-paper-grid — that was the see-through bug). */
vaadin-select-list-box,
vaadin-combo-box-overlay,
vaadin-date-picker-overlay,
vaadin-time-picker-overlay,
vaadin-menu-bar-overlay,
vaadin-context-menu-overlay {
  background: var(--overlay-bg) !important;
  border: 1px solid var(--line-soft);
  border-radius: var(--r-3);
  box-shadow: var(--shadow-2);
}
vaadin-select-item,
vaadin-item,
vaadin-menu-bar-item,
vaadin-context-menu-item {
  background: var(--overlay-bg) !important;
  color: var(--ink) !important;
}
/* Hovered / focused item uses a subtle tint so it's still readable. */
vaadin-select-item:hover,
vaadin-item:hover,
vaadin-menu-bar-item:hover,
vaadin-context-menu-item:hover,
vaadin-select-item[focused],
vaadin-item[focused],
vaadin-menu-bar-item[focused],
vaadin-context-menu-item[focused] {
  background: var(--bg-sunk) !important;
}

/* Text fields */
vaadin-text-field, vaadin-text-area, vaadin-combo-box,
vaadin-date-picker, vaadin-time-picker, vaadin-number-field,
vaadin-select, vaadin-email-field, vaadin-password-field {
  --vaadin-input-field-border-radius: var(--r-2);
  --vaadin-input-field-background: var(--surface);
  --vaadin-input-field-border-color: var(--line);
}

/* Tabs — flat, hairline-bottom */
vaadin-tabs {
  border-bottom: 1px solid var(--line-soft);
}
vaadin-tab {
  font-weight: 500;
  font-size: 13px;
  color: var(--ink-3);
}
vaadin-tab[selected] { color: var(--ink); }

/* Notification / toast.
   The base override below flattens Lumo's variants, so error/success must be
   re-asserted per variant — otherwise "Import failed" renders identically to
   "Imported 3 orders" (see ui/components/Toasts.kt, the only sanctioned way
   to show one). */
vaadin-notification-card {
  background: var(--ink);
  color: var(--bg);
  border-radius: var(--r-3);
  font-size: 13px;
}
vaadin-notification-card[theme~="error"] {
  background: var(--signal-red);
  color: var(--bg);
  font-weight: 500;
}
vaadin-notification-card[theme~="success"] {
  background: var(--signal-green);
  color: var(--bg);
}

/* ============================================================================
   4. Runo-only utilities
   (use in Vaadin views via `addClassName("eyebrow")` etc.)
   ============================================================================ */
.eyebrow {
  font-size: 11px; font-weight: 500;
  letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--ink-3);
}
.serif {
  font-family: var(--font-serif); font-weight: 400;
  letter-spacing: -0.01em;
}
.mono {
  font-family: var(--font-mono);
  font-feature-settings: "tnum", "zero";
}
.tnum { font-variant-numeric: tabular-nums; }

.page-title {
  font-family: var(--font-serif);
  font-size: 32px; font-weight: 400;
  letter-spacing: -0.02em; line-height: 1.1;
  color: var(--ink);
  margin: 0;
}
.page-sub { color: var(--ink-3); font-size: 14px; margin-top: 6px; }

.chip {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: 11.5px; padding: 2px 8px; border-radius: 999px;
  background: var(--bg-elev); color: var(--ink-2);
  border: 1px solid var(--line-soft);
  font-variant-numeric: tabular-nums;
}
.chip .dot { width: 6px; height: 6px; border-radius: 50%; background: var(--ink-3); }
.chip.green  .dot { background: var(--signal-green); }
.chip.amber  .dot { background: var(--signal-amber); }
.chip.red    .dot { background: var(--signal-red); }
.chip.accent { background: var(--accent-soft); color: var(--accent-ink); border-color: var(--accent-line); }
.chip.accent .dot { background: var(--accent); }
/* Selected state for chips used as a filter control (goods-type strip). Inverted
   surface, same pairing as vaadin-button[theme~="primary"] — --ink over --bg.
   NB: --paper is NOT a token (only --paper-cream / --paper-rule exist); an
   earlier inline `color: var(--paper)` was invalid, so colour fell back to the
   inherited --ink and the pill rendered black-on-black. */
.chip.on {
  background: var(--ink); color: var(--bg);
  border-color: var(--ink); font-weight: 600;
}
.chip.on .dot { background: var(--bg); }
.chip-click { cursor: pointer; user-select: none; }

/* A decision that was a "no". Still legible, deliberately not competing with the
   live suggestions beside it: a ruled-out folder is context, not an offer. The
   strike-through is on the folder name only — the label and the restore control
   next to it are live text and must stay readable. */
.chip-muted { background: var(--bg-sunk); color: var(--ink-4); border-color: var(--line-soft); }
.strike { text-decoration: line-through; }

.kbd {
  font-family: var(--font-mono); font-size: 10.5px;
  background: var(--bg-elev); border: 1px solid var(--line);
  border-bottom-width: 2px; padding: 1px 5px; border-radius: 4px;
  color: var(--ink-3);
}

/* striped placeholder for missing imagery / 3D scenes / charts */
.placeholder {
  background: repeating-linear-gradient(135deg, var(--bg-sunk) 0 8px, var(--bg-elev) 8px 16px);
  border: 1px solid var(--line-soft);
  border-radius: var(--r-3);
  color: var(--ink-4); font-family: var(--font-mono);
  font-size: 11px; letter-spacing: 0.05em; text-transform: uppercase;
  display: grid; place-items: center;
}

/* card-shaped section — surface tile with hairline + soft shadow (design handoff) */
.card {
  background: var(--surface);
  border: 1px solid var(--line-soft);
  border-radius: var(--r-3);
  box-shadow: var(--shadow-1);
}

/* ============================================================================
   5. CF Tech Calculator  (admin/cf-calculator)
   Self-contained layout for the coldfoam dosing calculator — a runo-skinned
   replica of the coldfoam-kalkultor prototype. Tokens only; the polyol/isocyanat
   split is carried by --signal-green / --signal-amber. Emoji are an approved
   exception for this one module (docs/design/handoff.md → Sanctioned exceptions).
   ============================================================================ */
.cf-calc { max-width: 760px; width: 100%; margin-inline: auto; }

.cf-head { display: flex; align-items: center; gap: var(--s-3); }
.cf-head-icon {
  width: 44px; height: 44px; flex-shrink: 0;
  display: grid; place-items: center; font-size: 22px;
  background: var(--signal-green-soft);
  border: 1px solid var(--line-soft); border-radius: var(--r-3);
}

.cf-section {
  background: var(--surface); border: 1px solid var(--line-soft);
  border-radius: var(--r-3); box-shadow: var(--shadow-1); padding: var(--s-5);
}
.cf-section-title {
  display: block; font-size: 11px; font-weight: 600;
  letter-spacing: 0.18em; text-transform: uppercase;
  color: var(--accent-ink); margin-bottom: var(--s-4);
}

.cf-grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s-4); }

.cf-formula {
  font-family: var(--font-mono); font-size: 12.5px; color: var(--ink-3);
  text-align: center; background: var(--bg-sunk);
  border: 1px dashed var(--line); border-radius: var(--r-2);
  padding: var(--s-3); margin: var(--s-4) 0;
}

.cf-result {
  background: var(--bg-sunk); border: 1px solid var(--line-soft);
  border-radius: var(--r-2); padding: var(--s-3) var(--s-4);
  display: flex; flex-direction: column; gap: var(--s-1);
}
.cf-result-value { font-size: 26px; font-weight: 600; color: var(--signal-green); }
.cf-unit { font-size: 14px; color: var(--ink-3); margin-left: 4px; }

.cf-ratio-row { display: grid; grid-template-columns: 1fr auto 1fr; gap: var(--s-3); align-items: end; }
.cf-ratio-divider { font-size: 18px; font-weight: 700; color: var(--accent-ink); padding-bottom: 8px; text-align: center; }

.cf-chem {
  display: flex; align-items: center; gap: var(--s-3);
  padding: var(--s-3) var(--s-4); border-radius: var(--r-2); margin-top: var(--s-3);
}
.cf-chem-dot { width: 12px; height: 12px; border-radius: 50%; flex-shrink: 0; }
.cf-chem-name { flex: 1; font-weight: 600; }
.cf-chem-pct { color: var(--ink-3); }
.cf-chem-grams { font-size: 20px; font-weight: 700; min-width: 96px; text-align: right; }
.cf-chem.poly { background: var(--signal-green-soft); border: 1px solid color-mix(in oklch, var(--signal-green) 35%, transparent); }
.cf-chem.poly .cf-chem-dot { background: var(--signal-green); }
.cf-chem.poly .cf-chem-name, .cf-chem.poly .cf-chem-grams { color: var(--signal-green); }
.cf-chem.iso { background: var(--signal-amber-soft); border: 1px solid color-mix(in oklch, var(--signal-amber) 35%, transparent); }
.cf-chem.iso .cf-chem-dot { background: var(--signal-amber); }
.cf-chem.iso .cf-chem-name, .cf-chem.iso .cf-chem-grams { color: color-mix(in oklch, var(--signal-amber) 60%, var(--ink)); }

.cf-note {
  font-size: 12px; color: var(--ink-2); background: var(--accent-soft);
  border-left: 3px solid var(--accent-line); border-radius: var(--r-2);
  padding: var(--s-2) var(--s-3); margin-top: var(--s-3);
}

.cf-toggle { display: flex; gap: var(--s-3); margin-bottom: var(--s-4); }
.cf-toggle-btn {
  flex: 1; cursor: pointer; text-align: center; padding: var(--s-3);
  border-radius: var(--r-2); border: 1px solid var(--line);
  background: var(--surface); color: var(--ink-3); font-weight: 600; letter-spacing: 0.04em;
}
.cf-toggle-btn:hover { border-color: var(--line-strong); }
.cf-toggle-btn.active { border-color: var(--accent); background: var(--accent-soft); color: var(--accent-ink); }

.cf-prog-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--s-3); margin-top: var(--s-4); }
.cf-prog {
  background: var(--bg-sunk); border: 1px solid var(--line-soft);
  border-radius: var(--r-2); padding: var(--s-3);
  display: flex; flex-direction: column; gap: var(--s-1);
}
.cf-prog-icon { font-size: 18px; }
.cf-prog-label { font-size: 9.5px; letter-spacing: 0.12em; text-transform: uppercase; color: var(--ink-3); }
.cf-prog-value { font-size: 20px; font-weight: 700; color: var(--ink); }
.cf-prog-unit { font-size: 10px; color: var(--ink-3); }
.cf-prog.poly  { border-color: color-mix(in oklch, var(--signal-green) 35%, transparent); }
.cf-prog.poly  .cf-prog-value { color: var(--signal-green); }
.cf-prog.iso   { border-color: color-mix(in oklch, var(--signal-amber) 35%, transparent); }
.cf-prog.iso   .cf-prog-value { color: color-mix(in oklch, var(--signal-amber) 60%, var(--ink)); }
.cf-prog.total { border-color: var(--accent-line); }
.cf-prog.total .cf-prog-value { color: var(--accent-ink); }
.cf-prog.time  { border-color: var(--accent-line); }

.cf-steps { display: flex; flex-direction: column; gap: var(--s-2); margin-top: var(--s-4); }
.cf-step {
  display: flex; align-items: flex-start; gap: var(--s-3); padding: var(--s-3);
  border-radius: var(--r-2); background: var(--bg-sunk);
  border: 1px solid var(--line-soft); font-size: 13px; color: var(--ink-2);
}
.cf-step-num {
  flex-shrink: 0; width: 22px; height: 22px; border-radius: 50%;
  display: grid; place-items: center; background: var(--accent-soft);
  color: var(--accent-ink); font-family: var(--font-mono); font-size: 11px; font-weight: 700;
}

.cf-range { display: flex; align-items: center; gap: var(--s-2); margin-top: var(--s-2); }
.cf-range-track { flex: 1; height: 4px; border-radius: 2px; background: var(--line); overflow: hidden; }
.cf-range-fill { height: 100%; background: var(--accent); border-radius: 2px; }
.cf-range-text { font-family: var(--font-mono); font-size: 10px; color: var(--ink-4); }

/* Stack the grids on shop-floor tablet portrait / phones. */
@media (max-width: 640px) {
  .cf-calc .cf-grid-2 { grid-template-columns: 1fr; }
  .cf-calc .cf-prog-grid { grid-template-columns: 1fr 1fr; }
}

/* Access-control menu-visibility matrix — multi-column checkbox grid so a
   tier's whole menu map fits on one screen. Groups flow into up to 3
   responsive columns (~200px min); each group is kept whole. */
.matrix-columns {
  columns: 2;
  column-gap: var(--s-4);
}
.matrix-group {
  break-inside: avoid;
  margin-bottom: var(--s-3);
}
.matrix-group > .eyebrow {
  display: block;
  margin: 0 0 var(--s-1);
  color: var(--ink-4);
}
.matrix-item {
  padding: var(--s-1) 0;
}

/* ============================================================================
   6. Design QA reference  (admin/design-qa - page deleted 11.10.2026, styles kept)
   Internal QA tooling — every runo primitive on one scrollable page so the
   system can be audited in isolation. These .dqa-* helpers cover only the
   primitives no existing utility can express: the per-token swatch blocks, the
   type-scale rows, sized placeholders and the accent-tinted insight card. Every
   value is a token. Not linked from public navigation.
   ============================================================================ */

/* section rhythm + wrapping rows */
.dqa-section   { margin-top: var(--s-7); }
.dqa-row       { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-3); }
.dqa-row.start { align-items: flex-start; }
.dqa-cards     { display: flex; flex-wrap: wrap; gap: var(--s-4); align-items: stretch; }
.dqa-cards > * { flex: 1 1 220px; }

/* token swatches — 56×56 block + mono name below */
.dqa-swatch-grid  { display: grid; grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); gap: var(--s-4); }
.dqa-swatch       { display: flex; flex-direction: column; gap: var(--s-2); }
.dqa-swatch-block { width: 56px; height: 56px; flex-shrink: 0; border-radius: var(--r-2); border: 1px solid var(--line); }

.dqa-sw-bg           { background: var(--bg); }
.dqa-sw-bg-elev      { background: var(--bg-elev); }
.dqa-sw-bg-sunk      { background: var(--bg-sunk); }
.dqa-sw-surface      { background: var(--surface); }
.dqa-sw-line-soft    { background: var(--line-soft); }
.dqa-sw-line         { background: var(--line); }
.dqa-sw-line-strong  { background: var(--line-strong); }
.dqa-sw-ink          { background: var(--ink); }
.dqa-sw-ink-2        { background: var(--ink-2); }
.dqa-sw-ink-3        { background: var(--ink-3); }
.dqa-sw-ink-4        { background: var(--ink-4); }
.dqa-sw-accent       { background: var(--accent); }
.dqa-sw-accent-soft  { background: var(--accent-soft); }
.dqa-sw-accent-ink   { background: var(--accent-ink); }
.dqa-sw-accent-line  { background: var(--accent-line); }
.dqa-sw-signal-red   { background: var(--signal-red); }
.dqa-sw-signal-amber { background: var(--signal-amber); }
.dqa-sw-signal-green { background: var(--signal-green); }

/* type scale — sizes the base utilities don't cover (.page-title=32, .eyebrow=11 already exist) */
.dqa-t40     { font-family: var(--font-serif); font-weight: 400; font-size: 40px; letter-spacing: -0.02em; line-height: 1.1; color: var(--ink); }
.dqa-t18     { font-family: var(--font-serif); font-weight: 400; font-size: 18px; letter-spacing: -0.01em; color: var(--ink); }
.dqa-t14     { font-family: var(--font-sans);  font-weight: 400; font-size: 14px; color: var(--ink); }
.dqa-t13m    { font-family: var(--font-sans);  font-weight: 500; font-size: 13px; color: var(--ink); }
.dqa-t11mono { font-family: var(--font-mono);  font-weight: 400; font-size: 11px; color: var(--ink-2); font-feature-settings: "tnum", "zero"; }

/* sized placeholders (the .placeholder utility carries no intrinsic size) */
.dqa-ph-square { width: 160px; height: 160px; }
.dqa-ph-wide   { width: 320px; height: 120px; }
.dqa-ph-tall   { width: 120px; height: 240px; }

/* SamoBot-style insight — accent-tinted card surface (handoff §"insight card") */
.dqa-insight      { border-color: var(--accent-line); background: color-mix(in oklch, var(--accent-soft) 30%, var(--surface)); }
.dqa-insight-icon { color: var(--accent); }

/* footer note */
.dqa-foot { font-family: var(--font-mono); font-size: 11px; color: var(--ink-4); }

/* ============================================================================
   6. Goals & variable comp  (admin/goals-and-tasks)
   Card-based layout ported from docs/design/mockups/goals-redesign-mockup.html.
   `g-`-prefixed so the generic names (.goal, .track, .right) can't collide.
   Tokens only — spacing via --s-*, color via --ink/--accent/--signal.
   ============================================================================ */

/* comp summary band: three stats + a right-aligned fulfilment readout.
   Class-based child selectors (Vaadin emits Span/Div, not <b>/<small>/<i>). */
.g-summary {
  margin-top: var(--s-6); padding: var(--s-5) var(--s-6);
  display: grid; grid-template-columns: auto 1fr;
  gap: var(--s-7); align-items: center;
}
.g-statv { font-size: 22px; font-weight: 500; font-variant-numeric: tabular-nums; }
.g-statv .g-unit { font-size: 13px; color: var(--ink-3); font-weight: 400; margin-left: 3px; }
.g-bigwrap { justify-self: end; text-align: right; min-width: 280px; }
.g-big { font-family: var(--font-serif); font-size: 30px; line-height: 1; font-variant-numeric: tabular-nums; }
.g-big .g-amt { color: var(--accent-ink); font-weight: 500; }
.g-big .g-muted { color: var(--ink-4); font-size: 18px; }
.g-wbar {
  height: 8px; border-radius: 999px; background: var(--bg-sunk);
  overflow: hidden; margin-top: 10px; border: 1px solid var(--line-soft);
}
.g-wbar > .g-wbarfill { display: block; height: 100%; background: var(--accent); border-radius: 999px; }

/* section head: eyebrow + action row */
.g-sechead {
  display: flex; align-items: baseline; justify-content: space-between;
  margin: var(--s-8) 0 var(--s-3);
}

/* goal card: weight rail | body | right readout */
.g-goal {
  padding: var(--s-5) var(--s-6); margin-bottom: var(--s-4);
  display: grid; grid-template-columns: 96px 1fr 232px;
  gap: var(--s-6); align-items: center; cursor: pointer;
}
.g-rail { display: flex; flex-direction: column; gap: 6px; align-items: flex-start; }
.g-weight {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  width: 74px; height: 74px; border-radius: var(--r-4);
  background: var(--accent-soft); border: 1px solid var(--accent-line);
}
.g-weight .g-wnum { font-size: 24px; font-weight: 600; color: var(--accent-ink); font-variant-numeric: tabular-nums; line-height: 1; }
.g-weight .g-wlbl { font-size: 9.5px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--accent-ink); opacity: 0.8; margin-top: 2px; }
.g-paymo { font-size: 12px; color: var(--ink-3); font-variant-numeric: tabular-nums; margin-left: 2px; }

.g-titlerow { display: flex; gap: 10px; align-items: center; }
.g-title { font-family: var(--font-serif); font-size: 19px; line-height: 1.25; margin: 0; color: var(--ink); }
.g-metric { font-size: 12.5px; color: var(--ink-3); margin-top: 5px; }

/* baseline -> current -> target bar; fill width = payout%, marker = 50% reference */
.g-track {
  position: relative; height: 10px; border-radius: 999px;
  background: var(--bg-sunk); border: 1px solid var(--line-soft); margin-top: 14px;
}
.g-track > .g-trackfill { position: absolute; left: 0; top: 0; bottom: 0; border-radius: 999px; background: var(--accent); }
.g-track > .g-mk { position: absolute; top: -3px; width: 2px; height: 16px; background: var(--ink-3); opacity: 0.5; border-radius: 2px; }
.g-scale { display: flex; justify-content: space-between; font-size: 11px; color: var(--ink-4); margin-top: 6px; font-variant-numeric: tabular-nums; }
.g-scale .g-strong { color: var(--ink-2); font-weight: 500; }

.g-right { display: flex; flex-direction: column; gap: 10px; align-items: flex-end; text-align: right; }
.g-spark { width: 200px; height: 34px; display: block; }
.g-payout { font-size: 11.5px; color: var(--ink-3); font-variant-numeric: tabular-nums; }
.g-pct { font-family: var(--font-serif); font-size: 22px; color: var(--ink); font-variant-numeric: tabular-nums; }
.g-pct .g-unit { font-size: 12px; color: var(--ink-3); font-family: var(--font-sans); }

/* supporting-steps strip (e.g. G3) spanning body + right columns */
.g-steps {
  grid-column: 2 / 4; margin-top: 14px; padding-top: 12px;
  border-top: 1px dashed var(--line); display: flex; flex-wrap: wrap; gap: 8px;
}
.g-step { font-size: 11px; color: var(--ink-3); background: var(--bg-elev); border: 1px solid var(--line-soft); border-radius: var(--r-2); padding: 3px 8px; }

/* ---- goal timeline (month × goal heatmap) ---- */
.g-tl { margin-top: var(--s-4); padding: var(--s-4) var(--s-5); overflow-x: auto; }
.g-tl-head,
.g-tl-row {
  display: grid;
  grid-template-columns: 260px repeat(12, minmax(0, 1fr));
  gap: 3px; align-items: center; min-width: 640px;
}
.g-tl-head { padding-bottom: var(--s-2); }
.g-tl-mh { text-align: center; font-size: 10px; color: var(--ink-4); font-variant-numeric: tabular-nums; }

.g-tl-row { padding: var(--s-2) 0; border-top: 1px solid var(--line-soft); cursor: pointer; }
.g-tl-row:hover { background: var(--bg-elev); }
.g-tl-row.expanded { background: var(--accent-soft); }

.g-tl-label { display: flex; align-items: center; gap: var(--s-2); padding-right: var(--s-3); min-width: 0; }
.g-tl-caret { width: 14px; height: 14px; color: var(--ink-3); flex: 0 0 auto; }
.g-tl-titlestack { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1 1 auto; }
.g-tl-titlerow { display: flex; align-items: center; gap: var(--s-2); min-width: 0; }
.g-tl-title { font-size: 13px; color: var(--ink); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.g-tl-sub { font-size: 11px; color: var(--ink-4); }
.g-tl-now { font-family: var(--font-mono); font-variant-numeric: tabular-nums; font-size: 13px; font-weight: 600; color: var(--ink-2); flex: 0 0 auto; }

.g-tl-cell { height: 26px; border-radius: var(--r-1); background: var(--bg-sunk); min-width: 0; }
.g-tl-cell.empty { background: var(--bg-elev); border: 1px dashed var(--line-soft); }

/* expanded goal detail — monthly graph + scale + actions */
.g-tl-detail {
  grid-column: 1 / -1; padding: var(--s-4) var(--s-3) var(--s-5);
  border-top: 1px solid var(--line-soft); background: var(--bg-elev);
  display: flex; flex-direction: column; gap: var(--s-3);
}
.g-tl-detail-head { display: flex; align-items: center; gap: var(--s-3); flex-wrap: wrap; }
.g-tl-metric { font-size: 12.5px; color: var(--ink-3); }
.g-tl-scale { font-size: 11.5px; color: var(--ink-4); font-variant-numeric: tabular-nums; }
.g-graph-wrap { width: 100%; max-width: 640px; }
.g-graph { width: 100%; height: auto; display: block; }
.g-graph-empty { font-size: 12.5px; color: var(--ink-4); font-style: italic; padding: var(--s-4) 0; }

/* ============================================================================
   Documentation approvals  (the review dialog, and the two state blocks)
   The completeness MATRIX these rules were written for is gone: /technical-docs
   was retired into /portfolio on 21.9.2026 (contract 11.9), the per-model
   documentation chips are .pf-docchip, and the register read down its columns
   is /portfolio/register, which reuses .pf-*. What is left here is what the
   approval dialog still draws, and it is reached from both screens.
   Tokens only — no hex, no magic spacing (design handoff).
   ============================================================================ */
/* The dialog's two state blocks: nothing filed under this type, or it could not be read. */
.tdocs-empty {
  display: flex; flex-direction: column; gap: var(--s-2);
  text-align: center; padding: var(--s-8) var(--s-4);
}
.tdocs-state-title { font-size: 14px; color: var(--ink-3); }
.tdocs-state-hint { font-size: 12px; color: var(--ink-4); }

/* --- The review dialog ------------------------------------------------------
   Reached from a documentation chip on a model detail and from a row on
   /portfolio/register. Both pass the same TechnicalDocModel and the same
   DocCategoryCell, so approving is one act wherever it is done from. */
/* Review dialog. Its content is slotted light DOM, so these plain class
   selectors reach it; .tdocs-appr itself sits on the overlay and is a marker. */
.tdocs-appr-head {
  display: flex; align-items: baseline; gap: var(--s-3);
  padding-bottom: var(--s-3); border-bottom: 1px solid var(--line-soft);
}
.tdocs-appr-model { font-size: 16px; color: var(--ink); }
.tdocs-appr-code { font-size: 11px; color: var(--ink-4); }

.tdocs-appr-body { display: flex; flex-direction: column; gap: var(--s-3); padding-top: var(--s-3); }
.tdocs-appr-meaning {
  font-size: 12px; line-height: 1.5; color: var(--ink-3);
  background: var(--bg-elev); border: 1px solid var(--line-soft);
  border-radius: var(--r-2); padding: var(--s-3);
}

.tdocs-appr-doc {
  display: grid; grid-template-columns: 1fr auto; gap: var(--s-2) var(--s-3);
  align-items: center;
  padding: var(--s-3) 0; border-bottom: 1px solid var(--line-soft);
}
.tdocs-appr-docmeta { display: flex; align-items: baseline; gap: var(--s-2); min-width: 0; }
.tdocs-appr-label {
  font-size: 13px; color: var(--ink-2);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.tdocs-appr-open { font-size: 11px; color: var(--accent); flex-shrink: 0; }
.tdocs-appr-docstate {
  grid-column: 1 / -1;
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-2);
}
.tdocs-appr-who { font-size: 11px; color: var(--ink-4); }
.tdocs-appr-lapsehint { font-size: 11px; color: color-mix(in oklch, var(--signal-amber) 60%, var(--ink)); }
.tdocs-appr-btn { grid-row: 1; grid-column: 2; }
.tdocs-appr-all { align-self: flex-start; }
.tdocs-appr-readonly { font-size: 12px; color: var(--ink-4); }
/* "Drive was not checked for changes" — a caveat on what the cell below is claiming, so it sits
   with the cell rather than in a toast that has already gone by the time anyone reads it. */
.tdocs-appr-unchecked {
  font-size: 11px; line-height: 1.5;
  color: color-mix(in oklch, var(--signal-amber) 60%, var(--ink));
}

.tdocs-appr-history { display: flex; flex-direction: column; gap: 4px; margin-top: var(--s-3); }
.tdocs-appr-hist-line {
  display: grid; grid-template-columns: 108px 74px 1fr auto; gap: var(--s-2);
  font-size: 11px; color: var(--ink-4); align-items: baseline;
}
.tdocs-appr-hist-when { color: var(--ink-4); }
.tdocs-appr-hist-what { color: var(--ink-3); }
.tdocs-appr-hist-doc, .tdocs-appr-hist-who {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.tdocs-appr-hist-empty { font-size: 11px; color: var(--ink-mute); }

/* ---------------------------------------------------------------------------
   6b. Goals 2026 rebuild  (admin/goals-and-tasks)
   Three-zone layout: company weekly scoreboard -> people -> pipeline.
   Ported from docs/design/mockups/goals-rebuild-mockup.html (2026-06-07).
   --------------------------------------------------------------------------- */
/* box-sizing so the horizontal padding (2x --s-6) stays INSIDE the 100% width —
   without it the page was 48px wider than its container, forcing a horizontal
   scrollbar and clipping the right-edge controls (New-goal button, +goal, met). */
.goals-page .gv-page { padding: var(--s-7) var(--s-6) var(--s-8); width: 100%; box-sizing: border-box; }
.gv-head { margin-bottom: var(--s-4); }
.gv-section { margin-top: var(--s-6); }
.gv-sec-head { display: flex; align-items: baseline; justify-content: space-between; gap: var(--s-4); margin-bottom: var(--s-3); }

/* company scoreboard */
.gv-scoreboard { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: var(--s-4); }
.gv-stat { padding: var(--s-4) var(--s-5); display: flex; flex-direction: column; gap: 8px; cursor: pointer; }
.gv-stat:hover { border-color: var(--line-strong); }
.gv-stat-lab { font-size: 11px; font-weight: 500; letter-spacing: .04em; text-transform: uppercase; color: var(--ink-3); }
.gv-stat-valrow { display: flex; align-items: baseline; gap: 6px; }
.gv-stat-val { font-family: var(--font-serif); font-size: 26px; line-height: 1; font-variant-numeric: tabular-nums; }
.gv-stat-sub { font-size: 12px; color: var(--ink-3); }
.gv-stat-foot { display: flex; align-items: center; justify-content: space-between; }
.gv-cad { font-size: 10px; color: var(--ink-4); letter-spacing: .03em; }

/* people sections */
.gv-person { margin-bottom: var(--s-4); padding: 0; overflow: hidden; }
.gv-person-head { display: grid; grid-template-columns: 1fr 260px 130px; gap: var(--s-5); align-items: center; padding: var(--s-4) var(--s-5); }
.gv-name { font-family: var(--font-serif); font-size: 19px; line-height: 1.1; }
.gv-blend-top { display: flex; justify-content: space-between; align-items: baseline; font-size: 11px; color: var(--ink-3); }
.gv-blend-top .tnum { font-size: 15px; color: var(--ink); font-family: var(--font-serif); }
.gv-wbar { height: 7px; border-radius: 999px; background: var(--bg-sunk); overflow: hidden; border: 1px solid var(--line-soft); margin-top: 6px; }
.gv-wbarfill { height: 100%; background: var(--accent); border-radius: 999px; }
.gv-gcount { justify-self: end; display: flex; align-items: center; gap: var(--s-3); font-size: 12px; color: var(--ink-3); }

/* goal rows */
.gv-grow { display: grid; grid-template-columns: 56px 1fr 96px 130px 92px; gap: var(--s-5); align-items: center; padding: 12px var(--s-5); border-top: 1px solid var(--line-soft); cursor: pointer; }
.gv-grow:hover { background: var(--bg-elev); }
.gv-wt { display: flex; flex-direction: column; align-items: center; justify-content: center; width: 50px; height: 44px; border-radius: var(--r-2); background: var(--accent-soft); border: 1px solid var(--accent-line); }
.gv-wt-b { font-size: 16px; font-weight: 600; color: var(--accent-ink); font-variant-numeric: tabular-nums; line-height: 1; }
.gv-wt-l { font-size: 8px; letter-spacing: .1em; text-transform: uppercase; color: var(--accent-ink); opacity: .75; }
.gv-grow1 { display: flex; align-items: center; gap: 9px; min-width: 0; }
.gv-gid { font-size: 11px; font-weight: 600; color: var(--ink-4); font-variant-numeric: tabular-nums; }
.gv-gobj { font-family: var(--font-serif); font-size: 16px; line-height: 1.25; }
.gv-gmet { font-size: 12px; color: var(--ink-3); margin-top: 4px; }
.gv-gpct { justify-self: end; text-align: right; font-family: var(--font-serif); font-size: 18px; font-variant-numeric: tabular-nums; }
.gv-gpct-sub { display: block; font-size: 11px; color: var(--ink-3); font-family: var(--font-sans); }

/* sparkline */
.gv-spark-wrap { display: flex; align-items: center; }
.gv-spark { width: 84px; height: 24px; display: block; }

/* customer concentration panel (company zone) */
.gv-conc { margin-top: var(--s-4); padding: var(--s-4) var(--s-5); display: flex; flex-direction: column; gap: 10px; }
.gv-conc-head { display: flex; align-items: baseline; gap: var(--s-3); flex-wrap: wrap; }
.gv-conc-figure { display: flex; align-items: baseline; gap: 6px; }
.gv-conc-big { font-family: var(--font-serif); font-size: 34px; line-height: 1; }
.gv-conc-unit { font-size: 12px; color: var(--ink-3); letter-spacing: .04em; }
.gv-conc-better { color: var(--signal-green); border-color: var(--signal-green); }
.gv-conc-worse { color: var(--signal-red); border-color: var(--signal-red); }
.gv-conc-sub { font-size: 13px; color: var(--ink-2); }
.gv-conc-bar { display: flex; width: 100%; height: 14px; border-radius: 999px; overflow: hidden; background: var(--bg-sunk); }
.gv-conc-seg { height: 100%; min-width: 2px; }
.gv-conc-seg-0 { background: var(--cat-1); }
.gv-conc-seg-1 { background: var(--cat-2); }
.gv-conc-seg-2 { background: var(--cat-3); }
.gv-conc-seg-3 { background: var(--cat-4); }
.gv-conc-seg-4 { background: var(--cat-6); }
.gv-conc-seg-rest { background: var(--ink-mute); }
.gv-conc-legend { display: flex; flex-wrap: wrap; gap: 4px var(--s-4); font-size: 12px; color: var(--ink-2); }
.gv-conc-key { display: flex; align-items: center; gap: 6px; }
.gv-conc-dot { width: 8px; height: 8px; border-radius: 999px; display: inline-block; }
.gv-conc-trendwrap { display: flex; flex-direction: column; gap: 4px; }
.gv-conc-trend { width: 100%; height: 60px; display: block; background: var(--bg-elev); border: 1px solid var(--line-soft); border-radius: var(--r-2); }
.gv-conc-axis { display: flex; justify-content: space-between; font-size: 11px; color: var(--ink-3); font-variant-numeric: tabular-nums; }
.gv-conc-act { display: flex; align-items: center; gap: var(--s-3); flex-wrap: wrap; }
.gv-conc-note { font-size: 11px; color: var(--ink-3); line-height: 1.5; }

/* detail dialog */
.gv-detail-big { font-family: var(--font-serif); font-size: 34px; line-height: 1; font-variant-numeric: tabular-nums; }
.gv-detail-met { font-size: 13px; color: var(--ink-2); line-height: 1.5; }
.gv-graph-wrap { width: 100%; }
.gv-graph { width: 100%; height: auto; display: block; background: var(--bg-elev); border: 1px solid var(--line-soft); border-radius: var(--r-2); }
.gv-graph-empty { font-size: 12.5px; color: var(--ink-4); font-style: italic; padding: var(--s-4) 0; }
.gv-scale { font-size: 11.5px; color: var(--ink-4); font-variant-numeric: tabular-nums; }

/* detail dialog — read-first */
.gv-detail-hero { display: flex; align-items: flex-end; justify-content: space-between; gap: var(--s-3); }
.gv-detail-headline { display: flex; align-items: baseline; gap: var(--s-2); }
.gv-detail-of { font-size: 13px; color: var(--ink-3); }
.gv-detail-foot { font-size: 11.5px; color: var(--ink-4); font-variant-numeric: tabular-nums; }

/* record a reading */
.gv-record-periodbox { display: flex; align-items: center; justify-content: space-between; gap: var(--s-3);
  background: var(--bg-sunk); border: 1px solid var(--line-soft); border-radius: var(--r-2); padding: 10px 12px; }
.gv-record-periodlab { display: flex; flex-direction: column; gap: 1px; }
.gv-record-period { font-size: 14px; font-weight: 500; color: var(--ink); }
.gv-record-echo { font-size: 13px; color: var(--accent-ink); font-weight: 500; }

/* manage readings */
.gv-rd-list { display: flex; flex-direction: column; width: 100%; }
.gv-rd-head, .gv-rd-row { display: grid; grid-template-columns: 1.5fr .7fr .55fr 1fr auto; gap: var(--s-3); align-items: center; }
.gv-rd-head { font-size: 10.5px; letter-spacing: .06em; text-transform: uppercase; color: var(--ink-4); padding-bottom: 8px; border-bottom: 1px solid var(--line-soft); }
.gv-rd-row { padding: 9px 0; border-bottom: 1px solid var(--line-soft); font-size: 13px; }
.gv-rd-per { font-weight: 500; }
.gv-rd-now { font-size: 10px; padding: 1px 6px; border-radius: 999px; background: var(--accent-soft); color: var(--accent-ink); margin-inline-start: 6px; }
.gv-rd-val, .gv-rd-pct { text-align: right; }
.gv-rd-note { color: var(--ink-3); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.gv-rd-acts { display: flex; gap: 2px; justify-content: flex-end; }

/* edit-goal form */
.gv-edit-grp { font-size: 11px; font-weight: 600; letter-spacing: .06em; text-transform: uppercase; color: var(--ink-3); margin-top: var(--s-3); }
.gv-scoring-preview { font-size: 12.5px; color: var(--ink-3); background: var(--bg-elev); border: 1px solid var(--line-soft); border-radius: var(--r-2); padding: 9px 11px; }

/* 2026 per-person overview — stacked small multiples */
.gv-ov-blend { display: flex; flex-direction: column; gap: 6px; max-width: 280px; margin-bottom: var(--s-3); }
.gv-ov-row { display: grid; grid-template-columns: 220px 1fr; gap: var(--s-5); align-items: center; padding: var(--s-3) 0; border-top: 1px solid var(--line-soft); cursor: pointer; width: 100%; }
.gv-ov-row:hover { background: var(--bg-elev); }
.gv-ov-gut { display: flex; flex-direction: column; gap: 3px; }
.gv-ov-gut1 { display: flex; align-items: center; gap: 8px; }
.gv-ov-wt { display: inline-flex; align-items: center; justify-content: center; min-width: 26px; height: 22px; padding: 0 6px; border-radius: var(--r-2);
  background: var(--accent-soft); color: var(--accent-ink); border: 1px solid var(--accent-line); font-size: 12px; font-weight: 600; font-variant-numeric: tabular-nums; }
.gv-ov-obj { font-family: var(--font-serif); font-size: 15px; line-height: 1.2; }
.gv-ov-pct { font-family: var(--font-serif); font-size: 16px; font-variant-numeric: tabular-nums; color: var(--ink); }
.gv-band-wrap { width: 100%; }
.gv-band { width: 100%; height: 54px; display: block; }
.gv-ov-axis { display: grid; grid-template-columns: 220px 1fr; gap: var(--s-5); margin-top: 6px; width: 100%; }
.gv-ov { width: 100%; }
.gv-ov-months { display: flex; justify-content: space-between; font-size: 9px; color: var(--ink-4); letter-spacing: .03em; }

/* pipeline */
.gv-pipeline { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: var(--s-4); }
.gv-pipe-card { padding: var(--s-5); border: 1px dashed var(--line-strong); border-radius: var(--r-3); background: var(--bg-elev); }
.gv-pipe-h { font-size: 11px; letter-spacing: .06em; text-transform: uppercase; color: var(--ink-3); display: block; margin-bottom: 12px; }
.gv-pipe-tags { display: flex; flex-wrap: wrap; gap: 8px; }
.gv-ptag { font-size: 12px; color: var(--ink-3); background: var(--surface); border: 1px solid var(--line-soft); border-radius: 999px; padding: 5px 11px; }

/* footnote */
.gv-foot { margin-top: var(--s-7); font-size: 11.5px; color: var(--ink-4); line-height: 1.6; }

/* Goods codebook grid (FactorifyGoodsView) — clearer column affordances.
   Columns are reorderable: a move cursor on the header signals "drag me".
   The sort control is enlarged so sorting reads as obviously clickable. The
   column show/hide lives in a subtle gear at the table's top-right corner (a
   Vaadin button positioned in Kotlin, not styled here). */
vaadin-grid.goods-grid::part(header-cell) {
  cursor: move;
}
vaadin-grid.goods-grid vaadin-grid-sorter {
  cursor: pointer;
  font-size: 14px;            /* up from the 11px header text */
}
vaadin-grid.goods-grid vaadin-grid-sorter::part(indicators) {
  font-size: 1.5em;           /* the up/down arrow — a much bigger hit + read */
}
/* leave room on the right so the corner gear never covers the last sorter */
vaadin-grid.goods-grid::part(header-cell):last-of-type {
  padding-right: var(--s-6);
}

/* ============================================================================
   ERP view utilities
   Extracted from inline element.style.set(...) chains across the ERP/import
   views (ErpImportView, PipelineStrip, FactorifyApiCallsView,
   KohlSampleFinderView) so those views carry zero server-side inline styles —
   the tokens now live here, addressed by class. Purely mechanical: every rule
   below reproduces the token values that were previously set inline, so the
   rendered result is unchanged.
   ============================================================================ */

/* Layout helpers */
.stack-1 { display: flex; flex-direction: column; gap: var(--s-1); }
.spacer  { flex: 1; }
.muted   { color: var(--ink-3); }

/* Padded card interiors (design guide §7: .card-body for padded content) */
.card-body { padding: var(--s-5); display: flex; flex-direction: column; gap: var(--s-3); }
.card-row  { padding: var(--s-3) var(--s-5); display: flex; align-items: center; gap: var(--s-3); }

/* Bg-only chip fills — keep the base .chip ink + border, tint the background only.
   (Distinct from .chip.accent, which also restyles text + border.) */
.chip.fill-green  { background: var(--signal-green-soft); }
.chip.fill-red    { background: var(--signal-red-soft); }
.chip.fill-accent { background: var(--accent-soft); }
.chip.fill-sunk   { background: var(--bg-sunk); }
.chip.fill-ghost  { background: transparent; }

/* Signal-coloured inline glyphs (vaadin-icon fills with currentColor) */
.signal-green-ink { color: var(--signal-green); }
.signal-red-ink   { color: var(--signal-red); }

/* Inline banner strip (live-host / warning) */
.banner { display: flex; align-items: center; gap: var(--s-2); padding: var(--s-2) var(--s-3); border-radius: var(--r-2); background: var(--bg-sunk); }
.banner.banner--alert { background: var(--signal-red-soft); }
/* Amber, not red: the target is a real install, but the test one — see ErpImportView.buildLiveBanner. */
.banner.banner--warn  { background: var(--signal-amber-soft); }

/* Pipeline strip tiles (PipelineStrip) */
.status-row { padding: var(--s-2) var(--s-1); }
.stat-num   { font-size: 20px; font-weight: 600; }
.pipeline-tile { flex: 1; display: flex; flex-direction: column; align-items: center; padding: var(--s-3); border: 1px solid var(--line); border-radius: var(--r-2); cursor: pointer; }
.pipeline-tile.pipeline-tile--active { background: var(--accent-soft); border-color: var(--accent-line); }
.pipeline-tile.pipeline-tile--flag   { background: var(--signal-red-soft); }

/* Scrollable JSON / code panel (FactorifyApiCallsView) */
.code-scroll { max-height: 30%; overflow: auto; padding: var(--s-3); }
.code-pre    { white-space: pre-wrap; margin: 0; }

/* ============================================================
   Toolscan (ToolscanView) — the foamscan rig window.
   Mirrors docs/design/mockups/toolscan.html; tokens only.
   ============================================================ */
.ts-view { max-width: 1080px; margin: 0 auto; width: 100%; }

/* health strip */
.ts-strip { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--s-3); margin-bottom: var(--s-5); width: 100%; }
.ts-tile { border: 1px solid var(--line); border-radius: var(--r-3); background: var(--surface); padding: var(--s-4) var(--s-4) var(--s-3); box-shadow: var(--shadow-1); }
.ts-tile-n { font-size: 24px; font-weight: 600; font-variant-numeric: tabular-nums; letter-spacing: -0.02em; display: flex; align-items: baseline; gap: var(--s-2); }
.ts-tile-small { font-size: 13px; font-weight: 500; color: var(--ink-3); }
.ts-tile-l { font-size: 11px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--ink-3); font-weight: 600; margin-top: var(--s-1); }
.ts-tile-m { font-size: 12px; color: var(--ink-4); margin-top: 2px; }
.ts-tile.ts-bad { background: var(--signal-red-soft); border-color: transparent; }
.ts-tile.ts-bad .ts-tile-n { color: var(--signal-red); }
.ts-live { display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: var(--signal-green); align-self: center; }

/* latest-scan hero */
.ts-hero { border: 1px solid var(--accent-line); border-radius: var(--r-3); background: color-mix(in oklch, var(--accent-soft) 26%, var(--surface)); padding: var(--s-5) var(--s-6); margin-bottom: var(--s-6); box-shadow: var(--shadow-1); display: flex; align-items: center; gap: var(--s-6); flex-wrap: wrap; width: 100%; }
.ts-hero-empty { border: 1px dashed var(--line-strong); border-radius: var(--r-3); padding: var(--s-5) var(--s-6); margin-bottom: var(--s-6); color: var(--ink-3); width: 100%; }
.ts-lbl { font-size: 11px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--ink-3); font-weight: 600; margin-bottom: 6px; }
.ts-hero-code { font-family: var(--font-mono); font-size: 26px; font-weight: 500; letter-spacing: -0.01em; }
.ts-hero-name { font-family: var(--font-serif); font-size: 17px; color: var(--ink-2); margin-top: var(--s-1); }
.ts-hero-arrow { font-size: 20px; color: var(--ink-4); }
.ts-hero-prog { font-family: var(--font-mono); font-size: 40px; font-weight: 600; line-height: 1; color: var(--accent-ink); font-variant-numeric: tabular-nums; }
.ts-hero-when { margin-left: auto; text-align: right; }
.ts-hero-t { font-family: var(--font-mono); font-size: 14px; color: var(--ink-2); }
.ts-hero-ago { font-size: 12px; color: var(--ink-4); margin-top: 2px; }

/* tabs */
.ts-tabs { display: flex; gap: 2px; border-bottom: 1px solid var(--line); margin-bottom: var(--s-4); width: 100%; }
.ts-tab { font: inherit; font-size: 13px; font-weight: 500; background: transparent; border: 0; border-bottom: 2px solid transparent; color: var(--ink-3); padding: var(--s-2) 14px; cursor: pointer; margin-bottom: -1px; display: inline-flex; align-items: baseline; gap: var(--s-1); }
.ts-tab.on { color: var(--ink); border-bottom-color: var(--accent); font-weight: 600; }
.ts-tab-cnt { font-family: var(--font-mono); font-size: 11px; color: var(--ink-4); }

/* filter bar */
.ts-bar { display: flex; gap: var(--s-2); align-items: center; margin-bottom: var(--s-4); flex-wrap: wrap; width: 100%; }
.ts-seg { display: inline-flex; border: 1px solid var(--line); border-radius: var(--r-2); overflow: hidden; background: var(--surface); }
.ts-seg button { font: inherit; font-size: 12px; border: 0; background: transparent; color: var(--ink-3); padding: 6px 11px; cursor: pointer; border-right: 1px solid var(--line-soft); }
.ts-seg button:last-child { border-right: 0; }
.ts-seg button.on { background: var(--accent-soft); color: var(--accent-ink); font-weight: 600; }
.ts-search { margin-left: auto; width: 240px; }
.ts-empty { padding: var(--s-5) var(--s-3); color: var(--ink-4); font-style: italic; }

/* scan-feed timeline: time | rail | card */
.ts-tl { width: 100%; }
.ts-day { padding: var(--s-4) 0 var(--s-2); }
.ts-day span { font-size: 11px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--ink-4); font-weight: 600; }
.ts-ev { display: grid; grid-template-columns: 76px 18px 1fr; gap: var(--s-2); align-items: stretch; margin-bottom: var(--s-2); }
.ts-ev-t { font-family: var(--font-mono); font-size: 12px; color: var(--ink-4); text-align: right; padding-top: 14px; font-variant-numeric: tabular-nums; }
.ts-rail { position: relative; }
.ts-rail::before { content: ""; position: absolute; top: 0; bottom: calc(-1 * var(--s-2)); left: 50%; width: 1px; background: var(--line-soft); transform: translateX(-50%); }
.ts-rail::after { content: ""; position: absolute; top: 16px; left: 50%; width: 9px; height: 9px; border-radius: 50%; background: var(--surface); border: 1.5px solid var(--ink-4); transform: translateX(-50%); }
.ts-ev.ts-unknown .ts-rail::after { border-color: var(--signal-red); }
.ts-ev-card { border: 1px solid var(--line); border-radius: var(--r-3); background: var(--surface); padding: var(--s-3) var(--s-4); box-shadow: var(--shadow-1); display: flex; align-items: center; gap: var(--s-4); flex-wrap: wrap; }
.ts-ev.ts-unknown .ts-ev-card { border-color: color-mix(in oklch, var(--signal-red) 30%, var(--line)); }
.ts-ev.ts-fresh .ts-ev-card { background: color-mix(in oklch, var(--accent-soft) 30%, var(--surface)); border-color: var(--accent-line); }
.ts-ev-code { font-family: var(--font-mono); font-size: 14px; font-weight: 500; min-width: 150px; }
.ts-ev-res { display: flex; align-items: baseline; gap: var(--s-2); }
.ts-ev-prog { font-family: var(--font-mono); font-size: 17px; font-weight: 600; color: var(--ink); font-variant-numeric: tabular-nums; }
.ts-ev-name { font-size: 13px; color: var(--ink-3); }
.ts-ev-right { margin-left: auto; display: flex; align-items: center; gap: var(--s-3); }
.ts-ev-dev { font-family: var(--font-mono); font-size: 11px; color: var(--ink-4); }

/* tools table (custom DOM rows so TableFlip can glide them) */
.ts-grid { background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-3); overflow: hidden; box-shadow: var(--shadow-1); width: 100%; }
.ts-thead, .ts-row { display: grid; grid-template-columns: 26% 30% 12% 14% 12% 6%; align-items: center; }
.ts-thead { border-bottom: 1px solid var(--line); background: var(--bg-sunk); }
.ts-thead span { font-size: 11px; letter-spacing: 0.08em; text-transform: uppercase; color: var(--ink-4); font-weight: 600; padding: 10px var(--s-4); }
.ts-row { border-bottom: 1px solid var(--line-soft); }
.ts-row:hover { background: var(--bg-elev); }
.ts-row > div { padding: 12px var(--s-4); min-width: 0; }
.ts-toolname span { font-weight: 500; }
.ts-codes { display: flex; gap: 6px; flex-wrap: wrap; }
.ts-prog { font-family: var(--font-mono); font-size: 16px; font-weight: 600; font-variant-numeric: tabular-nums; }
.ts-upd { font-family: var(--font-mono); font-size: 12px; color: var(--ink-4); }
.ts-tfoot { display: flex; align-items: center; gap: var(--s-3); padding: 10px var(--s-4); border-top: 1px solid var(--line-soft); background: var(--bg-sunk); font-size: 12px; color: var(--ink-3); }
.ts-tfoot-right { margin-left: auto; }

/* marker-code chip + dialog bits */
.chip.ts-chip-code { font-family: var(--font-mono); font-weight: 500; background: var(--bg-sunk); border-color: var(--line-soft); color: var(--ink-2); }
.ts-chip-x { font: inherit; border: 0; background: transparent; color: var(--ink-4); cursor: pointer; padding: 0 0 0 2px; line-height: 1; }
.ts-chip-x:hover { color: var(--signal-red); }
.ts-code-editor { display: flex; flex-direction: column; gap: var(--s-2); width: 100%; }
.ts-map-code { font-family: var(--font-mono); font-size: 18px; font-weight: 600; margin-top: var(--s-1); }

@media (max-width: 860px) {
  .ts-strip { grid-template-columns: repeat(2, 1fr); }
  .ts-thead, .ts-row { grid-template-columns: 1fr 1fr 60px 80px; }
  .ts-thead span:nth-child(n+5), .ts-row > div:nth-child(n+5) { display: none; }
}

/* ==========================================================================
   Order book — OrderBookView, /erp/order-book
   --------------------------------------------------------------------------
   Three pieces of bespoke DOM around an AdminGrid: the rung strip that sits
   between the page header and the table, the limitations card under it, and
   the reach band at the foot. None of them is a Vaadin component and none is
   a shape the theme already carries, which is the whole bar for a block down
   here — the chips themselves reuse .chip/.chip-click untouched.
   Scoped under .obk (the view root) except the promise dialog, which Vaadin
   renders in an overlay OUTSIDE the view and therefore cannot be nested.
   ========================================================================== */

.obk .obk-strip { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-2); width: 100%; padding: var(--s-1) 0; }
.obk .obk-strip-div { width: 1px; align-self: stretch; min-height: 18px; background: var(--line); margin: 0 var(--s-2); }
.obk .obk-grow { flex: 1 1 auto; }

/* The count inside a chip, and the settled tail beside them: both are numbers
   people compare down a column of renders, so both are mono + tabular. */
.obk .obk-n { font-family: var(--font-mono); font-weight: 600; font-variant-numeric: tabular-nums; }
.obk .obk-strip-tail { font-family: var(--font-mono); font-size: 11px; font-variant-numeric: tabular-nums; color: var(--ink-4); }

/* A chip with no rows behind it. Greyed and inert — a filter that can only
   ever produce an empty table is an affordance that lies, so it does not get
   a pointer, a role, or a hover. */
.obk .chip.obk-off { opacity: 0.55; cursor: default; }

/* The clickable chips are keyboard-reachable (role=button + tabindex), so they
   need a visible focus ring of their own — the theme gives .chip none. */
.obk .chip[role="button"]:hover { border-color: var(--line-strong); }
.obk .chip[role="button"]:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

/* Limitations card: permanent, never collapsed. Measure is capped so the
   sentences read as prose rather than stretching the full table width. */
.obk .obk-card { display: flex; flex-direction: column; gap: var(--s-3); padding: var(--s-5); }
.obk .obk-notes { display: flex; flex-direction: column; gap: var(--s-2); }
.obk .obk-note { max-width: 84ch; font-size: 13.5px; line-height: 1.5; color: var(--ink-2); }

/* Reach band — quieter than the limitations, because it is about orders that
   are not on this screen at all. */
.obk .obk-band { display: flex; flex-direction: column; gap: var(--s-1); width: 100%; padding: 0 var(--s-1) var(--s-2); }
.obk .obk-reach { max-width: 96ch; font-size: 12.5px; line-height: 1.5; color: var(--ink-3); }

/* The load-failure sentence. Replaces the entire book — never shown beside
   counts, because zeros next to it would read as "the book is empty". */
.obk .obk-fail { max-width: 72ch; margin: 0; padding: var(--s-3) var(--s-4); border-radius: var(--r-2); background: var(--signal-red-soft); color: var(--signal-red); }

/* Promise dialog. Outside .obk: a vaadin-dialog-overlay is not a descendant
   of the view, so a nested selector would never match. */
.obk-dialog { display: flex; flex-direction: column; gap: var(--s-3); width: 100%; }
.obk-dl { display: grid; grid-template-columns: 116px 1fr; align-items: baseline; gap: var(--s-3); }
.obk-dt { font-size: 11px; font-weight: 500; letter-spacing: 0.08em; text-transform: uppercase; color: var(--ink-4); }
.obk-dd { font-size: 13.5px; color: var(--ink); }

/* "You have already told the customer this date." Amber, not red: re-promising
   is a legitimate act, and the rep is being told a consequence, not stopped.
   It sits directly above the picker because it is a caveat on that one field —
   read after the date is typed, it would arrive too late to change anything. */
/* Text on --signal-amber-soft is mixed toward --ink, never bare --signal-amber: at 12.5px the
   raw signal colour (L 70%) on the soft ground (L 94%) is around 2:1 and unreadable. Same mix as
   .tdocs-appr-lapsehint and .cf-chem.iso, the file's two other amber-on-amber-soft blocks. */
.obk-warn { max-width: 46ch; padding: var(--s-2) var(--s-3); border-radius: var(--r-2); background: var(--signal-amber-soft); color: color-mix(in oklch, var(--signal-amber) 60%, var(--ink)); font-size: 12.5px; line-height: 1.45; }

@media (max-width: 720px) {
  .obk .obk-grow { flex-basis: 100%; }
  .obk-dl { grid-template-columns: 1fr; gap: var(--s-1); }
}

/* ============================================================================
   Spustenie 1.10.  (/launch)
   The QI -> Factorify launch control room. Read-only in Faza 1.

   The page is one column of bands rather than a dashboard of equal panels: the
   reading order IS the order the questions get asked (are we going to make it,
   what is on fire, where is the work, what is not due on 1.10. at all), and
   equal-weight panels would flatten that into a wall.
   ============================================================================ */

.lr { gap: var(--s-6); }
.lr-head { display: flex; align-items: flex-end; justify-content: space-between; gap: var(--s-4); width: 100%; }
.lr-head-actions { display: flex; align-items: center; gap: var(--s-2); flex: none; }
.lr-body { display: flex; flex-direction: column; gap: var(--s-6); width: 100%; }
.lr-h2 { font-size: 15px; font-weight: 600; color: var(--ink); margin: var(--s-1) 0 0; }
.lr-muted { color: var(--ink-3); font-size: 12.5px; }
.lr-key { font-size: 11.5px; color: var(--ink-3); }
.lr-stamp { color: var(--ink-4); font-size: 11.5px; margin: 0; }

/* --- vitals: the header figures. auto-fit rather than a fixed column count so
   the row reflows on a laptop instead of scrolling sideways. */
.lr-vitals { display: grid; grid-template-columns: repeat(auto-fit, minmax(148px, 1fr)); gap: var(--s-3); width: 100%; }
.lr-vital {
  display: flex; flex-direction: column; gap: var(--s-1);
  padding: var(--s-3) var(--s-4);
  background: var(--bg-sunk); border-radius: var(--r-2);
}
/* The countdown is the one number this screen exists for, so it gets the accent
   ground and a larger figure. Everything else on the row stays quiet. */
.lr-vital.lead { background: var(--accent-soft); }
.lr-vital.lead .stat-num { font-size: 30px; color: var(--accent-ink); }
.lr-vital-sub { color: var(--ink-3); font-size: 11.5px; line-height: 1.35; }

/* --- HORI. Red ground, and it is simply absent when nothing is on fire — an
   empty "nothing is on fire" panel trains people to skim past the band that
   will one day be full. */
.lr-fire { padding: var(--s-4) var(--s-5); border-color: var(--signal-red-soft); }
.lr-fire-row {
  display: grid; grid-template-columns: 110px minmax(0, 2fr) 1fr 1fr auto;
  align-items: center; gap: var(--s-3);
  padding: var(--s-2) 0; border-top: 1px solid var(--line-soft);
}
.lr-fire-row:first-of-type { margin-top: var(--s-3); }
.lr-fire-title { font-size: 13.5px; color: var(--ink); overflow-wrap: anywhere; }

/* --- lanes: the Kanban summary Faza 1 shows instead of a Kanban. */
.lr-lanes { display: flex; flex-wrap: wrap; gap: var(--s-2); width: 100%; }
.lr-lane {
  flex: 1 1 140px; display: flex; flex-direction: column; gap: var(--s-1);
  padding: var(--s-3) var(--s-4);
  border: 1px solid var(--line-soft); border-radius: var(--r-2);
}
.lr-lane.done { background: var(--signal-green-soft); border-color: transparent; }

/* --- usek tiles. */
.lr-section { display: flex; flex-direction: column; gap: var(--s-2); width: 100%; }
.lr-tiles { display: grid; grid-template-columns: repeat(auto-fill, minmax(248px, 1fr)); gap: var(--s-3); margin-top: var(--s-2); }
.lr-tile { display: flex; flex-direction: column; gap: var(--s-2); padding: var(--s-4); }
.lr-tile.hot { border-color: var(--signal-red); }
.lr-tile-head { display: flex; align-items: baseline; justify-content: space-between; gap: var(--s-2); }
.lr-tile-title { font-size: 14px; font-weight: 600; color: var(--ink); }
.lr-tile-pct { font-size: 14px; font-weight: 600; color: var(--ink-2); }
.lr-bar { margin: var(--s-1) 0; }
.lr-tile-facts { display: flex; flex-wrap: wrap; gap: var(--s-1) var(--s-4); }
.lr-fact { display: flex; align-items: baseline; gap: var(--s-1); }
.lr-fact-label { font-size: 11px; letter-spacing: 0.06em; text-transform: uppercase; color: var(--ink-4); }
.lr-fact-value { font-size: 12.5px; color: var(--ink-2); }
.lr-tile-link { font-size: 12.5px; color: var(--accent); text-decoration: none; margin-top: auto; padding-top: var(--s-1); }
.lr-tile-link:hover { text-decoration: underline; }

/* --- tiers: what is NOT due on 1.10. A legend, not a scoreboard. */
.lr-tiers { display: flex; flex-direction: column; gap: var(--s-1); margin-top: var(--s-2); }
.lr-tier { display: grid; grid-template-columns: 34px minmax(0, 1fr) auto; align-items: center; gap: var(--s-3); padding: var(--s-1) 0; }
.lr-tier-key { justify-content: center; font-weight: 600; }
.lr-tier-label { font-size: 13px; color: var(--ink-2); }

/* --- Faza 2: a section's own tickets, inside its tile.
   An open list needs the whole row, not a 248px column — `:has()` widens the tile
   the moment vaadin-details reflects `opened`, with no server round trip, so the
   list is readable without the grid reflowing on every collapse. */
.lr-rows-details { width: 100%; margin-top: var(--s-2); }
.lr-tile:has(.lr-rows-details[opened]) { grid-column: 1 / -1; }
.lr-rows { display: flex; flex-direction: column; overflow-x: auto; }
.lr-row {
  display: grid; grid-template-columns: 110px minmax(0, 2fr) 1fr 1fr auto 156px;
  align-items: center; gap: var(--s-3);
  padding: var(--s-1) 0; border-top: 1px solid var(--line-soft); min-width: 620px;
}
.lr-row.hot { background: var(--signal-red-soft); }
.lr-row.done .lr-row-title { color: var(--ink-3); }
.lr-row-title { font-size: 13px; color: var(--ink); overflow-wrap: anywhere; }
.lr-row-lane { width: 148px; }
.lr-key-link { color: var(--accent); text-decoration: none; }
.lr-key-link:hover { text-decoration: underline; }

/* --- Models in Factorify, /launch/fy-entry. Borrows the board's lr-* bands, vitals and
   tiles on purpose — it is the same switch read from Factorify's side — and adds only the
   goods-type buttons inside a tile and the height of the item list in its dialog. */
.fe-installs { flex: none; }
.fe-types { display: flex; flex-wrap: wrap; gap: var(--s-1); margin-top: var(--s-1); }
.fe-names { overflow-wrap: anywhere; line-height: 1.6; margin: var(--s-2) 0 0; }
.fe-notstarted { width: 100%; }
.fe-items-grid { height: 62vh; }

/* --- Faza 2: one ticket, /launch/ticket/<key>. */
.lt-key-row { display: flex; align-items: center; gap: var(--s-2); }
.lr-back { font-size: 12.5px; color: var(--accent); text-decoration: none; }
.lr-back:hover { text-decoration: underline; }
.lt-facts { padding: var(--s-4) var(--s-5); }
.lt-blocked { padding: var(--s-4) var(--s-5); display: flex; flex-direction: column; gap: var(--s-1); }
.lt-blocked-note { margin: 0; font-size: 13.5px; color: var(--ink); }
.lt-lanes { display: flex; flex-wrap: wrap; gap: var(--s-2); margin-top: var(--s-2); }
.lt-link-row { display: grid; grid-template-columns: 110px minmax(0, 1fr); align-items: center; gap: var(--s-3); padding: var(--s-1) 0; }
.lt-link-kind { justify-content: center; }
.lt-link-a { font-size: 13px; color: var(--accent); text-decoration: none; overflow-wrap: anywhere; }
.lt-link-a:hover { text-decoration: underline; }
.lt-denik-box { margin-top: var(--s-2); }
.lt-denik-row { padding: var(--s-2) 0; border-top: 1px solid var(--line-soft); }
.lt-denik-meta { display: flex; align-items: baseline; gap: var(--s-3); }
.lt-denik-by { font-size: 12.5px; font-weight: 600; color: var(--ink-2); }
.lt-denik-text { margin: var(--s-1) 0 0; font-size: 13.5px; color: var(--ink); white-space: pre-wrap; overflow-wrap: anywhere; }
.lt-hist-row { display: grid; grid-template-columns: 130px minmax(0, 1fr) auto; align-items: baseline; gap: var(--s-3); padding: var(--s-1) 0; border-top: 1px solid var(--line-soft); }
.lt-hist-text { font-size: 13px; color: var(--ink-2); }

@media (max-width: 760px) {
  .lr-head { flex-direction: column; align-items: stretch; }
  .lr-fire-row { grid-template-columns: 1fr; gap: var(--s-1); }
  .lr-tier { grid-template-columns: 34px 1fr; }
  .lr-tier > :last-child { grid-column: 2; }
  .lt-link-row, .lt-hist-row { grid-template-columns: 1fr; gap: var(--s-1); }
}

/* ==========================================================================
   /portfolio - the portfolio master data screen (PortfolioView, PortfolioDetailDialog)
   --------------------------------------------------------------------------
   Tokens only. Nothing here is nested under .pf: the dialog's body is teleported out of
   the view root by vaadin-dialog-overlay, so a scoped selector would silently stop
   matching the moment a rule was reused there. The pf- prefix is the scope instead.
   ========================================================================== */

/* The header band. It used to stack a title column beside an action column and then let the
   page gap and three separate margin-tops pile up under it - 36px of nothing between the
   description and the search box, and 36 more between the search box and the filters. The
   bands now meet at the page's own gap and nothing carries a margin of its own. */
.pf { gap: var(--s-4); }
.pf-head { display: flex; flex-direction: column; gap: 2px; width: 100%; }
.pf-head-line { display: flex; align-items: center; justify-content: space-between; gap: var(--s-4); width: 100%; }
.pf-head-actions { display: flex; align-items: center; gap: var(--s-2); flex: none; }
.pf-head .page-sub { margin: 2px 0 0; }
.pf-filters { display: flex; flex-direction: column; gap: var(--s-3); width: 100%; }
.pf-filter-row { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-2); }
.pf-filter-row > .eyebrow { flex: 0 0 92px; }
.pf-strip-div { width: 1px; align-self: stretch; min-height: 20px; background: var(--line-soft); }
.pf-more { width: 100%; }
.pf-more::part(summary) { font-size: 12px; color: var(--ink-3); }

/* Filter chips ------------------------------------------------------------ */
.chip.pf-facet { padding: 3px 10px 3px 4px; gap: var(--s-2); font-size: 12px; }
.chip.pf-facet:hover { border-color: var(--line-strong); }
.chip.pf-facet:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.chip.pf-off { opacity: 0.5; cursor: default; }
.pf-n { font-family: var(--font-mono); font-variant-numeric: tabular-nums; font-weight: 600; opacity: 0.7; }
/* The customer's share of Kroslak turnover, last four months (contract 11.4). Quieter than the
   facet count beside it: it is context for the name, not the number the chip is counting. */
.pf-share { font-family: var(--font-mono); font-variant-numeric: tabular-nums; font-size: 11px; color: var(--ink-3); white-space: nowrap; }
.pf-ico { width: 12px; height: 12px; }
.chip.pf-dev-toggle.on { background: var(--accent-soft); color: var(--accent-ink); border-color: var(--accent-line); }

/* Customer / person marks - small categorical marks, never large fills ----- */
.pf-mark {
  position: relative; overflow: hidden; width: 20px; height: 20px; border-radius: 50%;
  flex: 0 0 auto; display: grid; place-items: center;
  font-size: 9px; font-weight: 600; letter-spacing: 0.02em; color: var(--surface);
  background: var(--ink-3);
}
.pf-cat-1 { background: var(--cat-1); } .pf-cat-2 { background: var(--cat-2); }
.pf-cat-3 { background: var(--cat-3); } .pf-cat-4 { background: var(--cat-4); }
.pf-cat-5 { background: var(--cat-5); } .pf-cat-6 { background: var(--cat-6); }
.pf-cat-7 { background: var(--cat-7); } .pf-cat-8 { background: var(--cat-8); }
.pf-cat-9 { background: var(--cat-9); } .pf-cat-10 { background: var(--cat-10); }
.pf-logo {
  width: 20px; height: 20px; border-radius: 50%; object-fit: contain; flex: 0 0 auto;
  background: var(--surface); border: 1px solid var(--line-soft);
}

/* Count line, sections, meta ---------------------------------------------- */
.pf-count { display: block; font-size: 12.5px; color: var(--ink-3); }
.pf-list { display: flex; flex-direction: column; gap: var(--s-4); width: 100%; }
.pf-sec { font-size: 18px; font-weight: 400; color: var(--ink-2); margin-top: var(--s-3); }
.pf-meta {
  display: flex; flex-wrap: wrap; gap: var(--s-4); width: 100%;
  margin-top: var(--s-5); padding-top: var(--s-3); border-top: 1px solid var(--line-soft);
  font-size: 12px;
}

/* Card grid ---------------------------------------------------------------- */
.pf-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(232px, 1fr)); gap: var(--s-4); width: 100%; }
.pf-card {
  display: flex; flex-direction: column; overflow: hidden; cursor: pointer;
  transition: border-color 80ms ease, box-shadow 80ms ease;
}
.pf-card:hover { border-color: var(--line-strong); box-shadow: var(--shadow-2); }
.pf-card:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.pf-photo {
  width: 100%; aspect-ratio: 3 / 2; object-fit: cover; display: block;
  background: var(--bg-sunk); border: 0; border-radius: 0; border-bottom: 1px solid var(--line-soft);
}
.pf-card-body { display: flex; flex-direction: column; gap: var(--s-2); padding: var(--s-3) var(--s-4) var(--s-4); flex: 1; }
.pf-card-title { display: flex; align-items: baseline; justify-content: space-between; gap: var(--s-2); min-width: 0; }
.pf-card-name {
  font-family: var(--font-serif); font-size: 17px; color: var(--ink); min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.pf-card-code { font-size: 11px; color: var(--ink-4); flex: 0 0 auto; }
.pf-card-cust { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-1); }
.chip.pf-cust-chip { padding: 2px 8px 2px 3px; gap: var(--s-1); font-size: 11px; max-width: 100%; }
.pf-card-chips { display: flex; flex-wrap: wrap; gap: var(--s-1); }
.pf-card-foot {
  display: flex; align-items: center; justify-content: space-between; gap: var(--s-2);
  margin-top: auto; padding-top: var(--s-2); font-size: 12px; color: var(--ink-3);
}
.pf-link { font-size: 12.5px; color: var(--accent); text-decoration: none; }
.pf-link:hover { text-decoration: underline; }
/* Amber ink is mixed toward --ink: raw --signal-amber on a light ground is about 2:1. */
.pf-link-amber { color: color-mix(in oklch, var(--signal-amber) 60%, var(--ink)); }

/* Development stage strip on a card ---------------------------------------- */
.pf-stage { display: flex; flex-direction: column; gap: var(--s-1); }
.pf-stage-name { font-size: 12px; color: var(--ink-2); }
.pf-stage-dots { display: flex; gap: 3px; }
.pf-step { flex: 1 1 0; height: 3px; border-radius: 2px; background: var(--line); }
.pf-step-on { background: var(--accent); }

/* Empty / error / first-open states ---------------------------------------- */
.pf-empty, .pf-error {
  display: flex; flex-direction: column; align-items: center; gap: var(--s-2);
  text-align: center; padding: var(--s-8) var(--s-4);
}
.pf-state-title { font-size: 14px; color: var(--ink-3); }
.pf-error .pf-state-title { color: var(--signal-red); }
.pf-state-hint { font-size: 12px; color: var(--ink-4); }

/* Detail dialog (teleported out of the view root - never nest these) --------
   Revised 20.9.2026 (contract 11.3): the dialog was five headings and a column of
   one-line facts, and Samo asked for "much more compact, and clickable things more
   obvious". So the rhythm tightened one step (--s-4 between blocks, --s-1 inside a
   fact), state moved from stacked rows onto wrapping chip lines, and every link is
   a bordered pill rather than bare coloured text. */
.pf-dbody { display: flex; flex-direction: column; gap: var(--s-4); width: 100%; }
.pf-dhead { display: grid; grid-template-columns: 220px minmax(0, 1fr); gap: var(--s-4); align-items: start; }
.pf-dhead > .pf-photo { border: 1px solid var(--line-soft); border-radius: var(--r-3); }
.pf-dupload { width: 100%; }
.pf-dl-wrap { display: flex; flex-direction: column; gap: var(--s-1); min-width: 0; }
.pf-dl { display: grid; grid-template-columns: 104px minmax(0, 1fr); align-items: baseline; gap: var(--s-3); }
.pf-dt { font-size: 11px; font-weight: 500; letter-spacing: 0.08em; text-transform: uppercase; color: var(--ink-4); }
.pf-dd { font-size: 13.5px; color: var(--ink); overflow-wrap: anywhere; }
.pf-dsec { margin: 0; font-family: var(--font-serif); font-size: 18px; font-weight: 400; color: var(--ink-2); }
/* Heading and its number on ONE line — what replaced five stacked H2s. */
.pf-dsec-line { display: flex; align-items: baseline; flex-wrap: wrap; gap: var(--s-3); }
.pf-dsec-block { display: flex; flex-direction: column; gap: var(--s-2); width: 100%; }
.pf-dnote { font-size: 12px; color: var(--ink-3); font-variant-numeric: tabular-nums; }
.pf-dchips { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-1); }

.pf-dverdict { font-weight: 600; }
.pf-tone-green { color: var(--signal-green); }
.pf-tone-amber { color: color-mix(in oklch, var(--signal-amber) 60%, var(--ink)); }
.pf-tone-red { color: var(--signal-red); }
.pf-tone-accent { color: var(--accent-ink); }

.pf-dregister { display: flex; flex-direction: column; gap: var(--s-2); width: 100%; }
.pf-drow {
  display: flex; align-items: center; flex-wrap: wrap; gap: var(--s-3);
  padding: var(--s-2) 0; border-top: 1px solid var(--line-soft);
}
.pf-drow-name { font-size: 13.5px; color: var(--ink); }
.pf-drow .pf-dnote { margin-left: auto; }

/* Links look like links: a bordered pill, not a word that happens to be blue.
   The one thing beside them that is NOT clickable ("no model pas yet") is an amber
   chip, which shares none of this. */
.pf-dlinkrow { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-2); }
.pf-dlink {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: 12px; padding: 3px 10px; border-radius: 999px;
  color: var(--accent-ink); background: var(--accent-soft);
  border: 1px solid var(--accent-line); text-decoration: none;
}
.pf-dlink:hover { border-color: var(--accent); text-decoration: underline; }
.pf-dlink:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
/* A customer chip and the pill to its product page on the customer's own website (contract
   11.14), kept together so a wrapping row never strands the pill beside the wrong customer. */
.pf-cust-pair { display: inline-flex; flex-wrap: wrap; align-items: center; gap: var(--s-1); max-width: 100%; }

/* --- The documentation matrix ----------------------------------------------
   This replaced nine EMPTY boxes: .tdocs-cell classes on childless divs, whose only
   trace of a category was a title attribute (contract 11.3, Samo's screenshot). The
   rule the shape encodes: a category is TEXT on a chip, never an attribute on a box.
   Three mark shapes, so colour is never the only signal — a filled dot is a document,
   a hollow ring is a folder link, an en dash is nothing linked. Folder and missing
   both take .chip-muted, so the shape is what separates them. */
.pf-docmatrix { display: flex; flex-wrap: wrap; gap: var(--s-1); width: 100%; }
.chip.pf-docchip { padding: 3px 9px; }
.pf-docchip-name { white-space: nowrap; }
.chip.pf-docchip .pf-doc-ring {
  width: 6px; height: 6px; border-radius: 50%;
  background: transparent; border: 1.5px solid var(--ink-3); box-sizing: border-box;
}
.chip.pf-docchip .pf-doc-dash { font-size: 11px; line-height: 1; color: var(--ink-4); }
.chip.pf-docchip.chip-click:hover { border-color: var(--accent-line); box-shadow: inset 0 0 0 1px var(--accent-line); }
/* .chip carries no focus ring of its own and these are keyboard-reachable. */
.chip.pf-docchip.chip-click:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
/* The document itself stays one click away, as it was on the matrix: the mark is a
   real anchor with its own padding, not a 6 px dot with a listener. */
.pf-doc-open { display: inline-flex; align-items: center; padding: 3px; margin: -3px 0 -3px -3px; border-radius: 50%; }
.pf-doc-open:hover { background: var(--accent-soft); }
.pf-doc-open:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
/* Category hue, on an unapproved document only — once a state colour is on the chip
   a second hue would be two signals fighting over one 6 px circle. */
.chip.pf-doccat-1 .dot { background: var(--cat-1); }
.chip.pf-doccat-2 .dot { background: var(--cat-2); }
.chip.pf-doccat-3 .dot { background: var(--cat-3); }
.chip.pf-doccat-4 .dot { background: var(--cat-4); }
.chip.pf-doccat-5 .dot { background: var(--cat-5); }
.chip.pf-doccat-6 .dot { background: var(--cat-6); }
.chip.pf-doccat-7 .dot { background: var(--cat-7); }
.chip.pf-doccat-8 .dot { background: var(--cat-8); }
.chip.pf-doccat-9 .dot { background: var(--cat-9); }
/* A tap target a finger can hit; the 26 px chip is a pointer-only size. */
@media (pointer: coarse) {
  .chip.pf-docchip { padding: 7px 10px; }
}

.pf-deditor { display: flex; flex-direction: column; gap: var(--s-3); width: 100%; padding-top: var(--s-2); }
.pf-dedit { display: flex; align-items: flex-end; gap: var(--s-2); width: 100%; }
.pf-dfield { flex: 1; }

.pf-dstages { display: flex; flex-direction: column; gap: var(--s-1); }
.pf-dstage {
  display: flex; align-items: center; gap: var(--s-3); padding: var(--s-1) var(--s-2);
  border-radius: var(--r-2); font-size: 13px; color: var(--ink-3);
}
.pf-dstage-on { color: var(--ink); }
.pf-dstage-now { background: var(--accent-soft); color: var(--accent-ink); }
.pf-stage-raw { align-self: flex-start; }

/* Amro's 28 development steps. Same row rhythm as the sampling stages above, but the state is a
   chip rather than a position, so the chip is pushed to the right edge and the name takes the
   slack — 28 rows scroll, and a name that wraps must not shove the chip out of line. */
.pf-dsteps { display: flex; flex-direction: column; gap: 2px; }
.pf-dstep {
  display: flex; align-items: center; gap: var(--s-3); padding: var(--s-1) var(--s-2);
  border-radius: var(--r-2); font-size: 13px; color: var(--ink-2);
}
.pf-dstep > :nth-child(2) { flex: 1; min-width: 0; }
.pf-dstep > .chip { flex: 0 0 auto; }

@media (max-width: 760px) {
  .pf-head-line { flex-wrap: wrap; }
  .pf-filter-row > .eyebrow { flex: 0 0 100%; }
  .pf-grid { grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); }
  .pf-dhead { grid-template-columns: 1fr; }
  .pf-dl { grid-template-columns: 1fr; gap: 2px; }
  .pf-drow .pf-dnote { margin-left: 0; }
}

/* ==========================================================================
   /marketing/* - the Mailchimp snapshot (MarketingPage and its six views)
   --------------------------------------------------------------------------
   Tokens only; the mk- prefix is the scope. Every screen is a header band, an optional
   warning band, filters, a result line with the one primary action, and a grid.
   ========================================================================== */
.mk-page { gap: var(--s-4); }
.mk-head { display: flex; flex-direction: column; gap: 2px; width: 100%; }
.mk-head .page-sub { margin: 2px 0 var(--s-2); }
.mk-tabs { width: 100%; }
/* The loader (admin only) stands apart at the right end of the strip. */
.mk-snapshot { color: var(--ink-3); font-size: 12.5px; margin-top: var(--s-2); }
.mk-back { font-size: 13px; }

.mk-notes { display: flex; flex-direction: column; gap: var(--s-2); width: 100%; }
.mk-note { flex-wrap: wrap; align-items: baseline; line-height: 1.45; width: 100%; box-sizing: border-box; }
.mk-note > span:first-child { flex: 1 1 420px; }
.mk-quick { flex: none; }

.mk-filters {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: var(--s-2) var(--s-3); width: 100%; align-items: end;
}
.mk-filters > * { min-width: 0; width: 100%; }
.mk-filter-wide { grid-column: span 2; }
.mk-filter-checks {
  grid-column: 1 / -1; display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-1) var(--s-4);
}

.mk-result-bar { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-3); width: 100%; }
.mk-result { color: var(--ink-3); font-size: 13px; flex: 1 1 auto; }
.mk-search { flex: 0 1 320px; min-width: 200px; }

.mk-grid { width: 100%; height: 62vh; min-height: 360px; }
.mk-grid-auto { width: 100%; }
.mk-empty { width: 100%; }
.mk-muted { color: var(--ink-3); font-size: 12.5px; }

.mk-status { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-2); }
.mk-status-note { color: var(--ink-3); font-size: 13px; }

.mk-cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: var(--s-3); width: 100%; }
.mk-card { min-width: 0; }
.mk-field { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.mk-field-label { color: var(--ink-3); font-size: 11.5px; }
.mk-field-value { color: var(--ink); font-size: 13.5px; overflow-wrap: anywhere; }
.mk-facts { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: var(--s-3); }
.mk-facts > .eyebrow { grid-column: 1 / -1; }

.mk-section { display: flex; flex-direction: column; gap: var(--s-2); width: 100%; }
.mk-h2 { font-family: var(--font-serif); font-weight: 400; font-size: 22px; margin: var(--s-4) 0 0; color: var(--ink); }
.mk-details { width: 100%; }

.mk-kpis { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: var(--s-3); width: 100%; }
.mk-kpi { display: flex; flex-direction: column; gap: 2px; padding: var(--s-3) var(--s-4); }
.mk-kpi-value { font-family: var(--font-serif); font-size: 26px; color: var(--ink); line-height: 1.15; }

.mk-tab-content { width: 100%; padding-top: var(--s-3); }
.mk-email { display: flex; flex-direction: column; gap: var(--s-2); width: 100%; }
.mk-email-frame {
  width: 100%; max-width: 820px; height: 78vh; min-height: 480px;
  border: 1px solid var(--line-soft); border-radius: var(--r-3); background: var(--surface);
}
.mk-pre {
  white-space: pre-wrap; overflow-wrap: anywhere; font-family: var(--font-mono); font-size: 12.5px;
  background: var(--bg-sunk); border-radius: var(--r-2); padding: var(--s-4); margin: 0; max-width: 820px;
}
.mk-url { overflow-wrap: anywhere; white-space: normal; font-size: 13px; }

.mk-chips { display: flex; flex-wrap: wrap; gap: var(--s-2); }

/* The history: day headings, then one row per event - time, what happened, which campaign. */
.mk-tl { display: flex; flex-direction: column; gap: 2px; width: 100%; }
.mk-tl-day { margin: var(--s-3) 0 var(--s-1); }
.mk-tl-row {
  display: grid; grid-template-columns: 48px 118px minmax(0, 1fr); align-items: baseline;
  gap: var(--s-3); padding: var(--s-1) var(--s-2); border-radius: var(--r-2); font-size: 13px;
}
.mk-tl-row:hover { background: var(--bg-sunk); }
.mk-tl-time { color: var(--ink-3); }
.mk-tl-type { justify-self: start; }
.mk-tl-body { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.mk-tl-detail { color: var(--ink-3); font-size: 12.5px; overflow-wrap: anywhere; }

.mk-upload { width: 100%; max-width: 640px; }
.mk-drops { display: flex; flex-direction: column; gap: 2px; width: 100%; }
.mk-drop-row { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-2) var(--s-3); font-size: 13px; }
.mk-package { width: 100%; }

@media (max-width: 760px) {
  .mk-filter-wide { grid-column: auto; }
  .mk-tl-row { grid-template-columns: 44px minmax(0, 1fr); }
  .mk-tl-body { grid-column: 1 / -1; }
  .mk-grid { height: 70vh; }
}

/* ==========================================================================
   /marketing/invitations - guest care for a company event (GuestCareView), the Invitations tab of Marketing
   --------------------------------------------------------------------------
   Tokens only; the iv- prefix is the scope. Phone first: the cards stack in one
   column and every control is full width until the viewport has room for more.
   The list is a FLIP root (TableFlip) - cards and group heads carry data-flip-key.
   ========================================================================== */
.iv-page { gap: var(--s-4); }
.iv-head { display: flex; flex-direction: column; gap: 2px; width: 100%; }
.iv-head .page-sub { margin: 2px 0 var(--s-1); }
.iv-head .mk-tabs { margin-bottom: var(--s-2); }
.iv-meta { color: var(--ink-3); font-size: 12.5px; }

.iv-kpis { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: var(--s-3); width: 100%; }
.iv-kpi { display: flex; flex-direction: column; gap: 2px; padding: var(--s-3) var(--s-4); min-width: 0; }
.iv-kpi-value { font-family: var(--font-serif); font-size: 30px; color: var(--ink); line-height: 1.1; }
.iv-kpi-meta { color: var(--ink-3); font-size: 12.5px; line-height: 1.4; }

.iv-toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-2) var(--s-4); width: 100%; }
.iv-filters { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-2); flex: 1 1 auto; }
.iv-filters .chip { font-size: 12.5px; padding: 4px 10px; }
.iv-sep { width: 1px; align-self: stretch; background: var(--line); margin: 0 var(--s-1); }
.iv-actions { display: flex; gap: var(--s-2); }

.iv-list { display: flex; flex-direction: column; gap: var(--s-3); width: 100%; }
.iv-empty { color: var(--ink-3); font-size: 13.5px; padding: var(--s-4) 0; }
.iv-group {
  display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: var(--s-1) var(--s-3);
  margin-top: var(--s-3); padding-bottom: var(--s-1); border-bottom: 1px solid var(--line);
}
.iv-group-name { font-family: var(--font-serif); font-size: 21px; color: var(--ink); }
.iv-group-meta { color: var(--ink-3); font-size: 12.5px; }

.iv-guest { display: flex; flex-direction: column; gap: var(--s-2); padding: var(--s-3) var(--s-4); min-width: 0; }
.iv-guest--inactive { background: var(--bg-sunk); }
.iv-guest--inactive .iv-name { color: var(--ink-3); }
.iv-guest-top { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--s-3); }
.iv-guest-name { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-2); min-width: 0; }
.iv-name { font-size: 15.5px; font-weight: 600; color: var(--ink); overflow-wrap: anywhere; }
.iv-status { flex: none; font-size: 12.5px; padding: 4px 8px 4px 10px; }
.iv-caret { width: 14px; height: 14px; color: var(--ink-3); }
.iv-guest-contact { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--s-1) var(--s-3); font-size: 13px; color: var(--ink-2); }
.iv-company { color: var(--ink-2); }
.iv-link { color: var(--accent-ink); text-decoration: none; overflow-wrap: anywhere; }
.iv-link:hover { text-decoration: underline; }
.iv-guest-sessions { display: flex; flex-wrap: wrap; gap: var(--s-1) var(--s-2); }
.iv-note { width: 100%; }
.iv-guest-foot { display: flex; flex-wrap: wrap; align-items: flex-end; justify-content: space-between; gap: var(--s-2) var(--s-3); }
.iv-owner { flex: 1 1 220px; max-width: 320px; }
.iv-guest-foot-end { display: flex; align-items: center; gap: var(--s-2); margin-left: auto; }
.iv-last { color: var(--ink-3); font-size: 12px; }
.iv-more { flex: none; }

.iv-team { width: 100%; margin-top: var(--s-4); }
.iv-team-list { display: flex; flex-direction: column; gap: var(--s-2); }
.iv-guest--team { background: var(--bg-elev); }

.iv-dialog-note { display: block; color: var(--ink-3); font-size: 13px; line-height: 1.45; margin-bottom: var(--s-3); }
.iv-import-text { width: 100%; min-height: 220px; font-family: var(--font-mono); font-size: 12px; }
.iv-import-result { display: flex; flex-direction: column; gap: var(--s-1); margin-top: var(--s-3); }
.iv-import-summary { width: 100%; box-sizing: border-box; }
.iv-import-row { font-size: 13px; color: var(--ink-2); }
.iv-history { display: flex; flex-direction: column; gap: var(--s-2); }
.iv-history-row { display: flex; flex-direction: column; gap: 1px; font-size: 13px; color: var(--ink-2); padding-bottom: var(--s-2); border-bottom: 1px solid var(--line-soft); }

/* The switch inside Invitations (InvitationsNav): who to invite, then the guests. An underline
   switch rather than chips, so it never reads as one more filter. */
.iv-subnav { display: flex; flex-wrap: wrap; gap: var(--s-4); border-bottom: 1px solid var(--line-soft); margin-bottom: var(--s-2); }
.iv-subnav-link {
  font-size: 14px; color: var(--ink-3); text-decoration: none;
  padding: var(--s-2) 0; margin-bottom: -1px; border-bottom: 2px solid transparent;
}
.iv-subnav-link:hover { color: var(--ink); }
.iv-subnav-link.is-active { color: var(--ink); font-weight: 600; border-bottom-color: var(--ink); }
.iv-subnav-link:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* /marketing/invitations/sort - who to invite (PoolSortView). The ip- prefix; the page, KPIs,
   toolbar, dialogs and history reuse the iv- pieces above. A company's people stand together
   under its head; the list is a FLIP root, rows and heads carry data-flip-key. */
.ip-search { width: 100%; max-width: 420px; }
.ip-inviter { min-width: 240px; }
.ip-company-head {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--s-1) var(--s-3);
  padding-top: var(--s-3); margin-top: var(--s-2); border-top: 1px solid var(--line-soft);
}
.ip-company-name { font-family: var(--font-serif); font-size: 19px; color: var(--ink); overflow-wrap: anywhere; }
.ip-company-meta { color: var(--ink-3); font-size: 12.5px; }
.ip-company-actions { display: flex; flex-wrap: wrap; gap: var(--s-2); margin-left: auto; }
.ip-row { display: flex; flex-direction: column; gap: var(--s-2); padding: var(--s-3) var(--s-4); min-width: 0; }
.ip-row--member { margin-left: var(--s-4); }
.ip-row--not-inviting { background: var(--bg-sunk); }
.ip-row--not-inviting .ip-name { color: var(--ink-3); }
.ip-top { display: flex; align-items: flex-start; gap: var(--s-3); }
.ip-rank { flex: none; min-width: 2.5em; color: var(--ink-3); font-size: 12.5px; padding-top: 3px; }
.ip-who { display: flex; flex-direction: column; gap: 1px; flex: 1 1 auto; min-width: 0; }
.ip-name { font-size: 15.5px; font-weight: 600; color: var(--ink); overflow-wrap: anywhere; }
.ip-company { color: var(--ink-2); font-size: 13px; overflow-wrap: anywhere; }
.ip-actions { display: flex; align-items: center; gap: var(--s-2); flex: none; }
.ip-actions vaadin-button.ip-go { font-weight: 600; }
.ip-chips { display: flex; flex-wrap: wrap; gap: var(--s-1) var(--s-2); }
/* The company-size chip links to the register page; it reads as a chip, not as underlined text. */
.ip-size { text-decoration: none; }
.ip-why { font-size: 13.5px; color: var(--ink-2); line-height: 1.45; }
.ip-suggest { font-size: 12.5px; color: var(--ink-3); line-height: 1.4; }
.ip-reason { font-size: 12.5px; color: var(--ink-2); font-style: italic; }
.ip-note { font-size: 12.5px; color: var(--ink-2); line-height: 1.45; white-space: pre-line; }
.ip-answer { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-1) var(--s-2); }
.ip-answer-label { font-size: 12.5px; color: var(--ink-3); }
.ip-answer vaadin-button.ip-answer-note { margin-left: auto; }
.ip-more { display: flex; justify-content: center; width: 100%; }

@media (max-width: 640px) {
  .iv-kpis { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s-2); }
  .iv-kpi { padding: var(--s-2) var(--s-3); }
  .iv-kpi-value { font-size: 24px; }
  .iv-guest { padding: var(--s-3); }
  .iv-owner { max-width: none; flex-basis: 100%; }
  .iv-guest-foot-end { width: 100%; justify-content: space-between; margin-left: 0; }
  .iv-sep { display: none; }
  .ip-row { padding: var(--s-3); }
  .ip-row--member { margin-left: 0; }
  .ip-top { flex-wrap: wrap; }
  .ip-actions { width: 100%; justify-content: flex-end; }
  .ip-company-actions { width: 100%; margin-left: 0; }
  .ip-search { max-width: none; }
  .ip-inviter { min-width: 0; width: 100%; }
}

/* /marketing/invitations/send - approve and send the invitations (InviteSendView). The is- prefix
   for the page, its status card, sections and dialogs; iv-mail- for one mail's card
   (InviteMailCard). The page, KPIs and dialogs reuse the iv-/ip- pieces above. Phone first: one
   column, every card full width, buttons wrap under the text. The board is a FLIP root: section
   heads and cards carry data-flip-key, so a mail slides from In review to Ready to send. */
.is-status { display: flex; flex-direction: column; gap: var(--s-2); padding: var(--s-3) var(--s-4); width: 100%; box-sizing: border-box; }
.is-status-row { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-1) var(--s-3); font-size: 13.5px; color: var(--ink-2); }
.is-status-dot { flex: none; width: 8px; height: 8px; border-radius: 50%; background: var(--ink-4); }
.is-status-dot--green { background: var(--signal-green); }
.is-status-dot--amber { background: var(--signal-amber); }
.is-status-dot--red { background: var(--signal-red); }
.is-status-text { flex: 1 1 240px; min-width: 0; line-height: 1.45; overflow-wrap: anywhere; }
.is-status-action { flex: none; }
.is-restricted { width: 100%; box-sizing: border-box; font-size: 13px; }

.is-kpis { grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); }
.is-kpis .iv-kpi-value { font-size: 26px; }

.is-notice { display: flex; flex-direction: column; gap: var(--s-2); width: 100%; }
.is-notice:empty { display: none; }
.is-notice-text { width: 100%; box-sizing: border-box; font-size: 13.5px; line-height: 1.45; }
.banner.is-notice-ok { background: var(--signal-green-soft); }

.is-board { gap: var(--s-3); }
.is-section { display: flex; flex-direction: column; gap: var(--s-1); margin-top: var(--s-4); padding-bottom: var(--s-2); border-bottom: 1px solid var(--line); }
.is-section-top { display: flex; align-items: baseline; gap: var(--s-2); }
.is-section-title { font-family: var(--font-serif); font-size: 21px; font-weight: 400; color: var(--ink); margin: 0; }
.is-section-explain { color: var(--ink-3); font-size: 13px; line-height: 1.45; }
.is-section-actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-2) var(--s-3); margin-top: var(--s-2); }
.is-progress { color: var(--ink-2); font-size: 13px; }
.is-progress:empty { display: none; }
.is-section-empty { display: flex; flex-wrap: wrap; gap: var(--s-1) var(--s-2); color: var(--ink-3); font-size: 13px; padding: var(--s-1) 0; }
.is-section-empty-title { color: var(--ink-2); font-weight: 600; }
.is-section-empty-title::after { content: " ·"; color: var(--ink-4); font-weight: 400; }

.iv-mail-card { display: flex; flex-direction: column; gap: var(--s-2); padding: var(--s-3) var(--s-4); min-width: 0; }
.iv-mail-card--check-sent { border-color: var(--signal-red); }
.iv-mail-card--sent { background: var(--bg-elev); }
.iv-mail-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--s-1) var(--s-3); }
.iv-mail-company { font-size: 15.5px; font-weight: 600; color: var(--ink); overflow-wrap: anywhere; }
.iv-mail-body, .iv-mail-review, .iv-mail-below { display: flex; flex-direction: column; gap: var(--s-2); min-width: 0; }
.iv-mail-flag:empty { display: none; }
.iv-mail-line { display: flex; flex-wrap: wrap; gap: var(--s-1) var(--s-2); font-size: 13.5px; color: var(--ink-2); min-width: 0; }
.iv-mail-label { flex: none; min-width: 4.5em; color: var(--ink-3); font-size: 12.5px; padding-top: 1px; }
.iv-mail-addresses, .iv-mail-subject-text, .is-confirm-value { min-width: 0; overflow-wrap: anywhere; color: var(--ink); }
.iv-mail-recipients { display: flex; flex-direction: column; gap: var(--s-1); }
.iv-mail-people { font-size: 13.5px; color: var(--ink-2); line-height: 1.45; overflow-wrap: anywhere; }
.iv-mail-meta { color: var(--ink-3); font-size: 12.5px; line-height: 1.4; }
.iv-mail-reason { color: var(--ink-2); font-size: 12.5px; line-height: 1.4; font-style: italic; }
.iv-mail-explain { width: 100%; box-sizing: border-box; font-size: 13px; line-height: 1.45; }
.iv-mail-clean { color: var(--signal-green); font-size: 13px; }

.iv-mail-preview {
  white-space: pre-wrap; overflow-wrap: anywhere; font-size: 13.5px; line-height: 1.55; color: var(--ink);
  padding: var(--s-3); border-radius: var(--r-2); background: var(--bg-sunk); max-height: 360px; overflow-y: auto;
}
.iv-mail-missing { font-family: var(--font-mono); font-size: 12.5px; color: var(--signal-red); }

.iv-mail-members { display: flex; flex-direction: column; gap: var(--s-1); }
.iv-mail-member { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-1) var(--s-2); padding: var(--s-1) 0; border-bottom: 1px solid var(--line-soft); }
.iv-mail-member:last-child { border-bottom: 0; }
.iv-mail-role { flex: none; min-width: 3.5em; }
.iv-mail-role-text { flex: none; min-width: 3.5em; color: var(--ink-3); font-size: 12.5px; }
.iv-mail-who { display: flex; flex-direction: column; gap: 1px; flex: 1 1 180px; min-width: 0; }
.iv-mail-name { font-size: 14px; font-weight: 600; color: var(--ink); overflow-wrap: anywhere; }
.iv-mail-address { font-size: 12.5px; color: var(--ink-3); overflow-wrap: anywhere; }
.iv-mail-remove { flex: none; margin-left: auto; }
.iv-mail-outcomes { display: flex; flex-wrap: wrap; gap: var(--s-1); width: 100%; }
.iv-mail-add { width: 100%; max-width: 420px; }
.iv-mail-subject, .iv-mail-text { width: 100%; }
.iv-mail-text { min-height: 220px; }

.iv-mail-findings { display: flex; flex-direction: column; gap: var(--s-1); }
.iv-mail-finding { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-1) var(--s-2); padding: var(--s-2) var(--s-3); border-radius: var(--r-2); font-size: 13px; line-height: 1.45; }
.iv-mail-finding--block { background: var(--signal-red-soft); color: var(--ink); }
.iv-mail-finding--check { background: var(--bg-sunk); color: var(--ink-2); }
.iv-mail-finding-level { flex: none; font-size: 11.5px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.04em; }
.iv-mail-finding--block .iv-mail-finding-level { color: var(--signal-red); }
.iv-mail-finding--check .iv-mail-finding-level { color: var(--ink-3); }
.iv-mail-finding-text { flex: 1 1 200px; min-width: 0; overflow-wrap: anywhere; }
.iv-mail-fix { flex: none; }
.iv-mail-ack { font-size: 13.5px; }

.iv-mail-actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-2); }
.iv-mail-actions vaadin-button.ip-go { font-weight: 600; }
vaadin-button.iv-mail-danger { color: var(--signal-red); }
.iv-mail-sending { display: flex; align-items: center; gap: var(--s-2); color: var(--ink-2); font-size: 13px; }
.iv-mail-progress { flex: 0 0 120px; margin: 0; }

.is-confirm, .is-confirm-list { display: flex; flex-direction: column; gap: var(--s-2); margin-bottom: var(--s-3); }
.is-confirm-list { max-height: 50vh; overflow-y: auto; }
.is-confirm-item { display: flex; flex-direction: column; gap: 1px; padding-bottom: var(--s-2); border-bottom: 1px solid var(--line-soft); font-size: 13.5px; }

.is-signature-text { width: 100%; }
.is-signature-html textarea { font-family: var(--font-mono); font-size: 12px; }
.is-signature-frame { width: 100%; min-height: 140px; border: 1px solid var(--line); border-radius: var(--r-2); background: var(--surface); }

.is-settings-section { display: block; margin-top: var(--s-4); }
.is-settings-file { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-2); margin-bottom: var(--s-2); }
.is-settings-current { flex: 1 1 240px; font-size: 13.5px; color: var(--ink-2); overflow-wrap: anywhere; }
.is-settings-row { display: flex; flex-wrap: wrap; align-items: flex-end; gap: var(--s-2) var(--s-3); margin-top: var(--s-2); }
.is-settings-facts { min-height: 140px; }
.is-settings-links { display: flex; flex-direction: column; gap: var(--s-3); }
.is-settings-link { display: flex; flex-direction: column; gap: var(--s-1); padding-bottom: var(--s-2); border-bottom: 1px solid var(--line-soft); }
.is-settings-session { font-weight: 600; color: var(--ink); font-size: 14px; }
.is-settings-placeholder { width: 10em; }
.is-settings-url { flex: 1 1 260px; }

/* Controls read as controls, on every screen (Invitations 29.9.2026, app-wide the
   same day). Filled ink = the screen's one primary; ink outline (.ip-go) = a card's
   or row's own next step; --line-control outline = anything else you press or type
   into; text only = icon buttons (x, the row menu). A static chip keeps its hairline
   pill, so a pill with a firm outline is always a filter or a menu. Colour variants
   (error, success, contrast) keep their colour and gain the same edge. See
   runo-design.md "Controls vs. content". */
vaadin-button:where(:not([theme~="primary"]):not([theme~="tertiary"]):not([theme~="tertiary-inline"])) {
  background: var(--surface);
  border: 1px solid var(--line-control);
}
vaadin-button:where(:not([theme~="primary"]):not([theme~="tertiary"]):not([theme~="tertiary-inline"])):not([disabled]):hover {
  background: var(--bg-sunk);
  border-color: var(--ink-3);
}
vaadin-button.ip-go:not([disabled]) { border-color: var(--ink); font-weight: 600; }
/* A pressed toggle (an answer that is set): outlined in the accent with a tick, never filled — the
   filled button stays the screen's one primary (D-009). The tick is the button's own icon. */
vaadin-button.is-pressed:not([disabled]) { border-color: var(--accent); color: var(--accent-ink); font-weight: 600; }
vaadin-button.is-pressed:not([disabled]):hover { border-color: var(--accent); background: var(--accent-soft); }
vaadin-button:where(:not([theme~="primary"]):not([theme~="tertiary"]):not([theme~="tertiary-inline"]))[disabled] {
  background: var(--bg-sunk);
  border-color: var(--line);
}
/* Lumo paints a tertiary background with !important from this custom property, so set it, not background. */
vaadin-button[theme~="tertiary"]:not([disabled]):hover { --vaadin-button-tertiary-background: var(--bg-sunk); color: var(--ink); }
/* Fields and drop-downs: a visible box. Lumo defaults the border width to 0, so
   the colour the theme sets never drew and a --surface field vanished on a card.
   Read-only and disabled keep the borderless look, so they read as inert. */
:is(vaadin-text-field, vaadin-text-area, vaadin-combo-box, vaadin-multi-select-combo-box, vaadin-select,
  vaadin-date-picker, vaadin-time-picker, vaadin-date-time-picker, vaadin-number-field, vaadin-integer-field,
  vaadin-big-decimal-field, vaadin-email-field, vaadin-password-field):not([readonly]):not([disabled]) {
  --vaadin-input-field-border-width: 1px;
  --vaadin-input-field-border-color: var(--line-control);
}
/* Checkboxes and radio buttons: the unchecked box is a control too, so --surface with the
   --line-control edge, like a field. Checked keeps Lumo's filled accent (the host rule that
   clears the edge would lose to this one, hence the :not()s); disabled and read-only stay inert.
   The grid tree toggle is the same kind of control: --ink-3, not Lumo's --lumo-contrast-50pct. */
:is(vaadin-checkbox, vaadin-radio-button):not([checked]):not([indeterminate]):not([disabled]):not([readonly]) {
  --vaadin-checkbox-background: var(--surface);
  --vaadin-checkbox-background-hover: var(--bg-sunk);
  --vaadin-radio-button-background: var(--surface);
  --vaadin-radio-button-background-hover: var(--bg-sunk);
  --vaadin-input-field-border-width: 1px;
  --vaadin-input-field-border-color: var(--line-control);
}
vaadin-grid-tree-toggle::part(toggle) { color: var(--ink-3); }
/* Filter chips and status menus: outlined, with hover and a focus ring. */
.chip:where(.chip-click, [role="button"]):not(.on) {
  background: var(--surface);
  border-color: var(--line-control);
  color: var(--ink);
}
.chip:where(.chip-click, [role="button"]):not(.on):hover { background: var(--bg-sunk); border-color: var(--ink-3); }
.chip:where(.chip-click, [role="button"]):focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
/* KPIs are flat, sunk readouts. On Who to invite and Send each is also the way into what it counts
   (.iv-kpi--jump, 30.9.2026): an edge on hover, the focus ring, and the accent while it is the list shown. */
.card.iv-kpi--readout { background: var(--bg-sunk); border-color: transparent; box-shadow: none; }
.card.iv-kpi--jump { cursor: pointer; transition: background-color .12s ease, border-color .12s ease; }
.card.iv-kpi--jump:hover { background: var(--surface); border-color: var(--line-control); }
.card.iv-kpi--jump:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.card.iv-kpi--jump.on { background: var(--accent-soft); border-color: var(--accent-line); }
.is-section, .is-section-empty { scroll-margin-top: var(--s-4); }
@media (prefers-reduced-motion: reduce) { .card.iv-kpi--jump { transition: none; } }

@media (max-width: 640px) {
  .is-status { padding: var(--s-3); }
  .is-kpis { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .iv-mail-card { padding: var(--s-3); }
  .iv-mail-label { min-width: 0; width: 100%; }
  .iv-mail-add { max-width: none; }
  .iv-mail-actions vaadin-button { flex: 1 1 auto; }
  .is-section-actions vaadin-button { width: 100%; }
  .is-settings-placeholder, .is-settings-url { width: 100%; flex-basis: 100%; }
}

/* /marketing/invitations/pipeline - everyone on a list in the stage they stand in
   (InvitePipelineView). The ipl- prefix; rows, heads and chips are the sorter's ip- pieces, the
   status card, stage heading and mail cards the send screen's is-/iv-mail- ones. The list is a flat
   FLIP root: heads, rows and an opened mail's card are siblings, each with its own data-flip-key,
   so nothing moves twice. */
/* The stages as one row of tabs with their counts (D-012; it replaced eight count tiles that
   pushed the first mail below the fold). A tab is lit with the accent while its stage is shown, as
   the tiles were; a stage at zero is dimmed and still pressable; an amber dot marks a stage where
   something waits on someone (D-004). Each tab is a control, so it carries the --line-control edge
   and the clickable chip's hover (runo-design.md, "Controls vs. content"). Hairlines set off Not
   inviting and the Guests link, which leaves the page (arrow). The row wraps rather than scrolls,
   so nothing is clipped on a phone. */
.ipl-stages { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-1); width: 100%; }
.ipl-tab {
  display: inline-flex; align-items: baseline; gap: var(--s-2); box-sizing: border-box;
  padding: 6px 12px; border: 1px solid var(--line-control); border-radius: var(--r-2);
  background: var(--surface); color: var(--ink-2); font-size: 13.5px; line-height: 1.3;
  text-decoration: none; cursor: pointer; user-select: none; white-space: nowrap;
  transition: background-color .12s ease, border-color .12s ease;
}
.ipl-tab:hover { background: var(--bg-sunk); border-color: var(--ink-3); color: var(--ink); text-decoration: none; }
.ipl-tab:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.ipl-tab-count { font-weight: 600; color: var(--ink); }
.ipl-tab.is-zero, .ipl-tab.is-zero .ipl-tab-count { color: var(--ink-4); font-weight: 400; }
.ipl-tab.on, .ipl-tab.on .ipl-tab-count { color: var(--accent-ink); font-weight: 600; }
.ipl-tab.on { background: var(--accent-soft); border-color: var(--accent-line); }
.ipl-tab-flag { align-self: center; width: 7px; height: 7px; border-radius: 50%; background: var(--signal-amber); }
.ipl-tab-away { align-self: center; width: 12px; height: 12px; color: var(--ink-3); }
.ipl-tabs-sep { width: 1px; align-self: stretch; margin: 2px var(--s-1); background: var(--line); }
@media (prefers-reduced-motion: reduce) { .ipl-tab { transition: none; } }
/* The shown stage's small print, on its heading; it drops under the title where there is no room. */
.ipl-stage .is-section-top { flex-wrap: wrap; }
.ipl-stage-meta { color: var(--ink-3); font-size: 13px; }
/* One toolbar: the search, the group chips, then Add someone and Lists. */
.ipl-toolbar .ip-search { flex: 0 1 340px; width: auto; min-width: 200px; }
/* A pressed group chip takes the shown stage tab's accent look, never the filled ink of the stage's
   one primary (D-009). Scoped here: the global .chip.on (goods-type strip) stays as it is. */
.ipl-toolbar .chip.on { background: var(--accent-soft); border-color: var(--accent-line); color: var(--accent-ink); }
.ipl-toolbar .chip.on:hover { border-color: var(--accent); }
/* Guests filter chips: a pressed one is outlined in ink with a tick, never filled (D-009, D-019). */
.iv-toolbar .chip.on { background: var(--surface); border-color: var(--ink); color: var(--ink); font-weight: 600; }
.iv-toolbar .chip.on::before { content: "\2713\00a0"; }
/* The sender card folded to one line (D-012); the toggle opens the full card under it. */
.is-status.is-open .is-sender-line { padding-bottom: var(--s-2); border-bottom: 1px solid var(--line-soft); }
.is-status vaadin-button.is-sender-toggle { color: var(--ink-2); }
.ipl-aside { display: flex; flex-wrap: wrap; gap: var(--s-2); width: 100%; margin-top: calc(-1 * var(--s-2)); }
.ipl-aside .chip { font-size: 12.5px; padding: 4px 10px; }
.ipl-stage { margin-top: var(--s-2); }
/* One mail = one band: the head stands out from the card and the people under it. */
.ipl-list > .ipl-head {
  align-items: center; margin-top: var(--s-5); padding: var(--s-2) var(--s-3);
  background: var(--bg-sunk); border: 1px solid var(--line); border-left: 3px solid var(--ink-3); border-radius: var(--r-2);
}
.ipl-list > .ipl-head:first-child { margin-top: var(--s-2); }
.ipl-head .ip-company-name { font-weight: 600; }
/* Approved a moment ago: stays in its place under To approve, stepped back. */
.ipl-list > .is-approved-here { opacity: 0.5; }
.ipl-head .ip-company-actions { align-self: center; }
.ipl-head vaadin-button.ipl-toggle { color: var(--ink-2); }
/* First contact on a head in review: a chip that switches (on) or a quiet offer (off, open mails only). */
.ipl-head vaadin-button.ip-first-contact {
  min-height: 0; height: auto; margin: 0; padding: 2px 6px 2px 8px; border-radius: 999px;
  font-size: 11.5px; color: var(--ink-2); border: 1px solid var(--line-soft); background: var(--bg-elev);
}
.ipl-head vaadin-button.ip-first-contact.is-off { border-style: dashed; background: transparent; color: var(--ink-3); }
.ipl-list > .iv-mail-card { margin-left: var(--s-4); }
/* The notes of a mail's people, between its head and its open card. */
.ipl-list > .ipl-notes { display: flex; flex-direction: column; gap: var(--s-1); margin-left: var(--s-4); padding: 0 var(--s-3); }
.ipl-address { color: var(--ink-3); }
/* Scheduled sending under "Send all": its day lines, its buttons, and the four fields of the dialog. */
.ipl-schedule { display: flex; flex-direction: column; gap: var(--s-1); margin-top: var(--s-3); }
.ipl-schedule-fields { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s-2) var(--s-3); margin-bottom: var(--s-3); }
.ipl-elsewhere { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-2); }

@media (max-width: 640px) {
  .ipl-tab { padding: 5px 10px; font-size: 13px; }
  .ipl-tabs-sep { display: none; }
  .ipl-toolbar .ip-search { flex-basis: 100%; }
  .ipl-list > .iv-mail-card { margin-left: 0; }
  .ipl-list > .ipl-notes { margin-left: 0; }
}

/* ============================================================================
   Partner check (erp/partners) — the company register, slice 1. Two card
   sections on one page; tokens only.
   ============================================================================ */
.pc-page    { gap: var(--s-4); }
.pc-head    { display: flex; flex-direction: column; gap: var(--s-1); }
.pc-actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-2); margin-top: var(--s-2); }
.pc-probe   { display: flex; flex-direction: column; gap: var(--s-1); font-size: 12px; }
.pc-section { display: flex; flex-direction: column; gap: var(--s-3); padding: var(--s-5); width: 100%; box-sizing: border-box; }
.pc-section h2 { margin: 0; }
.pc-toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-3); }
.pc-grid    { height: 60vh; }
.pc-flags   { display: flex; flex-wrap: wrap; gap: var(--s-1); }
/* Partner check — a partner's filed financial statements, opened under its row (slice 1c). */
.pc-statements      { display: flex; flex-direction: column; gap: var(--s-2); padding: var(--s-3) var(--s-4); }
.pc-statements-grid { max-width: 1040px; }
.pc-statements-links { display: flex; gap: var(--s-3); font-size: 12px; }

/* ============================================================================
   Sign-in page (LoginView) — one card, two doors.
   ----------------------------------------------------------------------------
   Kroslak team      the front door: the screen's one primary (ink) button.
   External partners the side door: under a hairline, a line saying which account to
                     use, soft grey buttons.
   Told apart by weight, not by colour or ornament. Tokens only.
   Black and white: the ink button, soft grey buttons, and provider marks drawn in the
   button's own text colour (currentColor) - no brand colours (Samo, 2026-10-13).
   Hover and focus live here because the buttons are plain anchors: a server-side
   hover listener once swallowed the first click (see LoginView.oauthButton).
   ============================================================================ */
.login-card {
  display: flex;
  flex-direction: column;
  flex-shrink: 0;                  /* the page is a full-height column: in a short window it scrolls, the card is never squeezed */
  box-sizing: border-box;
  width: calc(100% - var(--s-6) * 2);
  max-width: 420px;
  margin: var(--s-4) auto var(--s-5);
  border-radius: var(--r-5);       /* a soft, rounded sheet; the partner zone below follows it */
  box-shadow: var(--shadow-2);
  /* No overflow: hidden here. On a flex item it drops the automatic minimum height, the card
     shrinks to nothing in a short window and the sign-in buttons cannot be reached. */
}
.login-zone {
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
  padding: var(--s-6);
}
.login-zone > .eyebrow { text-align: center; color: var(--ink-2); }
.login-zone--partners {
  gap: var(--s-2);
  padding-top: var(--s-5);
  border-top: 1px solid var(--line-soft);   /* the same sheet, one hairline lower: the grey buttons say "second door" */
}
.login-zone-hint {
  margin: 0 0 var(--s-1);
  font-size: 13px;
  line-height: 1.5;
  text-align: center;
  text-wrap: balance;              /* two even lines, never one word left over */
  color: var(--ink-2);
}

.login-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-3);
  box-sizing: border-box;
  width: 100%;
  padding: var(--s-3) var(--s-4);
  border: 1px solid transparent;
  border-radius: var(--r-4);
  font-size: 15px;
  font-weight: 600;
  line-height: 20px;
  cursor: pointer;
}
.login-btn,
.login-btn:hover,
.login-btn:visited { text-decoration: none; }
.login-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.login-btn-logo { display: inline-flex; align-items: center; }
.login-btn-logo svg { display: block; }   /* fill is currentColor in the markup: the mark takes the label's colour */

/* Primary — ink fill. The hover steps toward the page, so it shows in dark mode too. */
.login-btn--primary,
.login-btn--primary:hover,
.login-btn--primary:visited { color: var(--bg); }
.login-btn--primary {
  min-height: 52px;
  background: var(--ink);
  box-shadow: var(--shadow-1);
}
.login-btn--primary:hover { background: color-mix(in oklch, var(--ink) 88%, var(--bg)); }

/* Secondary — a soft grey fill and a grey label, no edge: the quiet sibling of the ink button
   (Samo, 2026-10-13: "the same style" as the Google button on his reference). */
.login-btn--secondary,
.login-btn--secondary:hover,
.login-btn--secondary:visited { color: var(--ink-2); }
.login-btn--secondary {
  min-height: 48px;
  background: var(--bg-sunk);
  font-weight: 500;
}
.login-btn--secondary:hover { background: color-mix(in oklch, var(--bg-sunk) 90%, var(--ink)); }

/* /marketing/invitations/after - who to follow up with around the event (InviteAfterView). The ia-
   prefix; tiles, rows and chips are the pipeline's pieces. Two groups, so two tiles side by side. */
.iv-kpis.ia-kpis { grid-template-columns: repeat(2, minmax(0, 320px)); }
.ia-brief { display: flex; flex-direction: column; gap: var(--s-2); padding: var(--s-4); width: 100%; box-sizing: border-box; }
.ia-brief-text { min-height: 120px; }
.ia-draft { display: flex; flex-direction: column; gap: var(--s-2); padding-top: var(--s-2); border-top: 1px solid var(--line-soft); }
.ia-suggestion { font-size: 13.5px; color: var(--ink); line-height: 1.5; white-space: pre-line; }
.ia-body { min-height: 180px; }
@media (max-width: 700px) {
  .iv-kpis.ia-kpis { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* ============================================================================
   UI kit (ui/kit/) — PageHeader, KpiStrip, StatusChip, EmptyState.
   The parts every screen is built from; the catalogue in runo CLAUDE.md says
   which to reach for. Tokens only, no hex, no inline style.
   ============================================================================ */
/* PageHeader — eyebrow + serif title + one purpose line, one primary action right. */
.kit-page-header {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: var(--s-4); width: 100%; box-sizing: border-box;
}
.kit-page-header-text { display: flex; flex-direction: column; gap: var(--s-1); min-width: 0; }
.kit-page-header-purpose { margin: 0; }
.kit-page-header-action { flex-shrink: 0; }

/* KpiStrip — Vaadin Card tiles, a large monospaced number each; a tile may be a filter toggle. */
.kit-kpi-strip {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
  gap: var(--s-3); width: 100%;
}
.kit-kpi { min-width: 0; gap: var(--s-1); }
/* Card's default slot lays its children inline; a tile reads top-down: label, number, note. */
.kit-kpi > span { display: block; }
.kit-kpi-value { font-size: 30px; line-height: 1.1; color: var(--ink); }
.kit-kpi-meta  { color: var(--ink-3); font-size: 12.5px; line-height: 1.4; }
/* Kpi(tone = …): only the number takes the tone's colour (D-004). Amber is mixed toward ink so a
   large figure stays readable on paper as well as in dark mode — the pairing the Tasks board
   already used for amber text. */
.kit-kpi--amber  .kit-kpi-value { color: color-mix(in oklch, var(--signal-amber) 60%, var(--ink)); }
.kit-kpi--red    .kit-kpi-value { color: var(--signal-red); }
.kit-kpi--green  .kit-kpi-value { color: var(--signal-green); }
.kit-kpi--accent .kit-kpi-value { color: var(--accent-ink); }
/* Kpi(onClick = …): the tile is a toggle button that filters. Pressed = outlined in the accent
   with a tick, never filled like a primary button (D-009). Focus ring only for the keyboard. */
.kit-kpi--pressable { cursor: pointer; user-select: none; transition: border-color 80ms ease, box-shadow 80ms ease; }
.kit-kpi--pressable:hover { border-color: var(--line); }
.kit-kpi--pressable:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.kit-kpi--selected,
.kit-kpi--selected:hover { border-color: var(--accent); box-shadow: var(--shadow-1), inset 0 0 0 1px var(--accent); }
.kit-kpi--selected > .eyebrow { color: var(--accent-ink); padding-right: var(--s-5); }  /* clear of the tick */
.kit-kpi-tick { position: absolute; top: var(--s-2); right: var(--s-2); width: 16px; height: 16px; color: var(--accent); }
@media (prefers-reduced-motion: reduce) { .kit-kpi--pressable { transition: none; } }

/* StatusChip — the .chip pill; tone = .amber / .red / .green / .accent / plain grey. */
.kit-status { white-space: nowrap; }

/* EmptyState — two kinds of empty that must never look alike. */
.kit-empty {
  display: flex; flex-direction: column; align-items: center; text-align: center;
  gap: var(--s-2); padding: var(--s-7) var(--s-5);
  border: 1px dashed var(--line); border-radius: var(--r-3);
  width: 100%; box-sizing: border-box;
}
.kit-empty--all-clear { border-style: solid; border-color: var(--line-soft); background: var(--signal-green-soft); }
.kit-empty-tick  { color: var(--signal-green); width: 32px; height: 32px; }
.kit-empty-title { font-family: var(--font-serif); font-size: 18px; color: var(--ink); }
.kit-empty-text  { margin: 0; color: var(--ink-3); font-size: 13px; max-width: 46ch; }
.kit-empty-action { margin-top: var(--s-2); }

@media (max-width: 640px) {
  .kit-page-header { flex-direction: column; }
  .kit-kpi-strip { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s-2); }
  .kit-kpi-value { font-size: 24px; }
}

/* ============================================================================
   /erp/zones/cut-check — the two tabs, the Elements tab (CutPairingRegisterPanel,
   prefix cpr-) and the dialog of one element (CutPairingDialog, prefix cpd-).
   Tokens only. Nothing is nested under the view: a dialog's body is teleported
   out of it by vaadin-dialog-overlay, so the prefix is the scope.
   ============================================================================ */
/* The tabs under the page header, the way back to Material zones at their end. */
.cut-tabs { display: flex; align-items: center; gap: var(--s-3); width: 100%; }
.cut-tabs-switch { flex: 1; min-width: 0; }

/* The row of controls: model, text, "only where a file is missing", then the one primary button. */
.cpr-kpis { width: 100%; }
.cpr-controls { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-3); width: 100%; }
.cpr-model  { width: 260px; }
.cpr-search { width: 300px; }

/* The list. Eight columns at 1440 px: tighter side padding than the other tables, cells that
   wrap instead of cutting a file name short, and rows that read from their top line. */
vaadin-grid.cpr-grid { --vaadin-grid-cell-padding: var(--s-1) var(--s-2); }
vaadin-grid.cpr-grid::part(body-cell) { align-items: flex-start; }
.cpr-code { font-size: 12.5px; overflow-wrap: anywhere; }
.cpr-stack { display: flex; flex-direction: column; align-items: flex-start; gap: var(--s-1); min-width: 0; }
/* One pattern row per line; on a narrow window the files for two fabrics wrap under it. */
.cpr-pattern { display: flex; flex-direction: column; gap: var(--s-1); width: 100%; min-width: 0; }
.cpr-row { display: flex; flex-wrap: wrap; align-items: baseline; column-gap: var(--s-2); min-width: 0; }
.cpr-row + .cpr-row { border-top: 1px solid var(--line-soft); padding-top: var(--s-1); }
.cpr-row-when { color: var(--ink-2); }
/* Mirrored is the value being checked: it stands out by weight, "as drawn" stays quiet. */
.cpr-mirrored { font-weight: 600; color: var(--ink); }
.cpr-fabric { display: inline-flex; flex-wrap: wrap; align-items: baseline; column-gap: var(--s-1); min-width: 0; }
.cpr-fabric .mono, .cpr-row > .mono { overflow-wrap: anywhere; }
.cpr-missing { font-size: 12px; color: var(--signal-red); overflow-wrap: anywhere; }

/* The dialog: the facts the proposal rests on, then the pattern a zone editor may change. */
.cpd { display: flex; flex-direction: column; gap: var(--s-4); width: 100%; }
.cpd-facts { display: grid; grid-template-columns: 150px 1fr; align-items: baseline; gap: var(--s-2) var(--s-3); }
.cpd-dt { font-size: 11px; font-weight: 500; letter-spacing: 0.08em; text-transform: uppercase; color: var(--ink-4); }
.cpd-dd { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-2); min-width: 0; font-size: 13.5px; color: var(--ink); }
.cpd-block { display: flex; flex-direction: column; gap: var(--s-2); padding: var(--s-3) var(--s-4); border-radius: var(--r-2); background: var(--bg-sunk); }
.cpd-block > .page-sub { margin: 0; }
.cpd-candidate { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--s-3); }
.cpd-candidate-text { font-size: 12.5px; overflow-wrap: anywhere; min-width: 0; }
.cpd-chips { display: flex; flex-wrap: wrap; gap: var(--s-2); }
.cpd-form { display: flex; flex-direction: column; gap: var(--s-3); }
.cpd-rows { display: flex; flex-direction: column; gap: var(--s-2); }
/* One pattern row: what stands beside the element, three files, mirrored, remove. The heading
   line above the rows is the same grid, so each title sits over its own column. The two narrow
   columns at the end have a fixed width for that reason: the heading line has nothing in them,
   and with "auto" its titles slid to the right of the fields they name. */
.cpd-row {
  display: grid; align-items: start; gap: var(--s-2);
  grid-template-columns: minmax(200px, 1.5fr) repeat(3, minmax(0, 1fr)) 120px 36px;
}
/* A file name is in the fixed-width face; the label over its field is not. */
.cpd-form vaadin-text-field.mono::part(label) { font-family: var(--font-sans); font-feature-settings: normal; }
.cpd-row > vaadin-text-field, .cpd-row > vaadin-select { width: 100%; min-width: 0; }
.cpd-row > vaadin-checkbox, .cpd-row > vaadin-button { align-self: center; }
.cpd-row-head { align-items: end; font-size: 12px; color: var(--ink-3); }
.cpd-library { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-2); font-size: 13px; color: var(--ink-2); }
/* "Back to check" sits apart from Cancel and Save, at the other end of the footer. */
.cpd-reopen { margin-inline-end: auto; }

@media (max-width: 700px) {
  .cpr-model, .cpr-search { width: 100%; }
  .cpd-facts { grid-template-columns: 1fr; gap: var(--s-1); }
  .cpd-row { grid-template-columns: 1fr 1fr; }
  .cpd-row-head { display: none; }
}

/* The buyer an order's file names, on the order (V207): the name, the dealer list's pairing, one button. */
.order-buyer { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--s-2); width: 100%; }
.order-buyer .kit-status-label { white-space: normal; overflow-wrap: anywhere; }
/* The "which company is this dealer" dialog: a few blocks under each other. */
.order-buyer-dialog { display: flex; flex-direction: column; gap: var(--s-3); width: 100%; }

/* The element-code drawing (ElementCodeLegend): Juraj Škoda's picture of the codes, shown where
   the codes are. Never wider than it was drawn, never wider than the place it stands in. */
.code-legend { display: flex; flex-direction: column; gap: var(--s-2); width: 100%; max-width: 1084px; }
.code-legend-img { display: block; width: 100%; height: auto; border: 1px solid var(--line-soft); border-radius: var(--r-2); }
.code-legend .page-sub { margin: 0; }
.code-legend-panel { width: 100%; }
/* In a column that stretches its children the button keeps its own width. */
.code-legend-button { align-self: flex-start; }
