/* AFO3 — visual layer.
 *
 * Colours, proportions and layout come from the legacy skin
 * (AfoFaces/src/main/webapp/css/focus/{app,shared}.css and the header sprite
 * GIFs, sampled pixel-wise); the implementation is modern CSS — no sprite
 * images, no 9-slice tables, no em-cascade off `body { font-size:.7em }`.
 *
 * Legacy origins are noted per block as `app.css:<line>` / `shared.css:<line>`.
 */

:root {
  /* --- palette (legacy) --- */
  --teal: #007D8F;              /* grid header, action links  app.css:2084, 2524 */
  --teal-dark: #005966;         /* command links              app.css:15         */
  --teal-soft: #4F97A3;         /* nav icons (goto_*_active.png)                 */
  --page-bg: #D0DDE1;           /* body                       app.css:4          */
  --panel: #ffffff;
  --header-bg: #FEFEFE;         /* header-center.gif                             */
  --header-edge: #9AA4A7;       /* header-center.gif bottom row                  */
  --nav-active-bg: #E4EDEF;     /* header-mainnaviback_selected.gif              */
  --nav-hover-bg: #F1F4F5;
  --slate: #3F4E56;             /* active nav/tab label       app.css:498        */
  --muted: #848889;             /* inactive nav label         app.css:467        */
  --border: #B6C8CD;            /* grid borders               app.css:2092       */
  --border-soft: #C1C9CC;       /* divider.gif                                   */
  --row-1: #D0E0E6;             /* grid row 1                 app.css:2181       */
  --row-2: #E4ECEF;             /* grid row 2                 app.css:2185       */
  --row-hover: #C2D6DE;
  --block-bg: #d0dde2;          /* search-module block        app.css:3841       */
  --error: #b70000;             /* .errorMessage              shared.css:275     */
  --error-bg: #fdf3f3;
  --info: #4992A7;              /* .infoMessage               shared.css:284     */
  --info-bg: #f1f7f9;
  --text: #23313a;

  /* --- modern additions (the legacy CSS has no scales at all) --- */
  --sp-1: 4px;  --sp-2: 8px;  --sp-3: 12px;  --sp-4: 16px;  --sp-5: 24px;
  /* Width of the leaflet-detail column, and the offset the article column sticks
     at. The two MUST agree, which is why the column's width is enforced rather
     than suggested (see the rules near `.leaflet-cell`) and why
     `verify_assets.py` binds them. Sized for the 13px icon + 14px cell padding +
     1px border + a 3-digit `(n)`; it was 42px, which the aggregated cell overran. */
  /* The search form's field column: two `--col-max` tracks and the gap between
     them, i.e. exactly what `.field-grid.cols-2` can grow to. The form layout
     caps its first grid track at this so the values panel sits right after the
     fields (D30). Keep in step with `--col-max` in `.field-grid`. */
  --form-col: 684px;
  --leaflet-col-width: 58px;
  --r-sm: 3px;  --r-md: 6px;  --r-lg: 10px;
  --shadow-sm: 0 1px 2px rgba(31, 58, 66, .10);
  --shadow-md: 0 2px 10px rgba(31, 58, 66, .14);
  --ring: 0 0 0 2px #fff, 0 0 0 4px var(--teal-soft);
  --chrome-font: "Trebuchet MS", "Lucida Grande", Calibri, Arial, Helvetica, sans-serif;
  --body-font: system-ui, "Segoe UI", Roboto, Arial, sans-serif;
  --label-gutter: 135px;        /* .focusLabel width          app.css:4836       */
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--page-bg);
  color: var(--text);
  font: 13px/1.45 var(--body-font);
  display: flex;
  flex-direction: column;
  min-height: 100vh;
}

a { color: var(--teal-dark); }

:focus-visible { outline: none; box-shadow: var(--ring); border-radius: var(--r-sm); }

@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; animation: none !important; }
}

/* ===========================================================================
   Header band — legacy .headerTable (88px, header-center.gif = near-white with
   a light bottom edge), .mainNavi icon+label items, .sideNaviLinkLabel strip.
   app.css:339-560 / main.xhtml:80-308
   =========================================================================== */
.topbar {
  display: flex;
  align-items: stretch;
  gap: var(--sp-4);
  height: 64px;
  padding: 0 var(--sp-4);
  background: var(--header-bg);
  border-top: 3px solid var(--teal);
  border-bottom: 1px solid var(--header-edge);
  box-shadow: var(--shadow-sm);
  position: sticky;
  top: 0;
  z-index: 20;
}

.brand {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  text-decoration: none;
  padding-right: var(--sp-4);
  border-right: 1px solid var(--border-soft);
}
.brand-logo { height: 26px; width: auto; display: block; }
.brand-app {
  font: 700 17px/1 var(--chrome-font);
  letter-spacing: 1px;
  color: var(--slate);
  text-transform: uppercase;
}
.brand-ver { color: var(--teal); }

/* main navigation: icon over label, uppercase, letter-spaced (app.css:487-513) */
.mainnav { display: flex; align-items: stretch; gap: 2px; flex: 1; }
.mainnav-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
  min-width: 78px;
  padding: 0 12px;
  text-decoration: none;
  color: var(--muted);
  border-bottom: 2px solid transparent;
  transition: background-color .12s ease, color .12s ease;
}
.mainnav-item:hover { background: var(--nav-hover-bg); color: var(--slate); }
.mainnav-item.is-active {
  background: var(--nav-active-bg);
  color: var(--slate);
  border-bottom-color: var(--teal);
}
.mainnav-item.is-active .nav-icon { color: var(--teal); }
.nav-icon { width: 21px; height: 21px; color: #A9B6B9; transition: color .12s ease; }
.mainnav-item:hover .nav-icon { color: var(--teal-soft); }
.mainnav-label {
  font: 700 10px/1 var(--chrome-font);
  letter-spacing: 1px;
  text-transform: uppercase;
  white-space: nowrap;
}

/* utility strip (main.xhtml:258-308) — .sideNaviLinkLabel app.css:462-478 */
.utility { display: flex; align-items: center; gap: var(--sp-3); }
/* Sign out is a <button> in a form (a GET link would let any page sign the user
   out), settings is still an <a> — the reset makes the two indistinguishable. */
.utility-logout { display: inline-flex; margin: 0; }
button.utility-link { background: none; border: 0; padding: 0; cursor: pointer; }
.utility-link {
  font: 700 10px/1 var(--chrome-font);
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--muted);
  text-decoration: none;
  white-space: nowrap;
}
.utility-link:hover { color: var(--teal-dark); }
.utility-user {
  font: 700 11px/1 var(--chrome-font);
  color: var(--slate);
  white-space: nowrap;
}
/* Switch user (docs/ADMIN_SUDO.md): the staff's "Logged in as" link keeps
   the user's case; while switched it is amber, the colour this app gives what
   must not be overlooked, so nobody forgets whose account they act in. */
button.utility-user, button.sudo-active { text-transform: none; letter-spacing: 0; }
button.sudo-active { color: #b45309; font-size: 11px; }
/* legacy's `.sudoMode`: a red 1px frame round the page while switched */
body.sudo-mode { outline: 1px solid red; outline-offset: -1px; }
/* stays amber under the pointer: `.utility-link:hover` would turn it teal */
button.sudo-active:hover { color: #92400e; }
/* the Admin tab (admin.html, legacy incAdmin.xhtml): legacy's one row of
   controls, then three read-only boxes of plain text */
.admin-values { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-3); margin-bottom: var(--sp-4); }
.admin-field { display: inline-flex; align-items: center; gap: var(--sp-1); white-space: nowrap; }
.admin-field select { width: auto; }
.admin-block { margin-bottom: var(--sp-4); }
.admin-block textarea { display: block; width: 100%; max-width: 760px; font: 12px/1.4 ui-monospace, Consolas, monospace; }
.admin-block .form-actions { display: flex; justify-content: flex-start; align-items: center; gap: var(--sp-2); max-width: 760px; margin-top: var(--sp-2); }
.admin-block .form-actions form { margin: 0; }
.admin-caption { margin: 0 0 var(--sp-1); color: var(--slate); font-size: 12px; }
.sudo-panel { margin: var(--sp-2) var(--sp-4); max-width: 520px; }
.sudo-panel .sudo-filter { display: flex; gap: var(--sp-3); flex-wrap: wrap; margin-bottom: var(--sp-2); }
.sudo-panel .sudo-error { color: #b91c1c; margin: 0 0 var(--sp-2); }
.sudo-panel .pager { display: flex; gap: var(--sp-2); align-items: center; margin: var(--sp-1) 0; }
.scope { display: flex; align-items: center; gap: var(--sp-1); margin: 0; }
.scope select {
  font: 11px/1 var(--chrome-font);
  color: var(--slate);
  background: #fff;
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  padding: 3px 4px;
  width: auto;
}
.flag {
  width: 16px; height: 11px; display: block;
  border: 1px solid var(--border-soft);
}

/* ===========================================================================
   Second-level tabs — legacy contentPTS (app.css:772-800, tabs-a/*.gif)
   =========================================================================== */
.subtabs {
  display: flex;
  gap: 2px;
  padding: var(--sp-2) var(--sp-4) 0;
  overflow-x: auto;
}
.subtab {
  font: 700 11px/1 var(--chrome-font);
  letter-spacing: .5px;
  text-decoration: none;
  color: #72898C;                     /* inactive tab label  app.css:789 */
  background: var(--row-2);
  border: 1px solid var(--border);
  border-bottom: none;
  border-radius: var(--r-md) var(--r-md) 0 0;
  padding: 7px 12px 8px;
  white-space: nowrap;
  transition: background-color .12s ease, color .12s ease;
}
.subtab:hover { background: #EEF4F6; color: var(--slate); }
.subtab.is-active {
  background: var(--panel);
  color: var(--slate);
  box-shadow: inset 0 2px 0 var(--teal);
  padding-bottom: 9px;
}
.subtab.is-todo:not(.is-active) { opacity: .72; }

/* ===========================================================================
   Content + panels — legacy panelTabContentTable (9-slice rounded frame)
   =========================================================================== */
.content { flex: 1; padding: var(--sp-3) var(--sp-4) var(--sp-5); }
.subtabs + .content { padding-top: 0; }

h1 { font: 700 17px/1.2 var(--chrome-font); color: var(--slate); margin: 0 0 var(--sp-3); }

.panel {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-sm);
  margin-bottom: var(--sp-3);
}
/* collapsible panel header — teal, uppercase (app.css:2084-2101) */
.panel-head {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  background: var(--teal);
  color: #fff;
  font: 700 11px/1 var(--chrome-font);
  letter-spacing: 1px;
  text-transform: uppercase;
  padding: 8px var(--sp-3);
  border-radius: var(--r-md) var(--r-md) 0 0;
}
.panel-head .panel-hint {
  font-weight: 400;
  letter-spacing: .3px;
  text-transform: none;
  opacity: .85;
}
/* legacy switches the hint with the form's state — `(click to collapse)` on
   an open form, `(click to expand)` on a collapsed one (searchFormHeaderText-
   Collapse / -Expand, incPriceSearch.xhtml:51-59); both are in the markup and
   the `<details>` state shows one (2026-10-25) */
details[open] > .panel-head > .panel-hint-expand,
details:not([open]) > .panel-head > .panel-hint-collapse { display: none; }
.panel-head button.panel-toggle {
  all: unset;
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  flex: 1;
  color: inherit;
  font: inherit;
  letter-spacing: inherit;
}
.panel-body { padding: var(--sp-3); }

/* messages — shared.css:275-300. `#messages` is always in the DOM (it is an
   htmx swap target), so the gap below belongs to the last message, not to the
   container — an empty region must take no space. */
.messages { margin: 0; }
.messages .message:last-child { margin-bottom: var(--sp-3); }
.message {
  margin: 0 0 var(--sp-2);
  padding: 8px 10px;
  border-radius: var(--r-sm);
  border: 1px solid transparent;
}
.message-error { color: var(--error); background: var(--error-bg); border-color: #e6b8b8; }
.message-info  { color: var(--info);  background: var(--info-bg);  border-color: #bcd8e0; }

/* the pre-cutover pilot notice under the header (base.html): one slim centred
   line that scrolls away with the page. Deliberately OUTSIDE the teal/blue
   palette — in the info-message colours it vanished among the light-blue
   blocks around it (the user, 2026-09-11) — so it is the one warm element on
   the screen: amber ground, dark amber text, a shade bolder and larger. */
.pilot-notice {
  padding: 6px var(--sp-4);
  text-align: center;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: .01em;
  color: #6b3f00;
  background: #fff1cc;
  border-top: 1px solid #f0d27a;
  border-bottom: 1px solid #e3bd55;
}

/* Legacy's broadcast line (.broadcastMessage, app.css:327: red, bold, 2em in)
   — an administrator's notice, so it reads as an alert, not as chrome. */
.broadcast-message {
  padding: 6px var(--sp-4) 6px 2em;
  font-size: 13px;
  font-weight: 700;
  color: var(--error);
  background: var(--error-bg);
  border-bottom: 1px solid #e6b8b8;
}

/* ===========================================================================
   Forms — legacy .focusLabel (label left, 135px gutter) app.css:4836,
   .mediumWidthInput 180px shared.css:360-418
   =========================================================================== */
/* Legacy lays the fields out in a fixed `panelGrid columns="N"` — an HTML table,
   so its arrangement never reflows with the window. This grid used to be
   `repeat(auto-fit, minmax(330px, 1fr))`, which derived the column count from the
   viewport instead: the comfort block was 3+1 at 1366px but 4 at 1536px, and the
   feature block 3+3 / 4+2 / 5+1 — two users on different monitors saw different
   forms, and neither saw legacy's.

   The count is fixed per block (D11) and, since D21, it is **two** everywhere
   but the distribution block: legacy renders two label+input pairs per visual
   row in every field block (`columns="4"` = 2 pairs — a `foc:*` control is two
   table cells, FORM_LAYOUT §1 fact 1). Every `.field-grid` carries a `cols-N`
   class naming its track count; a block with more `.field-col`s than tracks
   wraps them onto further rows. Only the column *width* flexes, capped at
   --col-max so an input cannot stretch far past legacy's ~225px cells
   (`.searchModuleBlockComfortCol2`, app.css:707) and the leftover width stays
   empty, as it does in legacy's table. Below the narrow breakpoint the blocks
   collapse to one column — see the override at the bottom of this file.
   See docs/FORM_LAYOUT.md D11 + D21. */
.field-grid {
  --cols: 2;
  --col-max: 330px;
  display: grid;
  grid-template-columns: repeat(var(--cols), minmax(0, 1fr));
  max-width: calc(var(--cols) * var(--col-max) + (var(--cols) - 1) * var(--sp-5));
  gap: var(--sp-1) var(--sp-5);
  /* items sit at the top of their row, whatever their neighbour's height —
     legacy's `.searchFormLayoutRow1 { vertical-align: top }` for cells too */
  align-items: start;
  /* dense so an item following a full-row `.field-span` backfills the gap left
     in the row above it (the agtree picker when the confirm strip renders
     between it and the level dropdown). Every other item is 1×1, for which
     dense and sparse place identically — DOM order is preserved. */
  grid-auto-flow: row dense;
}
/* one narrow column — legacy's distribution block is a `columns="2"` grid with a
   single cell in it (incPriceSearch.xhtml:1320) */
.field-grid.cols-1 { --cols: 1; }
/* explicit twin of the default, so the narrow-screen override below can list
   every cols-N and win on plain specificity (no `:where()`) */
.field-grid.cols-2 { --cols: 2; }
/* three pairs per row — the optimizers' `panelGrid columns="6"`
   (incProductCompare.xhtml:66), measured as three columns on production
   (docs/OPTIMIZERS.md §1.1, stage 0) */
.field-grid.cols-3 { --cols: 3; }
.field-col { min-width: 0; }

/* --- Row-major blocks (D9-basic, 2026-08-18) --------------------------------
   The basic block's fields are DIRECT grid items (no .field-col), so they flow
   left-to-right two pairs per row — the reading order of legacy's `panelGrid
   columns="4"` table (FORM_LAYOUT §3.1). The two level blocks keep a wrapper
   div (they are htmx swap targets, #agtree-level / #mbd-level) which dissolves
   here so the level dropdown and its per-level input land side by side. Scoped
   to DIRECT children of the grid: the same macros inside a `.field-col` or the
   Price compare tab's `.pc-form-col` keep stacking. A browser without
   `display: contents` degrades to the wrapper being one cell — the pre-D9
   stacked arrangement, everything still usable. */
.field-grid > .agtree-level,
.field-grid > .mbd-level,
/* Product compare's four article-dependent listboxes, one htmx swap target */
.field-grid > .pc-boxes { display: contents; }
/* a field spanning both tracks: the agtree confirm strip and the date-format
   hint are full rows beneath the pair they refer to */
.field-grid .field-span { grid-column: 1 / -1; }
/* legacy's empty label+input pair (`priceSearchPlaceHolder0`,
   incPriceSearch.xhtml:198) — holds the cell beside the article input so
   row 1 reads [article][nothing] */
.field-spacer { min-width: 0; }

/* The search form's two columns — legacy `panelGrid columns="3"
   .searchFormLayout` (incPriceSearch.xhtml:62, app.css:3364). Cell 1 stacks
   every field block (with the module toggles and the button row as its header /
   footer facets), cell 2 holds the grouping box, cell 3 is never filled. The
   cells are top-aligned (`.searchFormLayoutRow1 { vertical-align: top }`).

   Legacy's cells are fixed-width table cells, so at a narrow window the page
   simply scrolls sideways. Here the two columns wrap instead: the field blocks
   keep a 700px floor (two 330px tracks + the gap, since D21 narrowed the
   blocks) and the grouping box drops beneath them when that no longer fits
   (roughly below a 1300px viewport). Squeezing the field columns further
   would leave the 135px label gutter more room than its input.
   See docs/FORM_LAYOUT.md D1. */
.search-form-layout {
  display: grid;
  /* the field columns, then whatever the side column needs. Capping the first
     track at the field grid's own max width is what keeps the side column
     BESIDE the fields instead of out at the page's right edge: as a flex row it
     grew to fill, and the values panel ended up an inch of empty space away
     from the input whose values it lists (the user, 2026-09-14 — D30). */
  grid-template-columns: minmax(0, var(--form-col)) auto;
  align-items: start;
  gap: var(--sp-3) var(--sp-4);
}
/* the module toggles are the form's header facet — they belong to the whole
   form, not to its left cell, so they span both tracks */
.search-form-layout > .modules { grid-column: 1 / -1; }
.search-form-main { min-width: 0; }
/* a form whose field grid is `cols-3` (the optimizers) caps its first track at
   THAT grid's width — three `--col-max` tracks and two gaps — or the fields
   are squeezed into the two-column width (found in the browser 2026-09-24,
   the selects showed one letter) */
.search-form-layout.form-cols-3 { --form-col: calc(3 * 330px + 2 * var(--sp-5)); }
/* the article's value search lists long names; do not narrow it to the field */
#article-out .picker-panel { min-width: 320px; }
/* Right-aligned in its track, so the panel's right edge lands on the module
   bar's — the bar spans both tracks, so the two share an edge and the form gets
   a straight right margin. Beside the field columns (`start`, the first shape
   D30 took) it read as another column of the form rather than as something
   next to it (the user, 2026-09-14). */
.search-form-side { justify-self: end; }

/* The multi-search values panel — legacy `foc:multiSearchValues`, the right-hand
   column of the search form (incPriceSearch.xhtml:1917, FORM_LAYOUT §2.3). One
   `<details>` per input that holds values, header `LABEL (n/max)`.

   Every input has a section in the markup even when it is empty, because htmx can
   only swap into an element that already exists — so the EMPTY ones are hidden
   here rather than left out there, and the panel itself disappears when no section
   has content (`:has`, already used elsewhere in this file for the module blocks).
   No JavaScript: `<details>` is the collapsible, which is what legacy's
   `ice:panelCollapsible` gives it. */
.msv-panel {
  display: grid;
  gap: var(--sp-1);
  margin-top: var(--sp-2);   /* legacy has the panel BELOW the grouping box */
}
.msv-panel:not(:has(.msv-input > *)) { display: none; }
.msv-input:empty { display: none; }
/* The Price compare tab puts its panel beside the form, as Price Search does —
   the XHTML reads as if it were below (the tag follows the form's panelGrid),
   but the enclosing panelGrid is multi-column, so ICEfaces renders it as the next
   cell. Measured on the running legacy app 2026-08-09. */
.pc-form-body {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-5);
}
.pc-form-main { flex: 1; min-width: 0; }
.pc-form-body > .msv-panel { flex: 0 0 auto; margin-top: 0; }
@media (max-width: 900px) {
  /* below the form only when there is no room beside it */
  .pc-form-body { flex-wrap: wrap; }
  .pc-form-main { flex: 1 1 100%; }
}
.msv-block {
  background: var(--block-bg);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  padding: var(--sp-1) var(--sp-2);
  font-size: 11px;
  max-width: 220px;
}
.msv-block > summary {
  cursor: pointer;
  color: var(--slate);
  text-transform: uppercase;
  letter-spacing: .02em;
  font-size: 10px;
}
.msv-row {
  display: flex;
  align-items: baseline;
  gap: 5px;
  color: var(--slate);
  padding-top: 2px;
}
/* legacy puts `(Remove All)` first, above the values */
.msv-row-all { color: var(--muted); }
.msv-value { overflow-wrap: anywhere; }

/* Legacy's `cancelMultiSearchWarningPopup` (main.xhtml:584), which asks before a
   level switch throws a selection away. A strip under the dropdown rather than a
   modal: the clone has no modals, and the question belongs where the answer is. */
.confirm-strip {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 10px;
  font-size: 12px;
  color: var(--slate);
}

/* PASTE — legacy's second tab of the value-search popup. Legacy puts that popup
   behind one small icon in the field row (`inputText.xhtml:68`, add.gif on
   `.multiValueAddButton`) and shows nothing else on the form; this does the same,
   so the form stays legacy's and the panel costs no vertical space. The panel is
   an absolutely positioned dropdown hanging off the icon — a popup without a
   window to manage — and it opens itself when a paste left something
   unrecognised, which is where the user has to look. */
.msv-paste { position: relative; flex: none; display: flex; }
.msv-paste > summary {
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  color: var(--slate);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  background: #fff;
  list-style: none;           /* Firefox's disclosure triangle */
}
.msv-paste > summary::-webkit-details-marker { display: none; }
.msv-paste > summary:hover { background: var(--teal); color: #fff; }
.msv-paste[open] > summary { background: var(--teal); color: #fff; }
.paste-icon { width: 13px; height: 13px; display: block; }
/* The dropdown. Right-aligned to the icon because the icon sits at the right end
   of the field row. It stays above the suggestion list (`.picker-results`),
   although opening it closes that list now (app.js §13: a click outside an
   overlay closes it). The top padding is room for its × (`.picker-close`). */
.msv-paste-body {
  position: absolute;
  top: calc(100% + 4px);
  right: 0;
  z-index: 30;
  width: 260px;
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 8px;
  padding-top: 20px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  box-shadow: var(--shadow-md);
}
/* the paste outcome (`no_items_could_be_added`, `only_the_first_n…`) — legacy's
   popup banner, so it stays with the text it is about */
.msv-paste-msg { font-size: 11px; }
.msv-paste-delims {
  display: flex;
  flex-wrap: wrap;
  gap: 2px 10px;
  font-size: 11px;
  color: var(--slate);
}
.msv-paste-delims label { display: inline-flex; align-items: center; gap: 4px; }
.msv-paste-text {
  width: 100%;
  font-family: inherit;
  font-size: 12px;
  padding: 4px 6px;
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  resize: vertical;
}
.msv-paste-actions { display: flex; gap: 6px; }
.msv-paste-actions .btn { padding: 4px 9px; letter-spacing: .5px; }

/* Legacy `.groupingBox` (app.css:190): a bordered box in the right-hand column
   holding the aggregation dropdown with the ignore-promotion-info checkbox
   STACKED beneath it — its `panelGrid columns="2"` fits exactly one control's
   label+input pair per row (incPriceSearch.xhtml:1893, FORM_LAYOUT §2.3;
   corrected with D21 on 2026-08-04 — the pair sat side by side before). The
   checkbox carries its own label inside the chip, so only the first row needs
   the label gutter. See docs/FORM_LAYOUT.md D4. */
.grouping-box {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  align-items: center;
  gap: var(--sp-2) var(--sp-4);
  background: var(--block-bg);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  padding: var(--sp-2) var(--sp-3);
}
.grouping-box .field { margin-bottom: 0; }
.grouping-box select { max-width: 180px; }

/* One search module = one block of fields, like legacy's searchModuleBlock
   (incPriceSearch.xhtml:186) — rendered only when the user has the module. */
.module-block { margin-top: var(--sp-3); border-top: 1px solid var(--border); padding-top: var(--sp-2); }

/* A module detail block stays hidden until its top toggle chip is on — legacy
   renders each on `#{priceSearch.XModuleEnabled}` (incPriceSearch.xhtml). Driven
   by the chip ids `#mod-<module>`; pure CSS `:has()`, no JavaScript.

   Since D2 split distribution / validity / media into their own blocks, all
   seven modules go through this one rule — the region group used to need a
   second pair of rules revealing a column inside a shared block. */
.module-block[data-module] { display: none; }
.panel-body:has(#mod-comfort:checked)      .module-block[data-module="comfort"],
.panel-body:has(#mod-companybrand:checked) .module-block[data-module="companybrand"],
.panel-body:has(#mod-feature:checked)      .module-block[data-module="feature"],
.panel-body:has(#mod-custom_code:checked)  .module-block[data-module="custom_code"],
.panel-body:has(#mod-distribution:checked) .module-block[data-module="distribution"],
.panel-body:has(#mod-validity:checked)     .module-block[data-module="validity"],
.panel-body:has(#mod-media:checked)        .module-block[data-module="media"] { display: block; }

/* The gallery module swaps the footer's Rows per page for its own three
   controls (GALLERY.md stage 1) — same :has() mechanics as the block reveals
   above. Both sets stay in the DOM (a hidden control still submits, which is
   why the gallery selects have their own names); this only decides which one
   the user sees. */
.gallery-controls { display: none; gap: var(--sp-2) var(--sp-4); align-items: center; }
.panel-body:has(#mod-gallery:checked) .rows-per-page { display: none; }
.panel-body:has(#mod-gallery:checked) .gallery-controls { display: inline-flex; }

/* --- the gallery grid (GALLERY.md stage 3) --------------------------------
   No inline styles (CSP): the column count (1-10) and the thumb class
   (small/medium/large -> 150/300/500 px, GalleryThumbNailSizeListBox) are both
   finite domains, so each value is a class. The tile is the thumb width + 4px,
   legacy's own `width:#{thumbWidth + 4}px` on the cell. */
.gallery-strip { display: flex; flex-wrap: wrap; gap: var(--sp-2) var(--sp-4);
                 margin: var(--sp-3) 0 var(--sp-2); }
.gallery-grid { display: grid; gap: var(--sp-3); justify-content: start;
                margin: var(--sp-2) 0; }
.gal-cols-1  { grid-template-columns: repeat(1,  min-content); }
.gal-cols-2  { grid-template-columns: repeat(2,  min-content); }
.gal-cols-3  { grid-template-columns: repeat(3,  min-content); }
.gal-cols-4  { grid-template-columns: repeat(4,  min-content); }
.gal-cols-5  { grid-template-columns: repeat(5,  min-content); }
.gal-cols-6  { grid-template-columns: repeat(6,  min-content); }
.gal-cols-7  { grid-template-columns: repeat(7,  min-content); }
.gal-cols-8  { grid-template-columns: repeat(8,  min-content); }
.gal-cols-9  { grid-template-columns: repeat(9,  min-content); }
.gal-cols-10 { grid-template-columns: repeat(10, min-content); }
.gal-thumb-small  .gallery-tile { width: 154px; }
.gal-thumb-medium .gallery-tile { width: 304px; }
.gal-thumb-large  .gallery-tile { width: 504px; }
.gal-thumb-small  .tile-image  { width: 150px; }
.gal-thumb-medium .tile-image  { width: 300px; }
.gal-thumb-large  .tile-image  { width: 500px; }
.gallery-tile { background: var(--panel); border: 1px solid var(--border);
                border-radius: var(--r-sm); padding: var(--sp-2);
                font-size: 12px; overflow: hidden; }
.gallery-tile .tile-article { font-weight: 600; color: var(--slate); }
/* Image search's tile leads with the organisation, and it is a FILTER link
   with the row selector beside it, not a line of its own
   (incImageSearch.xhtml:897-912) — everything below it is a plain tile-line. */
.gallery-tile .tile-org { display: flex; align-items: center; gap: var(--sp-1);
                font-weight: 600; color: var(--slate); line-height: 1.35;
                white-space: nowrap; overflow: hidden; }
.gallery-tile .tile-org .cell-link { font-weight: 600; overflow: hidden;
                text-overflow: ellipsis; }
.gallery-tile .tile-org input[type="checkbox"] { margin: 0; flex: none; }
.gallery-tile .tile-line, .gallery-tile .tile-price { line-height: 1.35;
                white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.gallery-tile .tile-price { font-weight: 600; }
/* "2 Seiten, 3 Einschaltungen" is the one tile line legacy does NOT shorten
   (it shortens the organisation and the description through
   getCellValueShort, and prints this one whole). At Klein the line is wider
   than the 154px tile, and legacy answers by letting the tile grow past its
   image — measured on production 2026-09-14. The clone keeps the grid even
   and wraps the line instead: nothing is hidden either way, and a gallery of
   uneven columns is worse than a two-line caption (DIVERGENCES §8). */
.gallery-tile .tile-count { white-space: normal; }
.gallery-mode { gap: var(--sp-2); }
.gallery-mode .radio-inline { display: inline-flex; align-items: center;
                              gap: 4px; font-size: 12px; }
/* the download selector, where legacy puts it: under the article line,
   above the quantity (incPriceSearch.xhtml:2829) */
.gallery-tile .tile-select { line-height: 1; margin: 2px 0; }
.gallery-tile .caption { font-weight: 600; }
.tile-leaflet { display: block; margin-top: var(--sp-1); border: 0; padding: 0;
                background: none; cursor: pointer; text-align: left; }
.tile-image { display: block; height: auto; }
.tile-image-missing { display: inline-block; padding: var(--sp-4) 0;
                      color: var(--muted); text-align: center;
                      background: var(--row-2); }
.tile-leaflet .cnt-leaflets { font-weight: 600; }

.module-block-title {
  margin: 0 0 var(--sp-2);
  font: 600 11px/1.4 var(--chrome-font);
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--teal);
}
.field {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  margin-bottom: 6px;
}
.field > .focus-label {
  flex: 0 0 var(--label-gutter);
  color: var(--slate);
  font-size: 12px;
  word-wrap: break-word;
}
.field-input { flex: 1; min-width: 0; display: flex; align-items: center; gap: var(--sp-1); }

/* `:not([type])` — an input without a type is a text input too (Personal info's
   name, e-mail and phone, the relevant-set name and filter); without it they
   kept the browser's inset frame */
input[type=text], input:not([type]), input[type=password], input[type=number],
input[type=search], select {
  font: 13px/1.3 var(--body-font);
  color: var(--text);
  background: #fff;
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  padding: 4px 6px;
  width: 100%;
  min-width: 0;
  transition: border-color .12s ease, box-shadow .12s ease;
}
input:hover, select:hover { border-color: var(--teal-soft); }
input:focus, select:focus {
  outline: none;
  border-color: var(--teal);
  box-shadow: 0 0 0 3px rgba(0, 125, 143, .14);
}
/* a disabled field must not look editable: the rule above paints every typed
   input and select white (a locked setting, Personal info's password under
   switch user, a picker's one-value list while values are picked) */
:is(input:not([type]), input[type=text], input[type=password], input[type=number],
    input[type=search], select, textarea):disabled {
  background: var(--row-2);
  border-color: var(--border);
  cursor: not-allowed;
}
input.w-small { max-width: 84px; }
input.w-medium { max-width: 180px; }
/* the unit listbox inside the quantity row (legacy `.unitListBox`) */
select.w-unit { max-width: 132px; }
/* the date listboxes (legacy `.mediumWidthSelect` on selectDateListbox, D22);
   wide enough for the longest entry, `dd.mm.yyyy (week: NN)` */
select.w-date { max-width: 180px; }

/* --- the calendar popup, datepicker mode (D22; built in app.js §5) --------
   Legacy's ice:selectInputDate popup: a month grid with ISO week numbers
   beside it. The panel is positioned from JS (absolute, page coordinates) —
   only the offsets live there, everything visual is here. */
/* ⚠️ Every rule below that styles a <button> MUST restate `color` and undo the
   uppercase/letter-spacing of the generic `button, .btn` rule above: that rule
   paints buttons teal with #fff text, so a calendar button inheriting it comes
   out white-on-white. Found live in the browser, not by any suite — the D22
   check in verify_routes.py now requires an explicit colour here. */
.cal-btn {
  border: 1px solid var(--border); background: var(--panel); cursor: pointer;
  color: var(--text); letter-spacing: 0; text-transform: none;
  border-radius: var(--r-sm); padding: 2px 6px; line-height: 1.3; font-size: 13px;
  flex: none;
}
.cal-btn:hover { border-color: var(--teal-soft); background: var(--panel); }
/* The date inputs share their flex row with those buttons, so they must hold
   their width instead of shrinking (`.field-input` is a flex container and the
   inputs are `width: 100%`). 84px is `input.w-small`, fitting dd.mm.yyyy. */
input[data-date-pattern] { flex: none; width: 84px; }
.cal-pop {
  position: absolute; z-index: 40; padding: var(--sp-2);
  background: var(--panel); border: 1px solid var(--border);
  border-radius: var(--r-md); box-shadow: var(--shadow-md);
}
.cal-head {
  display: flex; align-items: center; gap: var(--sp-1);
  margin-bottom: var(--sp-1);
}
.cal-title { flex: 1; text-align: center; font-weight: 600; font-size: 12px; }
.cal-nav {
  border: 1px solid var(--border); background: var(--panel); cursor: pointer;
  color: var(--text); letter-spacing: 0; text-transform: none;
  border-radius: var(--r-sm); width: 22px; padding: 1px 0;
  line-height: 1.4; font-size: 13px;
}
.cal-nav:hover { border-color: var(--teal-soft); background: var(--panel); }
.cal-grid { border-collapse: collapse; font-size: 11px; }
.cal-grid th { font-weight: 600; color: var(--muted); padding: 2px 4px; }
.cal-grid td { padding: 0; }
/* the week-number column, legacy `renderWeekNumbers` */
.cal-wk {
  color: var(--muted); text-align: right; padding-right: 6px;
  border-right: 1px solid var(--border-soft);
}
.cal-day {
  border: 0; background: none; cursor: pointer; color: var(--text);
  letter-spacing: 0; text-transform: none;
  width: 26px; padding: 3px 0; font-size: 11px; border-radius: var(--r-sm);
}
.cal-day:hover { background: var(--row-hover); }
/* days spilling in from the neighbouring months, still clickable */
.cal-out { color: var(--muted); }
.cal-sel { background: var(--teal); color: #fff; }

.hint { color: var(--muted); font-size: 11px; }
/* These three were inline `style=` attributes. The Content-Security-Policy
   (app/web/security.py) allows no inline style, and a one-off declaration is
   easier to keep consistent here than spread across three templates. */
.hint-wide { max-width: 62ch; }
.hint-spaced { margin-bottom: 12px; }

/* The background export's status line (price_search/_export_job.html) — it
   sits under the grid in #export-job-slot and polls itself while running. */
.exports { display: block; margin: 6px 0; }
/* `flex-wrap`: a row that does not fit its container wraps rather than
   squeezing its parts to their longest word over each other (the leaflet
   detail's side column, 2026-10-29; any narrow window) — on a row that fits
   it changes nothing */
.exports-row { display: flex; flex-wrap: wrap; gap: 0 8px; align-items: baseline;
               padding: 2px 0; }
.exports-fmt { font-size: 10.5px; font-weight: 700; letter-spacing: .06em;
               color: var(--muted); min-width: 26px; }
.exports-what { color: var(--muted); }
.export-job-error { color: var(--error); font-size: 11px; }
.focus-label-auto { flex: 0 0 auto; }

.fieldset-title {
  font: 700 11px/1 var(--chrome-font);
  letter-spacing: .8px;
  text-transform: uppercase;
  color: var(--muted);
  margin: var(--sp-2) 0;
}

/* buttons — legacy foc:commandButtonLink (sprite) app.css:3921-4130.
   The fill is the variable `--btn-fill`, and `:hover` changes only the
   variable. A button whose own rule sets its background (a link, an icon, a
   light control) never reads it, so hovering cannot fill it. A rule for every
   button that set `background` on hover did exactly that: it beat the class
   rules and painted teal-dark under the action links, Sign out, the popup ×
   and the grid's chart icon, whose labels then vanished. verify_assets.py
   refuses such a rule. Every button sets the variable itself, so it never
   inherits a hovered ancestor's value. */
button, .btn {
  --btn-fill: var(--teal);
  font: 700 11px/1 var(--chrome-font);
  letter-spacing: 1px;
  text-transform: uppercase;
  color: #fff;
  background: var(--btn-fill);
  border: 1px solid var(--btn-fill);
  border-radius: var(--r-sm);
  padding: 8px 16px;
  cursor: pointer;
  text-decoration: none;
  transition: background-color .12s ease, box-shadow .12s ease;
}
button:hover:not(:disabled), .btn:hover:not(:disabled) { --btn-fill: var(--teal-dark); }
button:disabled, .btn:disabled { opacity: .5; cursor: not-allowed; }
button.btn-secondary, .btn-secondary {
  color: var(--teal-dark);
  background: #fff;
  border-color: var(--border);
}
button.btn-secondary:hover, .btn-secondary:hover {
  background: var(--row-2);
  border-color: var(--teal-soft);
}
button.btn-small, .btn-small { padding: 4px 9px; letter-spacing: .5px; }
.form-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  align-items: center;
  gap: var(--sp-2);
  margin-top: var(--sp-3);
}
/* The "one of these fields" hint (Price search only) takes the whole row above
   the controls rather than squeezing in beside them. It used to have the width
   of the whole form to itself; since D30 the footer is the width of the field
   columns, where the hint wrapped to three lines and pushed the buttons around.
   Its own line keeps the button row exactly where the eye expects it. */
.form-actions > .hint { flex: 1 0 100%; text-align: left; }
/* The search footer: rows per page (and the gallery controls) at the LEFT,
   FIND / NEW SEARCH at the RIGHT — legacy's `.rowsPerPage { float: left }`
   and `.searchButtons { float: right }` (legacy app.css:75/3914), measured on
   Price search, Image search and the three optimizers 2026-10-25. Until then
   the three controls were packed together at the right (FORM_LAYOUT D7 read
   the footer's two cells as "side by side"). The report and settings forms
   have footers of their own and keep theirs. */
.form-actions.search-footer > button[type=submit] { margin-left: auto; }
/* A label+control pair outside the 135px `.focusLabel` gutter — the rows-per-page
   dropdown sits in the button row, where legacy also keeps it (`.rowsPerPage` next
   to `.searchButtons`), not in a field column. */
.inline-field {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  color: var(--slate);
  font-size: 12px;
  white-space: nowrap;
}
.inline-field select { width: auto; }

/* search-module toggles — legacy .searchModulesBlock app.css:3841, tri-state
   sprite buttons incPriceSearch.xhtml:64-185 */
.modules {
  background: var(--block-bg);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  padding: var(--sp-2) var(--sp-3);
  margin-bottom: var(--sp-3);
}
.modules-group { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2); }
.modules-group + .modules-group { margin-top: var(--sp-2); }
.modules-title {
  font: 700 10px/1 var(--chrome-font);
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--muted);
  flex: 0 0 var(--label-gutter);
}
.chip {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font: 700 10px/1 var(--chrome-font);
  letter-spacing: .8px;
  text-transform: uppercase;
  color: var(--slate);
  background: #fff;
  border: 1px solid var(--border);
  /* Squared corners (proposal B) — matches the tabs, inputs and grid; the old
     999px pill clashed with the otherwise sharp UI. See docs/design/. */
  border-radius: 3px;
  padding: 7px 12px;
  cursor: pointer;
  user-select: none;
  transition: background-color .12s ease, border-color .12s ease, color .12s ease;
}
.chip input { position: absolute; opacity: 0; width: 0; height: 0; }
.chip:hover { border-color: var(--teal-soft); }
/* Proposal B: a glyph replaces the status dot — "+" (can be added) when off,
   a check when on. The span stays empty in markup; the glyph is CSS content. */
.chip .chip-dot {
  font-weight: 700; font-size: 11px; line-height: 1;
  color: var(--muted);
  transition: color .12s ease;
}
.chip .chip-dot::before { content: "+"; }
/* the "on" state is the checkbox itself — no JS needed to reflect it */
.chip.is-on, .chip:has(input:checked) {
  background: var(--teal);
  border-color: var(--teal);
  color: #fff;
}
.chip.is-on .chip-dot, .chip:has(input:checked) .chip-dot { color: #fff; }
.chip.is-on .chip-dot::before,
.chip:has(input:checked) .chip-dot::before { content: "\2713"; }
.chip:focus-within { box-shadow: var(--ring); }

/* value-search pickers — legacy foc:multiSearchValues popup, as a type-ahead.
   No chip rule here any more: a picked value is shown in the values panel and
   nowhere else, which is legacy's own arrangement (FORM_LAYOUT D1, extended to
   the pickers 2026-08-09 — they had kept chips after the text blocks lost them,
   so the same value appeared twice). `.picker-remove` stays: it is the panel's
   remove button too. */
.picker-remove {
  all: unset;
  cursor: pointer;
  width: 15px;
  height: 15px;
  line-height: 14px;
  text-align: center;
  border-radius: 50%;
  color: var(--muted);
}
.picker-remove:hover { background: var(--teal); color: #fff; }
/* What a keystroke swaps — the match list, the refusal hint and the
   single-value confirm, never the box above them (components/pickers.html,
   `value_search`). An OVERLAY under the field since D28: legacy's value search
   is a floating window (ice:panelPopup, multiSearchValueSearchPopup.xhtml:8)
   and never moves the form, while the in-flow list used to push every row
   below it down for as long as it was open. `.picker-anchor` is the picker's
   own `.field-input`; the wrapper itself stays chromeless so an EMPTY answer
   draws nothing — the chrome lives on `.picker-panel`, which only renders with
   content. z-index below the paste dropdown (30): the two never share a field,
   but a picker's list may open under a NEIGHBOURING field's paste panel. */
.picker-anchor { position: relative; }

/* ONE BOX: the input and its icons (D29, 2026-09-14). Legacy hangs the value
   search, the "+" and paste beside the field as three bordered buttons, which
   ends the input ~70px short of every other input on the form. Here the border
   moves to the wrapper and the icons ride inside it at the right end, so the
   field is as wide as its neighbours and the affordances read as part of it
   rather than as three buttons after it. Only the free-text block takes this:
   the id picker's box is its input alone (nothing to hold), and a `<select>`
   brings its own frame and arrow.

   The wrapper now carries the chrome, so the input inside must give up its own
   border, background and focus ring — otherwise it draws a second box inside
   the first — and the wrapper answers hover and focus in its place. */
.picker-box {
  background: #fff;
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  padding-right: 3px;
  gap: 0;
  transition: border-color .12s ease, box-shadow .12s ease;
}
.picker-box:hover { border-color: var(--teal-soft); }
.picker-box:focus-within {
  border-color: var(--teal);
  box-shadow: 0 0 0 3px rgba(0, 125, 143, .14);
}
.picker-box > input[type=text],
.picker-box > input[type=search] {
  border: 1px solid transparent;   /* keeps the box's height off the border */
  background: none;
  border-radius: 0;
}
.picker-box > input:hover,
.picker-box > input:focus {
  border-color: transparent;
  box-shadow: none;
}
/* The icon group at the right end of the box. Icons, not buttons: no border and
   no fill of their own — the box is the only chrome — and they answer with the
   teal the rest of the form uses. 20px keeps the group inside the input's line
   box, so the field is exactly as tall as a plain one. */
/* No gap and a box barely wider than the 13px glyph: the three read as one
   group of icons belonging to the field. Spaced out they looked like three
   separate controls that happened to follow it (the user, 2026-09-14). */
.picker-tools { display: flex; align-items: center; gap: 0; flex: none; }
.picker-tools > .picker-find,
.picker-tools > .picker-add-inline,
.picker-tools .msv-paste > summary {
  width: 17px;
  height: 20px;
  border: 0;
  background: none;
  border-radius: var(--r-sm);
  color: var(--muted);
}
.picker-tools > .picker-find:hover,
.picker-tools > .picker-add-inline:hover,
.picker-tools .msv-paste > summary:hover {
  background: var(--nav-active-bg);
  color: var(--teal);
}
/* pressed = the list this icon opened is showing */
.picker-tools > .picker-find.open,
.picker-tools .msv-paste[open] > summary {
  background: var(--nav-active-bg);
  color: var(--teal);
}
/* The "+" is a glyph here, not a label. `.picker-add` starts from `all: unset`
   (content-box) and carries a suggestion row's 3px/6px padding, so inside the
   group it came out 29px wide against the other two at 17 — that uneven middle
   is what made the three look scattered rather than like one group. Both have
   to be restated; the width alone does not reach the padding. */
.picker-tools > .picker-add-inline {
  display: flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  padding: 0;
  font-size: 14px;
  line-height: 1;
}
.picker-out {
  position: absolute;
  top: calc(100% + 2px);
  left: 0;
  right: 0;
  z-index: 25;
}
.picker-panel {
  position: relative;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  box-shadow: var(--shadow-md);
  padding: 3px;
  /* room for the close button riding the top-right corner */
  padding-top: 20px;
}
/* legacy's popup header X — the same little circle as every remove button */
.picker-close { position: absolute; top: 3px; right: 3px; }
.picker-panel .hint { padding: 0 3px 3px; }
.picker-panel .confirm-strip { padding: 0 3px 4px; }
.picker-results {
  list-style: none;
  margin: 0;
  padding: 3px;
  max-height: 170px;
  overflow-y: auto;
  width: 100%;
  background: #fff;
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  box-shadow: var(--shadow-sm);
}
.picker-add {
  all: unset;
  cursor: pointer;
  display: block;
  width: 100%;
  padding: 3px 6px;
  border-radius: var(--r-sm);
  font-size: 12px;
  color: var(--text);
}
/* `all: unset` above resets box-sizing to its initial content-box, so it undoes
   the global `* { box-sizing: border-box }` — and a suggestion row is then
   `width: 100%` PLUS its 12px of padding, i.e. 12px wider than the list holding
   it. That is what put a horizontal scrollbar under every open suggestion list,
   whatever the text length. Scoped to the list on purpose: `.picker-add-inline`
   (the "+") sizes its 22px box the content-box way and would shrink to 22px
   overall if this landed on `.picker-add` itself. */
.picker-results .picker-add { box-sizing: border-box; }
/* …and a value longer than the list still overflowed it sideways, because
   `all: unset` leaves ordinary word wrapping, which will not break inside a
   word: one `Billa/Bad Radkersburg Gesch.Er.` put a horizontal scrollbar under
   the whole list. The rows wrap at any point instead — the list is 170px of
   names, not prose. */
.picker-results .picker-add { overflow-wrap: anywhere; }
.picker-add:hover { background: var(--teal); color: #fff; }
/* The "add value" button of a multi-value text input (components/pickers.html
   multi_text) sits INSIDE the field row next to the box, so it is a small square
   affordance — not the full-width row a suggestion-list entry is. */
.picker-add-inline {
  width: 22px;
  flex: none;
  text-align: center;
  border: 1px solid var(--border);
}
/* the darker edge of a hovered button, on the "+" square's own frame */
.picker-add-inline:hover { border-color: var(--teal-dark); }
/* Inside the overlay panel the list needs no chrome of its own (the panel
   carries it), and a row is legacy's PAIR of controls (multiLink.xhtml): the
   "+" square and the value's name side by side. The name stretches; the "+"
   keeps its 22px (`flex: none` on `.picker-add-inline`, excluded here because
   this three-class selector would out-rank it). */
.picker-panel .picker-results {
  border: 0;
  box-shadow: none;
  padding: 0;
  background: none;
}
.picker-panel .picker-results li { display: flex; align-items: center; gap: 3px; }
/* The value list of a multi-value text input carries a "+" beside every name,
   so the field's own width is too narrow for it. It gets ONE fixed width,
   whichever list it is: a width taken from the content (`max-content`, tried
   first) made the magnifier's list, with its longer names, wider and wrapping
   while the autocomplete's stayed narrow on one line — the same list looking
   two ways (vketman 2026-10-06). It floats over whatever is to the field's
   right; the overlay is absolutely positioned, so a panel wider than its
   anchor is simply drawn wider. A long name wraps, the same way in both. */
[id^="multi-out-"] .picker-panel {
  width: 420px;
  max-width: calc(100vw - 32px);
}
[id^="multi-out-"] .picker-results .picker-value {
  min-width: 0;
  white-space: normal;
  word-break: break-word;
  overflow-wrap: anywhere;
}
/* …and the "+" sits at the list's right edge, which is exactly where macOS
   draws its overlay scrollbar — ON the content, taking no room — so on a list
   long enough to scroll it covered every "+" but a sliver of its left border
   (vketman's screenshot, 2026-10-06). The list keeps a gutter for it. */
[id^="multi-out-"] .picker-results { padding-right: 18px; }
.picker-panel .picker-results .picker-add:not(.picker-add-inline) {
  flex: 1;
  width: auto;
}
/* The name beside the "+" of a list-building user: a row of an ordinary list —
   selecting it (click or arrow keys, app.js §8) only fills the box, and the
   selected row stays marked (DIVERGENCES.md §2). Laid out like the clickable
   name of a user without lists, so the rows line up either way. */
.picker-panel .picker-results .picker-value {
  all: unset;
  box-sizing: border-box;
  flex: 1;
  padding: 3px 6px;
  border-radius: var(--r-sm);
  overflow-wrap: anywhere;
  cursor: pointer;
}
.picker-panel .picker-results .picker-value:hover { background: var(--nav-active-bg); }
.picker-panel .picker-results .picker-value.selected,
.picker-panel .picker-results .picker-value:focus { background: var(--teal); color: #fff; }
/* The value-search icon, legacy's `add.gif` on `.multiValueAddButton`
   (`inputText.xhtml:68`): the same 22px square as the paste icon beside it,
   because in legacy one icon opens the popup that holds both. `.open` is the list
   showing — the pressed state, and the icon then closes it. */
.picker-find {
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  flex: none;
  padding: 0;
  color: var(--slate);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  background: #fff;
}
.picker-find:hover { background: var(--teal); color: #fff; border-color: var(--teal-dark); }
.picker-find.open { background: var(--teal); color: #fff; }
.search-icon { width: 13px; height: 13px; display: block; }

/* ===========================================================================
   Result grid — legacy .resultGrid / iceDatTbl app.css:2074-2260
   =========================================================================== */
.results { margin-top: var(--sp-2); }
.table-scroll {
  overflow-x: auto;
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  background: var(--panel);
}
/* the grid macro's `aside` (results_table.html): a block drawn right of the
   data table alone — MBD compare's summary — under the record line, the pager
   and the action links, which stay above both (legacy's `panelGrid
   columns="2"` holds just the table and the panel, incMbdCompare.xhtml:639) */
.table-aside { display: flex; align-items: flex-start; gap: var(--sp-5); }
.table-aside > .table-scroll { flex: 1 1 auto; min-width: 0; }
table.datatable {
  border-collapse: separate;
  border-spacing: 0;
  width: 100%;
  background: var(--panel);
  font-size: 12px;
  letter-spacing: .25px;
}
table.datatable th {
  background: var(--teal);
  color: #fff;
  font: 400 11px/1.2 var(--chrome-font);
  letter-spacing: 1px;
  text-transform: uppercase;
  text-align: center;
  padding: 5px 7px;
  border-right: 1px solid #B6C8CD;
  border-bottom: 1px solid #B6C8CD;
  position: sticky;
  top: 0;
  z-index: 2;
  white-space: nowrap;
}
table.datatable th:last-child { border-right: none; }
table.datatable td {
  padding: 3px 7px;
  border-right: 1px solid #FDFDFE;    /* app.css:2190 */
  vertical-align: top;
  white-space: nowrap;
}
/* The grid is ~30-60 columns wide (legacy parity), so long texts are clipped
   with an ellipsis and the full value stays in the cell's title attribute —
   AND in the title of the filter-link button inside it. Both, because a title
   on a child element wins over its parent's: while the button carried a generic
   "Search for this value", the cell's own title was unreachable over the text
   itself, which is the only place it is ever needed (2026-09-15). */
table.datatable td { max-width: 26ch; overflow: hidden; text-overflow: ellipsis; }
table.datatable td.num { max-width: none; }
/* The browser draws that ellipsis on text, not on a button: a filter link
   (`.cell-link`, below) was cut hard at the cell's edge with no "…", so its
   text seemed to run on into the next column (Redmine #19051). The link draws
   its own ellipsis, at the width the cell leaves it. Only the grid's filter
   links (`.cell-filter`, `results_table.html`): the reports put other
   `.cell-link` buttons in their cells — Promotion calendar's two-line counts,
   Segments' chart icon. */
table.datatable td .cell-filter {
  display: inline-block;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  vertical-align: top;
}
/* Image search wraps instead (`data-wrap`, Redmine #19051, DIVERGENCES.md §3):
   legacy's grid puts a long organisation or leaflet title on a second line.
   The line is as long as the 26ch cap. The cap sits on a block inside the cell
   (`.cell-wrap`) at `width: max-content`, so the column is exactly as wide as
   its longest line, up to the cap. A table cell's own width is only a
   suggestion to the table: in a narrow window the table would squeeze its
   text columns below that and wrap them word by word. The cell's own cap is
   lifted here, because Chrome applies it to the cell's padding as well: a
   26ch block in a 26ch cell lost its last 14px (measured 2026-10-25). */
table.datatable[data-wrap] td { max-width: none; white-space: normal; text-overflow: clip; }
table.datatable[data-wrap] .cell-wrap {
  width: max-content;
  max-width: 26ch;
  overflow-wrap: break-word;
}
/* a block is not moved by the cell's text-align: a number stays right */
table.datatable[data-wrap] td.num .cell-wrap { margin-left: auto; }
/* A resizable grid (Prices only so far, vketman 2026-10-06 — DIVERGENCES.md §3):
   the article column grows with its longest name up to 60 characters instead
   of stopping at 26, and every column but the leaflet icon and the toolbox has
   a handle on the right edge of its header that widens or narrows it with the
   mouse (app.js §9). A width set by the handle is inline on the cells and wins
   over both caps (the 60ch one sits after the pinned-column rules below, where
   `verify_assets` reads the article column's first rule as its sticky one). The leaflet column is left out on purpose: the article
   column's sticky offset IS its width (`--leaflet-col-width`, below). */
table.datatable[data-resizable] th .col-resize {
  position: absolute;
  top: 0;
  right: -3px;
  width: 7px;
  height: 100%;
  cursor: col-resize;
  z-index: 1;
}
table.datatable[data-resizable] th .col-resize:hover,
table.datatable[data-resizable] th .col-resize.dragging {
  background: rgba(255, 255, 255, .45);
}
/* while dragging, a stray text selection would follow the mouse across the grid */
body.col-resizing { cursor: col-resize; user-select: none; }
/* Two columns stay visible while scrolling sideways (modern addition — legacy
   pins nothing). They are selected by CLASS, from `GridColumn.pinned`, and never
   by position.

   `td:nth-child(2)` was the original rule and it was wrong on screen. Which
   column lands second depends on the grouping and on what the user hid, so the
   offset measured for the leaflet column got applied to Page position, or to the
   article, or to the leaflet id. Worse, the offset itself was a guess: the table
   is `table-layout: auto`, so a `width` on a cell is a hint, and the leaflet
   column really renders at about 28px (13px icon + 14px padding + 1px border).
   The browser therefore pushed the second column right from ~28px to the 42px
   the offset demanded — a visible GAP after column one — while the table kept its
   original slot reserved, so the shifted cell overlapped the *third* column and,
   being positioned where the third column is not, painted over its first
   character. "The article column loses its first letter" was that.

   The fix has two halves, and both are needed:
     1. pin by class (below), so an offset can only ever apply to the column it
        was measured for;
     2. make the leaflet column's width REAL, not a hint (`min-width` +
        `max-width` further down), so `--leaflet-col-width` is true by
        construction rather than by assumption.
   `verify_assets.py` holds both. */
table.datatable th.col-prosp_detail,
table.datatable td.col-prosp_detail {
  position: sticky;
  left: 0;
  z-index: 1;
}
table.datatable th.col-artikel,
table.datatable td.col-artikel {
  position: sticky;
  left: var(--leaflet-col-width);
  z-index: 1;
  box-shadow: 1px 0 0 var(--border);
}
/* Image search pins `organisation` where Price search pins `artikel`: the same
   offset, because the offset is where the second frozen column PARKS — flush
   against the first one — and not the sum of the columns before it. The toolbox
   and ID columns scroll underneath it, exactly as the leaflet id, page and page
   position do under `artikel`. Both selectors carry `.pinned` so the rule can
   only reach a grid whose registry actually pinned that column: the price grid
   has an `organisation` column too, and there it scrolls. */
table.datatable th.col-organisation.pinned,
table.datatable td.col-organisation.pinned {
  position: sticky;
  left: var(--leaflet-col-width);
  z-index: 1;
  box-shadow: 1px 0 0 var(--border);
}
table.datatable th.pinned { z-index: 3; }
/* the article column's own cap in a resizable grid — see `[data-resizable]` above */
table.datatable[data-resizable] td.col-artikel { max-width: 60ch; }
/* A grid with NO pinned leaflet column parks the article column at 0: the
   offset above is that column's width, and where the article column is the
   first frozen one (MBD compare, My leaflet compare) the offset pushed its
   cells 58px right, over the quantity column — "NGE / ANZ.IB X ANZ", "m/1x1"
   on MBD compare, found side by side 2026-10-25. After the resizable cap so
   `verify_assets` still reads the first `td.col-artikel` rule as the sticky one. */
table.datatable:not(:has(.col-prosp_detail.pinned)) th.col-artikel.pinned,
table.datatable:not(:has(.col-prosp_detail.pinned)) td.col-artikel.pinned { left: 0; }
table.datatable tbody tr:nth-child(odd) td { background: var(--row-1); }
table.datatable tbody tr:nth-child(even) td { background: var(--row-2); }
table.datatable tbody tr:hover td { background: var(--row-hover); }
table.datatable td.num { text-align: right; font-variant-numeric: tabular-nums; }
/* `.negativeDeviation` (legacy app.css:164) — the Price compare grid's only
   coloured cell. Its siblings `.positiveDeviation` / `.noDeviation` are declared
   there but empty, so nothing marks a dearer comparison. */
table.datatable td.is-negative { color: var(--error); }
table.datatable td.empty {
  text-align: center;
  color: var(--muted);
  padding: 18px;
  background: var(--panel) !important;
}

/* leaflet-detail column — legacy `priceSearchShowProspDetail` +
   prospDetailLink.xhtml (.goToPromotionDetailsIcon / .cntLeaflets). The icon is
   drawn for every row; `(n)` appears only when the row collapses several
   leaflets, and the icon is then the aggregated variant. */
/* The width has to be REAL, not a hint: the article column's sticky offset is
   this same variable, so if the browser lays this column out any wider or any
   narrower, that offset is wrong and the overlap/gap described above comes back.
   `table-layout` is auto, where a lone `width` only competes with content — hence
   min-width and max-width too, plus `overflow: hidden` so `(n)` can never push
   past it. The value fits the icon plus a 3-digit count comfortably; an absurd
   count clips its tail (recoverable — the span carries the raw number as its
   title) instead of breaking the layout, which is the degradation we want. */
table.datatable th.leaflet-cell,
table.datatable td.leaflet-cell {
  width: var(--leaflet-col-width);
  min-width: var(--leaflet-col-width);
  max-width: var(--leaflet-col-width);
  overflow: hidden;
  white-space: nowrap;
}
/* THE ROW TOOLBOX — legacy `foc:toolboxButton` + `foc:toolboxContent`
   (`incImageSearch.xhtml:685-687`, `:629-635`). Legacy keeps one hidden menu per
   screen and moves it to the clicked row with a script; the clone renders it into
   the row's own cell, so the anchor is the cell and there is nothing to position.
   The menu opens BELOW the grid, in the same slot its own two panels use: the
   grid sits in `.table-scroll`, and a scroll container clips whatever a cell
   tries to float over it, so a menu rendered into the cell was invisible (found
   in the browser 2026-09-14). Same shape as the leaflet-list popup. */
.toolbox-btn {
  all: unset;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  border-radius: var(--r-sm);
  color: var(--muted);
}
.toolbox-btn:hover { background: var(--nav-active-bg); color: var(--teal); }
.toolbox-icon { width: 13px; height: 13px; display: block; }
.toolbox-panel { margin-top: var(--sp-3); max-width: 320px; }
.toolbox-row { font-weight: normal; margin-left: var(--sp-2); }
.toolbox-menu {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  text-align: left;
}
.toolbox-item {
  all: unset;
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 4px 6px;
  border-radius: var(--r-sm);
  font-size: 12px;
  color: var(--text);
  white-space: nowrap;
}
.toolbox-item:hover { background: var(--teal); color: #fff; }
.tb-icon { width: 14px; height: 14px; flex: none; }

/* A cell's filter link — legacy `foc:multiLink` inside a result cell: the value
   is a link that searches for itself. Plain text with an underline on hover, so
   the grid still reads as a table of values rather than a page of links. */
.cell-link {
  all: unset;
  cursor: pointer;
  color: inherit;
}
.cell-link:hover { color: var(--teal); text-decoration: underline; }

/* A sign-coloured cell (registry `sign_class`) — Product compare's two
   deviation columns. Legacy's own colours (`css/focus/app.css:157-167`):
   `positiveDeviation` #02b31b, `negativeDeviation` red, `noDeviation` none. */
td.sign-neg { color: red; }
td.sign-pos { color: #02b31b; }

/* SEARCH SETTINGS — legacy's column dialog is a modal `panelGrid columns="6"`,
   i.e. three label+checkbox pairs per row (`main.xhtml:479-485`). Same three
   tracks here, in an inline panel; the boxes are in bean-registry order, which
   is the registry's own business. */
.settings-panel { margin-top: var(--sp-3); max-width: 620px; }
.settings-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--sp-1) var(--sp-4);
}
.settings-col { display: flex; align-items: center; gap: 6px; font-size: 12px; }
/* OK at the LEFT, under the first column of boxes, where legacy's modal has
   it (main.xhtml:463-497; measured 2026-10-25) */
.settings-actions { display: flex; justify-content: flex-start;
                    margin-top: var(--sp-3); }

/* THE IMAGE VIEWER — legacy's "Bildansicht" widget, opened from the toolbox.
   An inline dialog under the grid rather than a draggable window (DIVERGENCES
   §1). The image is the whole point of the screen, so it gets the width and the
   pagers sit under it, as legacy arranges them. */
.viewer { margin-top: var(--sp-3); }
.viewer-title { font-weight: normal; margin-left: var(--sp-2); }
.viewer-body { display: flex; flex-direction: column; align-items: center;
               gap: var(--sp-2); }
.viewer-links { align-self: flex-start; }
.viewer-image {
  max-width: 100%;
  max-height: 70vh;
  border: 1px solid var(--border);
  background: #fff;
}
.viewer-pagers { display: flex; gap: var(--sp-5); flex-wrap: wrap; }
.viewer-pager { display: inline-flex; align-items: center; gap: 4px; }
.viewer-pager-label { font-size: 11px; color: var(--slate);
                      text-transform: uppercase; letter-spacing: .5px; }
.viewer-pager-pos { font-size: 12px; color: var(--muted); }

/* The ⓘ beside a cell's value — legacy `.infoButtonRight` with
   `info_resultgrid.gif` (Image search's RW TOTAL, `GridColumn.info_tab`). It
   rides at the right end of a right-aligned number cell, so it needs no width of
   its own; `vertical-align` keeps it on the number's baseline rather than on the
   line box's. */
.info-btn {
  all: unset;
  cursor: pointer;
  color: var(--muted);
  margin-left: 4px;
  vertical-align: -2px;
}
.info-btn:hover { color: var(--teal); }
.info-icon { width: 12px; height: 12px; display: inline-block; }

/* The toolbox cell — Image search's `foc:toolboxButton` column. An icon cell, so
   it is sized and centred like the row selector; unlike `.leaflet-cell` its width
   carries no sticky offset, so a hint is enough. */
table.datatable th.toolbox-cell,
table.datatable td.toolbox-cell {
  width: 28px;
  min-width: 28px;
  text-align: center;
  white-space: nowrap;
}
/* SELECT ROWS FOR DOWNLOAD — the checkbox column, first in the grid when it is
   on at all (legacy registers it ahead of the leaflet icon). It is deliberately
   NOT sticky: the two pinned columns are pinned because they can never disappear
   (see `GridColumn.pinned`), and this one appears and disappears on a link. */
table.datatable th.rowsel-cell,
table.datatable td.rowsel-cell {
  width: 26px;
  min-width: 26px;
  text-align: center;
  padding-left: 4px;
  padding-right: 4px;
}
table.datatable .rowsel-cell input { margin: 0; vertical-align: middle; }

.leaflet-icon { width: 13px; height: 13px; vertical-align: -2px; }
.leaflet-link { color: var(--teal-dark); }
.leaflet-link.is-disabled { color: var(--muted); cursor: default; }
.cnt-leaflets {
  font-size: 10px;
  color: var(--muted);
  margin-left: 2px;
}
.agg-icon { width: 10px; height: 10px; opacity: .8; }

/* legacy `.caption` (shared.css:241) — the label of a single value or input,
   also the left column of the leaflet header pairs */
.caption { font-weight: bold; color: var(--slate); }

/* The icon is a link (one leaflet) or a button that opens the leaflet list
   (several) — the button is reset so the two are indistinguishable. */
button.leaflet-link {
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
  color: var(--teal-dark);
}

/* ===========================================================================
   Leaflet detail — legacy detail/incDetailProsp.xhtml. Two columns: the
   thumbnail + page list on the left, promotions/shops on the right
   (.prospDetailBlock, app.css:3120).
   =========================================================================== */
.leaflet-layout {
  display: grid;
  grid-template-columns: minmax(280px, 340px) minmax(0, 1fr);
  gap: var(--sp-3);
  align-items: start;
  padding: 0 var(--sp-4) var(--sp-4);
}
@media (max-width: 900px) {
  .leaflet-layout { grid-template-columns: minmax(0, 1fr); }
}
.leaflet-side { display: flex; flex-direction: column; gap: var(--sp-3); min-width: 0; }
.leaflet-main { min-width: 0; }
.leaflet-headblock { display: flex; gap: var(--sp-3); }
/* the page image (STATUS.md §5.1 stage 3) — legacy's imagePager: the scan in a
   fixed frame with the page walk underneath. The frame keeps its size whether or
   not there is a picture, so the header values beside it do not shift.

   124 x 172 since 2026-09-21 (was 96 x 132, which the user found too small).
   Two things set the size. It is about legacy's own: the thumbnail on
   production measures roughly 131 x 190, read off a screenshot rather than the
   DOM, so the frame is now near it instead of two thirds of it. And the left
   column is `minmax(280px, 340px)`, so every pixel here comes off the facts
   list beside it — at 124 the longest value on the reference leaflet
   ("Eurogast C+C/Interex") still sits on one line at the 280px minimum, which
   is what stopped the bump going further.

   The ratio is 0.72 against A4 portrait's 0.707, so an A4 scan is
   height-limited in the frame and letterboxed by about 2px a side — invisible,
   and `object-fit: contain` keeps any other format whole too. Costs no
   bandwidth: /leaflet/img/{id} serves the scan unscaled (1240 x 1754 on the
   reference page), so the browser was already downloading every pixel this
   shows. */
.leaflet-thumbcol {
  flex: 0 0 124px;
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
}
.leaflet-thumb {
  height: 172px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--row-2);
  border: 1px dashed var(--border);
  border-radius: var(--r-sm);
  overflow: hidden;
}
/* a page scan is portrait and much larger than the frame: contain it whole rather
   than cropping, since the point of the thumbnail is to recognise the page */
.leaflet-thumb-link { display: block; width: 100%; height: 100%; }
.leaflet-thumb-img { width: 100%; height: 100%; object-fit: contain; display: block; }
.leaflet-thumb-todo { color: var(--muted); font-size: 10px; text-transform: uppercase; }
.leaflet-thumb-pager {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-1);
}
.leaflet-thumb-count { color: var(--muted); font-size: 11px; }
.leaflet-pagebtn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  background: var(--surface);
  color: var(--text);
  text-decoration: none;
  line-height: 1;
}
.leaflet-pagebtn:hover { background: var(--row-2); }
.leaflet-pagebtn.is-disabled { color: var(--muted); background: var(--row-2); }
/* legacy `belowThumbDetailBlockContainer`: the JPG and A4-PDF links sit in a
   block of their own under the head panel, one per line rather than side by
   side (measured on production 2026-09-21, leaflet 1845711 page 17). */
.leaflet-imagelinks {
  flex-direction: column;
  align-items: flex-start;
  gap: var(--sp-1);
}
.leaflet-headside { min-width: 0; flex: 1; }
.leaflet-mut { display: flex; align-items: center; gap: var(--sp-1); flex-wrap: wrap; }
.leaflet-mut select { max-width: 100%; }
/* the 16 header values — label/value pairs, values right-aligned like legacy's
   columnClasses="caption,alignRight" */
.leaflet-facts {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 1px var(--sp-2);
  margin: var(--sp-2) 0 0;
  font-size: 11px;
}
.leaflet-facts dt { color: var(--muted); }
.leaflet-facts dd { margin: 0; text-align: right; overflow-wrap: anywhere; }
.leaflet-facts dd.num { font-variant-numeric: tabular-nums; }
.leaflet-filter {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  margin: var(--sp-2) 0;
}
.leaflet-filter input { width: 180px; }

/* The Price compare tab's filter form. Legacy lays it out as a four-column
   `searchModuleBlock`; here it is two columns that collapse to one when the
   window cannot hold them, because the tab shares its row with the leaflet
   block and has less width to spend than the search screen. The `.field` rows
   inside come from the shared form macros and already carry their own labels. */
.pc-form { margin: var(--sp-2) 0 var(--sp-3); }
.pc-form-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
  gap: 0 var(--sp-5);
  align-items: start;
}
.pc-form-col { min-width: 0; }        /* or a long picker chip widens the column */
.pc-form-actions {
  display: flex;
  gap: var(--sp-2);
  margin-top: var(--sp-3);
}
/* the selects in this form are labelled rows, not the free-standing control the
   `.leaflet-filter` rule sizes — so they follow the field width, not the panel */
.pc-form .field-input > select { width: 180px; }
/* Content structure's whole form: two labelled selects side by side, which is
   legacy's own four-column `prospDetailProductsFilterContainer` with two cells
   filled. They wrap rather than shrink, so the labels never sit on top of the
   controls on a narrow window. */
.cs-controls {
  display: flex;
  align-items: center;
  gap: var(--sp-4);
  flex-wrap: wrap;
  margin: var(--sp-2) 0 var(--sp-1);
}
.cs-controls .field { margin: 0; }
.cs-controls .field-input > select { width: 180px; }
/* Gross reach: two blocks, each a row of mode links over its grid (legacy's two
   `grossReachBlock` panels). The active mode is the one you are looking at, so it
   is not a link — same treatment the sub-tabs get. */
.reach-block + .reach-block { margin-top: var(--sp-4); }
.reach-caption {
  font-size: 13px;
  font-weight: 600;
  margin: 0 0 var(--sp-2);
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: .04em;
}
.reach-modes {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-1) var(--sp-3);
  margin-bottom: var(--sp-2);
  font-size: 12px;
}
.reach-mode { color: var(--teal); text-decoration: none; }
.reach-mode:hover { text-decoration: underline; }
.reach-mode.is-active { color: var(--text); font-weight: 600; }
/* the appended total row (legacy's `summary` style class) */
table.datatable tbody tr.is-summary td {
  font-weight: 600;
  border-top: 1px solid var(--border);
}
.subtabs-inner { padding: 0 0 var(--sp-2); }
/* a group's own tabs under the page's tab strip (Settings -> Settings): the
   same side padding as the strip above it, a little air between the two */
.subtabs.subtabs-nested { padding: var(--sp-1) var(--sp-4) 0; }
table.datatable tbody tr.is-selected td { background: var(--row-hover); }
/* link-driven pagers have no disabled attribute to style */
.pager.is-disabled { opacity: .45; cursor: default; }
.leaflet-popup { margin: 0 var(--sp-4) var(--sp-4); }
/* where app.js §14 scrolls the leaflet list to: clear of the sticky header
   (64px + its 4px of border) when it lands at the top, a little air when it
   lands at the bottom */
#leaflet-popup { scroll-margin-top: 76px; scroll-margin-bottom: var(--sp-4); }
.popup-close {
  float: right;
  background: none;
  border: 0;
  font-size: 15px;
  line-height: 1;
  cursor: pointer;
  /* every × sits in a teal `.panel-head`: white like its title (grey there
     was all but invisible) */
  color: inherit;
  opacity: .8;
}
.popup-close:hover { opacity: 1; }

/* sortable header — legacy tableSorter.xhtml (arrow + superscript priority) */
th .sorter {
  all: unset;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  color: inherit;
  font: inherit;
  letter-spacing: inherit;
}
th .sorter:hover { text-decoration: underline; }
.sort-arrow { width: 9px; height: 9px; }
.sort-ord {
  font-size: 9px;
  vertical-align: super;              /* .orderOrderText */
  opacity: .85;
}

/* action links above the grid — legacy .resetOrder app.css:2528-2538 */
.actionlinks {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-4);
  padding: var(--sp-2) 0;
}
.actionlink {
  font: 700 10px/1 var(--chrome-font);
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--teal);
  text-decoration: none;
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
}
.actionlink::before { content: "\203A"; margin-right: 5px; color: var(--teal-soft); }
.actionlink:hover { color: var(--teal-dark); text-decoration: underline; }
.actionlink[disabled], .actionlink.is-disabled {
  color: var(--muted);
  cursor: not-allowed;
  text-decoration: none;
}
.actionlink.is-disabled:hover { text-decoration: none; }

/* A format the download popup refuses stays in place carrying legacy's own reason
   as its tooltip. It has to be a <span> and not a disabled <button>: Chrome fires
   no mouse events on a disabled control, so the tooltip — the entire point — would
   never appear. This rule is what makes it read as refused. */
.chip-button.is-disabled {
  color: var(--muted);
  cursor: not-allowed;
  padding: 2px 6px;
}

/* the leaflet report's module checkboxes, on one line before its chip */
.report-modules { display: inline-flex; flex-wrap: wrap; gap: 2px 10px; font-size: 11px; }
.report-modules input[type="checkbox"] { margin: 0 3px 0 0; vertical-align: -2px; }

/* The leaflet detail lists its downloads in the head's side column, a third
   of the width the search screens give the same list under their grid. Every
   row wraps there (`.exports-row`); this lays the wrap out — the format on a
   line of its own, then the file and its size, then what it holds and until
   when — with a rule between two files (seen on gpu05 2026-10-29). */
.exports-stacked .exports-row { padding: 4px 0; }
.exports-stacked .exports-row + .exports-row { border-top: 1px solid var(--border-soft); }
.exports-stacked .exports-fmt { flex-basis: 100%; }
.exports-stacked .chip-button { overflow-wrap: anywhere; }

/* paginator — legacy dataPaginator.xhtml + app.css:3823 record line.
   The record line sits ABOVE the buttons and both hug the left edge, which is
   how ice:dataPaginator stacks them (text first, button table beneath) —
   until 2026-08-18 the clone had them side by side with the buttons pushed
   right. */
.paginator {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--sp-1);
  padding: var(--sp-2) 0;
}
.paginator-info { color: #444444; font-size: 11px; font-style: italic; }
.paginator-pages { display: flex; align-items: center; gap: 2px; }
.pager {
  all: unset;
  cursor: pointer;
  min-width: 22px;
  padding: 3px 6px;
  text-align: center;
  font: 700 11px/1 var(--chrome-font);
  color: var(--teal-dark);
  background: #fff;
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
}
.pager:hover:not(:disabled) { background: var(--row-2); border-color: var(--teal-soft); }
.pager.is-current {
  background: var(--teal);
  border-color: var(--teal);
  color: #fff;
  cursor: default;
}
.pager:disabled { opacity: .4; cursor: not-allowed; }

/* ===========================================================================
   Placeholder pages (screens whose port has not started)
   =========================================================================== */
.placeholder { color: var(--slate); }
.placeholder-lead {
  font: 700 15px/1.3 var(--chrome-font);
  color: var(--slate);
  margin: 0 0 var(--sp-2);
}
.placeholder-note { margin: 0 0 var(--sp-3); max-width: 62ch; }
.placeholder-src { color: var(--muted); font-size: 11px; margin: 0 0 var(--sp-3); }
.placeholder-src code {
  background: var(--row-2);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  padding: 1px 5px;
}
.placeholder-back a { font-weight: 700; }

/* ---------------------------------------------------------------------------
   The error page (app/web/errors.py). No legacy counterpart: legacy has no
   address to get wrong, so this is the clone's own screen and follows the
   placeholder's shape rather than anything measured. The status number is
   large and muted — recognisable at a glance, and readable back over a phone
   without reading out a sentence.
   --------------------------------------------------------------------------- */
/* The panel spans the content area, as every other panel on every other screen
   does — capping the PANEL instead left a small box adrift in the top-left
   corner of an empty page, which reads as a broken layout rather than a
   designed one. What is capped is the text measure. */
.errorpage-status {
  font: 700 40px/1 var(--chrome-font);
  color: var(--border);
  margin: 0 0 var(--sp-1);
  font-variant-numeric: tabular-nums;
}
.errorpage-head {
  font: 700 17px/1.3 var(--chrome-font);
  color: var(--slate);
  margin: 0 0 var(--sp-2);
}
.errorpage-lead { margin: 0 0 var(--sp-3); max-width: 62ch; }
.errorpage-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-4);
  margin: 0 0 var(--sp-3);
}
.errorpage-where { color: var(--muted); font-size: 11px; margin: 0; }
.errorpage-where code {
  background: var(--row-2);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  padding: 1px 5px;
}

/* ===========================================================================
   Login (legacy incWelcome.xhtml loginBox — app.css:4160-4200)
   =========================================================================== */
.login-page {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--sp-4);
  padding: var(--sp-5) var(--sp-4);
  background: radial-gradient(1200px 500px at 50% -10%, #E7EFF1 0%, var(--page-bg) 70%);
}
.login-card {
  width: 100%;
  max-width: 380px;
  background: var(--panel);
  border: 1px solid #D0DDE1;          /* login box border app.css:4173 */
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-md);
  overflow: hidden;
}
.login-hero {
  display: block;
  width: 100%;
  border-bottom: 1px solid var(--border);
}
.login-body { padding: var(--sp-4) var(--sp-5) var(--sp-5); }
.login-title {
  font: 700 15px/1.2 var(--chrome-font);
  letter-spacing: .5px;
  text-transform: uppercase;
  color: var(--slate);
  margin: 0 0 var(--sp-3);
}
.login-body label { display: block; margin-bottom: var(--sp-3); color: var(--slate); }
.login-body label .lbl {
  display: block;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: .8px;
  color: var(--muted);
  margin-bottom: 3px;
}
.login-body .check {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
}
.login-body .check input { width: auto; }
.login-body button[type=submit] { width: 100%; padding: 10px; }
.login-links {
  display: flex;
  justify-content: space-between;
  gap: var(--sp-3);
  margin-top: var(--sp-4);
  padding-top: var(--sp-3);
  border-top: 1px solid #E4ECEF;
  font-size: 12px;
}
.login-foot { color: #5d7078; font-size: 11px; }
.login-brand { display: flex; align-items: center; gap: var(--sp-2); }
.login-brand img { height: 30px; }

/* ===========================================================================
   Footer + HTMX indicator
   =========================================================================== */
.appfooter {
  padding: var(--sp-2) var(--sp-4);
  color: #5d7078;
  font-size: 11px;
  border-top: 1px solid #BFCED3;
}
/* the running version (git describe / image tag) — legible, not loud */
.appfooter-version {
  font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  font-size: 11px;
  color: #3F4E56;
  user-select: all;
}

.htmx-indicator { display: none; margin-left: var(--sp-3); color: var(--muted); }
.htmx-request .htmx-indicator, .htmx-request.htmx-indicator { display: inline; }
form.htmx-request button[type=submit] { opacity: .55; }

/* ===========================================================================
   Narrow screens (the legacy app was desktop-only)
   =========================================================================== */
@media (max-width: 900px) {
  .topbar { height: auto; flex-wrap: wrap; padding: var(--sp-2) var(--sp-3); gap: var(--sp-2); }
  .brand { border-right: none; padding-right: 0; }
  .mainnav { order: 3; flex-basis: 100%; overflow-x: auto; }
  .mainnav-item { min-width: 64px; padding: 6px 8px; }
  .utility { margin-left: auto; }
  .field > .focus-label { flex-basis: 110px; }
  /* One column below the breakpoint — a fixed multi-column arrangement cannot
     shrink past the 135px label gutter. Every `cols-N` is listed so this wins on
     specificity rather than by relying on `:where()`, which older browsers that
     the port has to support would drop along with the whole rule. (The grouping
     box is already single-column at every width since D21.) */
  .field-grid.cols-1, .field-grid.cols-2, .field-grid.cols-3 {
    --cols: 1;
    max-width: none;
  }
  /* the placeholder cell has nothing to hold open in a one-column grid */
  .field-spacer { display: none; }
  /* one column: the price-type radios wrap, and nothing is left to span */
  .report-grid .radio-row { flex-wrap: wrap; }
  .report-grid.cols-3 .radio-field { grid-column: auto; }
  /* the side column drops below the fields, which the flex layout used to do by
     wrapping (D30) */
  .search-form-layout { grid-template-columns: minmax(0, 1fr); }
  .search-form-layout > .modules { grid-column: auto; }
}

/* ===========================================================================
   Charts (app/charts/ — server-rendered SVG; docs/CHARTS.md §9 stage 0)
   Sixteen categorical slots: the validated placeholder palette from CHARTS.md
   §8.2 (0-7) plus a lightness-stepped second round (8-15), validated as one
   sequence 2026-08-23. A brand re-step edits these AND app/charts/PALETTE,
   then must pass tools/validate_chart_palette.py; tools/test_charts.py pins
   the two lists to each other.
   =========================================================================== */
:root {
  --chart-s0: #2a78d6;   /* blue    */
  --chart-s1: #eb6834;   /* orange  */
  --chart-s2: #1baf7a;   /* aqua    */
  --chart-s3: #eda100;   /* yellow  */
  --chart-s4: #e87ba4;   /* magenta */
  --chart-s5: #008300;   /* green   */
  --chart-s6: #4a3aa7;   /* violet  */
  --chart-s7: #e34948;   /* red     */
  --chart-s8: #519eff;   /* blue, light step    */
  --chart-s9: #c14100;   /* orange, dark step   */
  --chart-s10: #19855d;  /* aqua, dark step     */
  --chart-s11: #ba8019;  /* yellow, dark step   */
  --chart-s12: #bf5680;  /* magenta, dark step  */
  --chart-s13: #3da938;  /* green, light step   */
  --chart-s14: #695fcf;  /* violet, light step  */
  --chart-s15: #b81824;  /* red, dark step      */
  /* fixed-MEANING fills (legacy setFixedColors, gross reach): the legacy pair
     #be0000/#007f00 is deutan dE 1.0, so the clone separates it in lightness —
     measured in app/charts/__init__.py (FIXED), pinned by test_charts.py */
  --chart-missed: #9e1a1a;   /* dark red   */
  --chart-reached: #41a353;  /* light green */
  /* the value-axis gridlines of the axis-bearing charts. Named here rather than
     reused from --border-soft because the PDF back-end has no stylesheet and
     has to be told the value (app/charts NEUTRAL_GRID, pinned by
     test_charts.py). It read `var(--line)` until 2026-08-24 — a token that was
     never defined, so the gridlines did not render at all. */
  --chart-grid: #C1C9CC;
}

.chart {
  display: flex;
  align-items: center;
  gap: var(--sp-4);
  margin: var(--sp-3) 0;
}
.chart svg { width: min(280px, 100%); height: auto; flex: none; }
.chart figcaption { font-weight: 600; color: var(--slate); }

/* one class carries the slot color; fills and swatches read currentColor */
.chart-s0 { color: var(--chart-s0); }
.chart-s1 { color: var(--chart-s1); }
.chart-s2 { color: var(--chart-s2); }
.chart-s3 { color: var(--chart-s3); }
.chart-s4 { color: var(--chart-s4); }
.chart-s5 { color: var(--chart-s5); }
.chart-s6 { color: var(--chart-s6); }
.chart-s7 { color: var(--chart-s7); }
.chart-s8 { color: var(--chart-s8); }
.chart-s9 { color: var(--chart-s9); }
.chart-s10 { color: var(--chart-s10); }
.chart-s11 { color: var(--chart-s11); }
.chart-s12 { color: var(--chart-s12); }
.chart-s13 { color: var(--chart-s13); }
.chart-s14 { color: var(--chart-s14); }
.chart-s15 { color: var(--chart-s15); }
.chart-other { color: var(--muted); }        /* the fold bucket, never a series hue */
.chart-missed { color: var(--chart-missed); }
.chart-reached { color: var(--chart-reached); }

/* 2px surface gap between touching fills — the spacer, not a border */
.chart-slice { fill: currentColor; stroke: var(--panel); stroke-width: 2; }
.chart-slice:hover { filter: brightness(1.09); }

/* inside-slice labels: the one place text sits on a fill — ink by luminance */
.chart-lbl {
  font-size: 15px;
  text-anchor: middle;
  dominant-baseline: middle;
  pointer-events: none;
}
.chart-lbl-ink { fill: var(--text); }
.chart-lbl-light { fill: #fff; }

.chart-legend {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
  font-size: 12px;
  min-width: 0;
}
.chart-legend li { display: flex; align-items: center; gap: var(--sp-2); }
.chart-legend b { margin-left: auto; padding-left: var(--sp-3); font-weight: 600; }
.chart-swatch {
  width: 10px;
  height: 10px;
  border-radius: 2px;
  flex: none;
  background: currentColor;
}
.chart-empty { color: var(--muted); }

/* the two pies of the content-structure tab sit side by side and wrap */
.chart-row { display: flex; flex-wrap: wrap; gap: var(--sp-5); align-items: flex-start; }
.cs-chart-form button.actionlink { background: none; border: 0; padding: 0; font: inherit; cursor: pointer; }

/* stacked bars (app/charts/bar.py — gross reach, CHARTS.md §9 stage 2) */
.chart-bars { align-items: flex-start; flex-direction: column; }
.chart-bars svg { width: min(520px, 100%); }
.chart-bar-label { font-size: 13px; fill: var(--slate); text-anchor: end; dominant-baseline: middle; }
.chart-bar-lbl { font-size: 12px; }
.chart-bar-total { font-size: 12px; fill: var(--muted); dominant-baseline: middle; }
.chart-bars .chart-legend { flex-direction: row; flex-wrap: wrap; gap: var(--sp-3); }

/* gross reach: chart beside its grid, as legacy's two-column panelGrid */
.reach-chart-row { display: flex; flex-wrap: wrap; gap: var(--sp-5); align-items: flex-start; }
.reach-chart-row > .chart { flex: 1 1 300px; min-width: 260px; margin: 0; }
.reach-chart-row > .table-scroll { flex: 2 1 420px; }

/* axis-bearing charts (app/charts/line.py + column.py — result charts, stage 3) */
.chart-xy { flex-direction: column; align-items: stretch; max-width: 100%; }
.chart-scroll { overflow-x: auto; max-width: 100%; }
.chart-scroll svg { height: 380px; width: auto; max-width: none; display: block; }
.chart-line { fill: none; stroke: currentColor; stroke-width: 2; stroke-linejoin: round; }
.chart-dashed { stroke-dasharray: 6 4; }
.chart-pt { fill: currentColor; stroke: var(--panel); stroke-width: 1; }
.chart-pt-ghost { fill: transparent; stroke: none; }
.chart-grid { stroke: var(--chart-grid); stroke-width: 1; }
.chart-tick { font-size: 11px; fill: var(--slate); }
.chart-tick-y { text-anchor: end; dominant-baseline: middle; }
.chart-tick-x { text-anchor: end; }
.chart-xy .chart-legend { flex-direction: row; flex-wrap: wrap; gap: var(--sp-3); margin-top: var(--sp-2); }

/* the result-charts panel above the grid (price_search/_charts.html) */
.charts-panel { margin: var(--sp-3) 0; }
.charts-head { display: flex; justify-content: space-between; align-items: baseline; }
.charts-subtitle { margin: 2px 0; font-size: 12.5px; color: var(--muted); }
.charts-row { display: flex; flex-wrap: wrap; gap: var(--sp-5); align-items: flex-start; }
.charts-row > .chart { flex: 1 1 340px; min-width: 300px; max-width: 100%; }
.charts-controls { margin-top: var(--sp-3); display: flex; flex-direction: column; gap: var(--sp-2); align-items: flex-start; }
.charts-inputs { display: flex; flex-wrap: wrap; gap: var(--sp-2) var(--sp-4); align-items: center; }
/* a select here keeps its own width: the global `width: 100%` resolved against
   this shrink-to-fit row made each date list as wide as the row and pushed To
   onto a second line under From (Redmine #19044, the Segments chart panel;
   legacy's `panelGrid columns="6"` keeps the pair on one row) */
.charts-inputs select { width: auto; }
/* the widget's own two downloads sit under REFRESH, ruled off from the controls
   above them the way legacy's chart widget separates its resource links */
.charts-exports { border-top: 1px solid var(--border-soft); padding-top: var(--sp-2); }
.chart-link { background: none; border: 0; padding: 0 0 0 4px; cursor: pointer; color: var(--accent, #2f6f9e); vertical-align: middle; }
.chart-icon { width: 15px; height: 15px; display: inline-block; }

/* stage 3b: clickable points and the drill-down popup grid */
.chart-hit { cursor: pointer; }
.chart-hit:hover { filter: brightness(1.15); }
.chart-drill { margin: var(--sp-3) 0; max-width: 560px; }
.chart-drill .table-scroll { max-height: 340px; overflow-y: auto; }
.th-sort { background: none; border: 0; padding: 0; font: inherit; color: inherit; cursor: pointer; }
.drill-pager { display: flex; align-items: center; gap: var(--sp-2); margin-top: var(--sp-2); }
.pager-info { font-size: 12px; color: var(--muted); }

/* --- the report generator's panel (REPORT_GENERATOR.md stage 1) -----------
   Legacy's `pivotContainer` is a three-column layout: controls on the left,
   the axes and the result in the middle, a draggable dimension list on the
   right. Decision D1 replaces the drag with selects, which makes that third
   column redundant — so this is two columns, keeping legacy's proportions
   (a narrow control rail beside the result). */
.org { display: flex; flex-wrap: wrap; gap: var(--sp-4); align-items: flex-start;
       margin: var(--sp-3) 0; }
.org-controls { flex: 0 0 220px; display: flex; flex-direction: column;
                gap: var(--sp-2); }
.org-main { flex: 1 1 480px; min-width: 320px; }
.org-block { border: 1px solid var(--border-soft); border-radius: 3px;
             padding: var(--sp-2); margin: 0; }
.org-block > legend { padding: 0 4px; font-size: 12px; color: var(--muted); }
.org-radio { display: block; font-size: 13px; }
.org-field { display: flex; flex-direction: column; gap: 2px; font-size: 12px;
             color: var(--muted); }
.org-field > select, .org-field > input { font: inherit; font-size: 13px;
                                          color: var(--fg); }
.org-axes { display: flex; flex-wrap: wrap; gap: var(--sp-4);
            margin-bottom: var(--sp-3); }
.org-result { border: 1px solid var(--border-soft); border-radius: 3px;
              padding: var(--sp-3); }
.org-placeholder { margin: 0 0 var(--sp-2); font-size: 13px; color: var(--muted); }
.org-placeholder .tnum { font-variant-numeric: tabular-nums; color: var(--fg); }

/* the cross-table itself (stage 2): the corner cell carries legacy's
   `<left> \ <top>` header and the row heads are th, so a screen reader can
   name the cell's row as well as its column */
.org-table .org-corner { white-space: nowrap; }
.org-table .org-rowhead { text-align: left; font-weight: 600;
                          white-space: nowrap; }
.org-result .table-scroll { max-height: 60vh; }

/* the filter row (stage 3): legacy's five drop targets, as pairs of selects */
.org-filters { display: flex; flex-wrap: wrap; gap: var(--sp-3) var(--sp-4);
               margin-bottom: var(--sp-3); }
.org-filter { display: flex; align-items: flex-end; gap: var(--sp-2); }
.org-drop { background: none; border: 0; padding: 0 2px; line-height: 1;
            font-size: 16px; color: var(--muted); cursor: pointer; }
.org-drop:hover { color: var(--teal-dark); }

/* the report generator's chart display types (stage 4): the pies sit in the
   same flex row the result charts use, each under its row's own name */
.org-pie { flex: 0 1 300px; }
.org-pie-window { margin-bottom: var(--sp-3); }
.org-result .chart-scroll { max-width: 100%; overflow-x: auto; }
.org-charts-title { margin: 0 0 var(--sp-2); font-weight: 600; }

/* MBD compare (docs/OPTIMIZERS.md §5 stage 3d). A market cell is its own htmx
   swap target (a level switch replaces one side), and dissolves into the form
   grid like the level blocks above. The summary panel sits RIGHT of the data
   table — the grid macro's `aside`, `.table-aside` above — legacy's
   `panelGrid columns="2"` (incMbdCompare.xhtml:639), with its two
   sub-header rows (`mbdCompareSummaryTableSubHeader`). */
.field-grid > .mc-cell { display: contents; }
.mc-radios { display: flex; flex-wrap: nowrap; white-space: nowrap;
             gap: var(--sp-1) var(--sp-3); }   /* one line, as legacy */
/* `table.` on purpose: a bare `.mc-summary` loses to `table.datatable`'s
   width: 100 %, and the summary then takes the whole row (verify_assets). */
table.mc-summary { flex: 0 0 auto; width: auto; }
.mc-summary td.num { text-align: right; white-space: nowrap; }
.mc-summary tr.mc-sub th { text-align: left; }

/* THE OPTIMIZER FORMS — the layout pass of 2026-10-25 (docs/OPTIMIZERS.md §5
   stage 5), compared tab by tab with production. What the three search forms
   share beyond the search screens' own rules (the footer's left/right split
   was first scoped here, then given to every search footer the same evening —
   `.search-footer` above):
   - A chip stays on one line: its text is legacy's label, which legacy never
     wraps. The chip fields carry no empty 135px gutter (the page templates,
     as Price search's `ignore_promotion_info` chip), so even the long German
     ones fit the track; the one that does not — `bei der Aggregation die
     PromotionInfo ignorieren` — runs on into the free space right of the
     form, as its 244px label cell does in legacy. */
.optimizer-form .field > .field-input > .chip { white-space: nowrap; }
/* MBD compare's form: legacy's cells are as wide as the two level radio rows
   (`KeyAccount · KeyAccount Spezial · Account` on one line — 240px at its
   10.5px, ~290px here), so its two tracks are 440px, not 330px, and the
   controls keep the width they have on the other two tabs, as legacy's
   186px `.mediumWidthSelect` stays 186px in the wider cell. In a 330px track
   the radios overflowed their cell and painted over "MBD Level 2"
   (2026-10-25). The radios and the chips take the whole cell. */
.mbd-form .search-form-layout { --form-col: calc(2 * 440px + var(--sp-5)); }
.mbd-form .field-grid { --col-max: 440px; }
.mbd-form .field > .field-input:not(.mc-radios):not(:has(> .chip)) { max-width: 187px; }
.mbd-form .mc-radios { font-size: 12px; gap: var(--sp-1) var(--sp-2); }
/* My leaflet compare's list of the leaflet's planned lines: legacy's nested
   `ice:dataTable` is as wide as its content, with a row of air above and
   below it (incMyLeafletCompare.xhtml:120; 14px / 20px on production). It
   used to stretch across the field columns and touch the rows around it. */
.ml-aktion { margin: var(--sp-2) 0 var(--sp-3); }
.ml-aktion .table-scroll { width: fit-content; max-width: 100%; }
.ml-aktion table.datatable { width: auto; }

/* --- Reports pre-selection panel (reports/_preselection.html) ---------------
   Legacy incReportPreSelection.xhtml: fourteen bordered boxes of radios, ~600px
   wide, four radios a line (app.css:4432-4466 there), the inline list below
   them, the sets and the values panel to the right. */
.presel-layout { display: flex; flex-wrap: wrap; gap: 1rem; align-items: flex-start; }
.presel-main { flex: 0 1 620px; min-width: 0; }
.presel-side { flex: 1 1 280px; min-width: 240px;
  /* one column as wide as its widest child — the Load/Save grid — so the
     chosen-values box below is exactly that wide, as legacy's is */
  display: grid; grid-template-columns: minmax(0, max-content); align-content: start; }
/* the chosen values (legacy multiSearchValues under the Load/Save grid): the
   full width of that grid, the title centred, a gap under "(remove all)" —
   scoped here, the search screens keep their 220px side box */
.presel-msv .msv-block { max-width: none; }
.presel-msv .msv-block > summary { text-align: center; }
.presel-msv .msv-row-all { margin-bottom: 4px; }
.presel-grid { border: 1px solid var(--border); border-radius: 4px; }
.presel-group { display: flex; flex-wrap: wrap; padding: 2px 6px;
  border-top: 1px solid var(--border-soft); }
.presel-group:first-child { border-top: 0; }
.presel-radio { flex: 0 0 148px; display: flex; gap: 4px; align-items: center;
  padding: 2px 0; cursor: pointer; }
.presel-codetype { margin: 2px 0; }
.presel-list { margin-top: .5rem; border: 1px solid var(--border); padding: 6px; }
.presel-tabs { display: flex; gap: 2px; margin-bottom: 6px; }
.presel-tab { border: 1px solid var(--border); background: transparent; padding: 2px 10px;
  color: inherit; text-transform: uppercase; font-size: .8em; }
/* the tabs draw their own frame, so the darker edge of a hovered button is
   theirs to set; before .active, whose bottom edge stays open */
.presel-tab:hover { border-color: var(--teal-dark); }
.presel-tab.active { font-weight: 600; border-bottom-color: transparent; }
/* The value list under the radios — legacy's search row is ONE line: the
   label, a narrow input, FIND and CANCEL beside it. The input inherited the
   global `width: 100%` and pushed the buttons onto a line of their own. */
.presel-search { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
.presel-search .focus-label { font-size: 12px; color: var(--slate); }
.presel-search input[type=text] { flex: 0 0 187px; width: 187px; }
.presel-list .paginator-info { margin: 6px 0 4px; }
/* the pager's buttons at the size of their labels, as legacy's are (they
   inherited the 8×16px padding of an ordinary button) */
.presel-pages { display: flex; flex-wrap: wrap; gap: 2px; margin: 0 0 6px; }
.presel-values-grid { width: 100%; border-collapse: collapse; }
/* rows as dense as legacy's (~18px), not the body's 1.55 line height */
.presel-values-grid td, .presel-values-grid th { padding: 1px 4px; text-align: left;
  line-height: 1.3; }
/* the column heading sits apart from the rows, as legacy's header row does */
.presel-values-grid thead th { border-bottom: 1px solid var(--border); padding-bottom: 3px; }
.presel-values-grid .num { text-align: right; }
.presel-add { border: 0; background: transparent; padding: 0; text-align: left; cursor: pointer;
  text-transform: none; letter-spacing: normal; font: inherit; color: inherit; }
/* the paste box as legacy draws it: narrow and tall (~275 x 310px), one value
   per line being the common case — not the panel's full width at 8 rows */
.presel-paste textarea { width: 275px; max-width: 100%; height: 310px; }
.presel-delims { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 4px; }
/* Load / Save — legacy's `panelGrid columns="3"` (incReportPreSelection.xhtml:
   336-365): label, a narrow listbox or input, its button right beside it. The
   middle column used to be `1fr`, stretching both fields across the whole side
   column with the buttons at its far edge; it is the report forms' input width
   now (a 330px track less the 135px label gutter and the gap). */
.presel-sets { display: grid; grid-template-columns: auto 187px auto; gap: 4px 6px;
  justify-content: start; align-items: center; margin-bottom: 8px; }
.presel-save { display: contents; }

/* THE REPORT FORMS — legacy's three panelGrids per tab (reports/incReport*.xhtml):
   the criteria (`columns="4"`, or `"6"` on Penetration development and the
   Promotion calendar — two or three label+input pairs a row), the grouping box,
   and the footer grid of rows per page, count on level and the buttons, the
   buttons being simply the grid's next cell. */
/* The price type is an ordinary label+input pair, its five radios on one line
   as legacy's table cell lets them run (2026-10-11: it was a boxed fieldset
   whose options wrapped over two lines). Beside an empty third cell it takes
   that cell's room. */
.report-grid .radio-row { display: flex; flex-wrap: nowrap; align-items: center;
                          gap: 2px var(--sp-3); }
.report-grid .radio-row label { white-space: nowrap; display: inline-flex;
                                align-items: center; gap: 4px; }
.report-grid.cols-3 .radio-field { grid-column: span 2; }
/* as tall as a select row (29px), so its label lines up with its neighbour's */
.report-grid .radio-field { min-height: 29px; }
/* The level radio above Index and Segments: a row of its own, no label. */
.report-grid .report-level { margin: 0 0 var(--sp-2); }
/* legacy's `groupingBox`: the grouping listboxes framed together. The frame
   reaches 7px (6px padding + 1px border) into the panel's gutter on both
   sides, so the labels and listboxes inside stay in line with the fields above
   it, and the track widths match theirs. */
.report-grouping-box {
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  padding: var(--sp-2) 6px 2px;
  margin: var(--sp-2) -7px var(--sp-3);
  max-width: calc(var(--cols) * var(--col-max) + (var(--cols) - 1) * var(--sp-5) + 14px);
}
/* the buttons sit in the footer grid's next cell — beside rows per page, or
   under it when count on level takes the second cell — not at the far right */
.report-footer .form-actions { justify-content: flex-start; margin: 0 0 6px; }
/* the note under the buttons, close to them */
.report-form .note { margin: var(--sp-2) 0 0; }
/* The report tabs sit BELOW the pre-selection panel, as in legacy
   (incReports.xhtml: the panel is above the tab set), and attach to the
   form panel under them. */
.content > .subtabs { padding: var(--sp-3) 0 0; }
.content > .subtabs + .report-form > .panel,
.content > .subtabs + * > .panel:first-child { border-top-left-radius: 0; }

/* THE REPORT GRIDS — as dense as legacy's (11px, ~2px cell padding): the
   Penetration development grid is 27 columns, and legacy shows all of them,
   Reach included, without a horizontal scrollbar at 1540px; the clone's
   12px / 7px cells pushed the last block off screen. The table takes its
   content's width (and its frame with it) instead of stretching across the
   panel. Scoped to `.report-table`: the search screens keep their grid. */
table.datatable.report-table { width: auto; font-size: 11px; letter-spacing: 0; }
table.datatable.report-table th { padding: 3px 4px; letter-spacing: .5px; font-size: 10px;
  /* legacy's report headings read from the left ("SEP 2026", "ANZAHL ANGEBOTE"),
     group and column headings alike; the search grids centre theirs */
  text-align: left; }
table.datatable.report-table td { padding: 2px 4px;
  /* legacy's Arial at line-height normal gives a 16px text row; the clone's
     face at the inherited line height gave 20px */
  line-height: 1.15; }
/* legacy writes a report's names out in full ("Admiral Elektro Flächenheizung
   E-Power Comfort"); the search grids' 26ch ellipsis is theirs, not a report's */
table.datatable.report-table td { max-width: none; }
/* the quantity column (legacy outputQtyCntPkg) sits right, as numbers do */
table.datatable.report-table td.qty { text-align: right; }
/* Index report: a buyer's price sits over its index in one cell, set as
   tight as legacy's, so a detail row stays close to a group row's height */
table.datatable.report-table td.ix-cell { line-height: 1.1; }
/* Product space: the chart tick sits in a text row without the browser's
   3px checkbox margin, which made the row 23px against legacy's 21px */
table.datatable.report-table input.ps-tick { margin: 0 2px; vertical-align: middle; }
/* Promotion calendar: legacy sets every cell on the bottom line, so a name
   reads level with the price under its "1 / 1" */
table.datatable.report-table.pc-grid td { vertical-align: bottom; }
.table-scroll:has(> table.report-table) { width: fit-content; max-width: 100%; }
/* the sortable headings' button carried an ordinary button's 8x16px padding,
   which alone made every sortable column ~32px wider than its label */
table.report-table th .sort-link { background: none; border: 0; padding: 0;
  font: inherit; color: inherit; letter-spacing: inherit; text-transform: inherit;
  cursor: pointer; }
/* the report pager's buttons at the size of their labels, as legacy's are */
.paginator-pages .pg { padding: 4px 9px; letter-spacing: .5px; min-width: 26px; }
.results > .paginator-info, .results .paginator-info { margin: 6px 0 4px; }
.results .paginator-pages { margin: 0 0 6px; }

/* The Promotion calendar's current ISO week (legacy `currentWeek`), and its
   inline cell popup. */
.report-table .current-week { background: var(--nav-hover-bg); }
.pc-detail { margin-top: var(--sp-4); padding: var(--sp-3); }
.pc-detail .panel-head { display: flex; justify-content: space-between; align-items: center; }

/* The Index report's group rows and their fold toggle. */
.report-table tr.ix-group td { font-weight: 600; background: var(--nav-hover-bg); }
.fold-toggle { border: 0; background: none; cursor: pointer; font-size: 14px;
               color: var(--teal); padding: 0 4px; }

/* ===========================================================================
   Media Control — the Charts tab tree (docs/MEDIA_CONTROL.md stage 1c).
   Legacy upper-cases every tab label in CSS (stage 0, 2026-10-12); the labels
   themselves are data from mc_group_tree and are not translated.
   =========================================================================== */
.mc-tabs { padding-left: 0; }
.mc-tab { text-transform: uppercase; cursor: pointer; }
.mc-panel > .mc-tabset { margin-top: var(--sp-2); }
.mc-check-all { display: inline-flex; align-items: center; gap: var(--sp-1); font-size: 12px; }
/* legacy mediaControlCheckBoxCell (app.css:4744-4787): 18em x 12em tiles, a
   thin #eee frame, the picture centred below the tick at most 7em */
.mc-items { display: flex; flex-wrap: wrap; gap: 2px; margin: var(--sp-2) 0; }
.mc-item {
  width: 18em; height: 12em; box-sizing: border-box; overflow: hidden;
  border: thin solid #eeeeee; border-radius: 5px; padding: 4px;
  display: flex; flex-direction: column; align-items: stretch; font-size: 12px;
}
.mc-item > label { display: flex; align-items: flex-start; gap: 4px; min-height: 3.5em; }
.mc-item-picture { max-width: 7em; max-height: 7em; align-self: center; margin-bottom: 7px; }
.mc-leaf-top, .mc-leaf-bottom { display: flex; align-items: center; gap: var(--sp-3); margin: var(--sp-1) 0; }
/* The three columns (legacy mediaControlContainer, a 3-column panelGrid) */
.mc-columns { display: flex; align-items: flex-start; gap: var(--sp-3); }
.mc-col-left { flex: 0 0 220px; display: flex; flex-direction: column; gap: var(--sp-2); }
.mc-col-centre { flex: 1 1 auto; min-width: 0; }
.mc-col-right { flex: 0 0 auto; }
.mc-col-right:empty { display: none; }
/* legacy app.css:4799-4813: uppercase legends at .85em, a 1px #CACACA frame, radius 5px */
.mc-radio-group { border: 1px solid #CACACA; border-radius: 5px; padding: var(--sp-1) var(--sp-2); margin: 0; }
.mc-radio-group > legend { text-transform: uppercase; font-size: .85em; padding: 0 4px; color: var(--slate); }
.mc-radio { display: flex; align-items: center; gap: 5px; font-size: 12px; padding: 1px 0; }
.mc-brand-group { max-width: none; }
.mc-brand-values { max-height: 260px; overflow-y: auto; }
/* the centre's top row: the dates left, the manual floated right (app.css:4815-4822) */
.mc-top-row { display: flex; align-items: flex-end; gap: var(--sp-3); margin-bottom: var(--sp-2); }
.mc-dates { display: flex; gap: var(--sp-2); }
.mc-manual { margin-left: auto; font-size: 12px; }
.mc-aggregation { margin: 0 0 var(--sp-2); }
/* the organisation tree, right column: fully expanded on open */
.mc-org-tree { font-size: 12px; white-space: nowrap; }
.mc-org-node > summary { cursor: pointer; padding: 1px 0; }
.mc-org-node > summary label { cursor: pointer; }
.mc-org-children { list-style: none; margin: 0; padding: 0 0 0 22px; }
.mc-org-children li { padding: 1px 0; }
/* the article-group tree, left column bottom: closed on open, " (n)" after a parent */
.mc-ag-block { max-width: none; }
.mc-ag-filter { display: flex; gap: 4px; margin: 4px 0; }
.mc-ag-filter input { flex: 1; min-width: 0; font-size: 11px; }
.mc-ag-tree { max-height: 420px; overflow: auto; white-space: nowrap; }
.mc-ag-children { list-style: none; margin: 0; padding: 0 0 0 16px; }
.mc-ag-top { padding-left: 0; }
.mc-ag-node > summary { cursor: pointer; }
.mc-ag-leaf { display: inline-flex; gap: 4px; padding-left: 12px; }
/* the pies: two in a row, as legacy's 2-column panelGrid (§1.8) */
.mc-pies { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--sp-3); }
/* the group-tree search: the term in the top row, the paths below it */
.mc-tree-term input { width: 160px; }
.mc-tree-search { display: flex; align-items: flex-start; gap: var(--sp-2); margin: 0 0 var(--sp-2); }
.mc-tree-search-grid td { white-space: nowrap; }
/* the CI report: Find and the bold hint above, merged groupOn cells, images at full size */
/* legacy's left column (stage 0 screenshot): the five brand groups share one
   scroll box, each under a teal bar with its title centred in white capitals */
.mc-brand-box { max-height: 430px; overflow-y: auto; border: 1px solid var(--border);
  background: var(--block-bg); }
.mc-brand-box .mc-brand-group { border: none; border-radius: 0; padding: 0; background: none; }
.mc-brand-box .mc-brand-group > summary { background: var(--teal); color: #fff; text-align: center;
  font-weight: 700; padding: 2px 4px; list-style: none; }
.mc-brand-box .mc-brand-group > summary::-webkit-details-marker { display: none; }
.mc-brand-box .mc-brand-values { max-height: none; overflow: visible; padding: 2px 4px; }
/* the section tabs inside the centre column */
.mc-col-centre > .subtabs { padding-left: 0; }
/* visual pass against legacy (2026-10-13 screenshots): the three columns as three
   panels, the dates in a light box, the section tabs in capitals, every nested
   tab set framed, the article-group tree under the same teal bar as the brands,
   the aggregation's label above its select */
.mc-section > .panel-body { background: none; padding: 0; }
.mc-columns > .mc-col { background: var(--panel); border: 1px solid var(--border);
  border-radius: var(--r-md); padding: var(--sp-2); }
.mc-top-row { background: var(--block-bg); border: 1px solid var(--border); border-radius: var(--r-md);
  padding: 4px var(--sp-2); }
.mc-col-centre > .subtabs .subtab { text-transform: uppercase; }
.mc-panel > .mc-tabset, .mc-col-centre > .mc-tabset { border: 1px solid var(--border);
  border-radius: var(--r-md); padding: 4px var(--sp-2) var(--sp-2); }
.mc-ag-block { border-radius: 0; padding: 0; }
.mc-ag-block > summary { background: var(--teal); color: #fff; text-align: center; font-weight: 700;
  padding: 2px 4px; list-style: none; }
.mc-ag-block > summary::-webkit-details-marker { display: none; }
.mc-ag-block > .mc-ag-filter, .mc-ag-block > .mc-ag-tree { margin: 4px; }
.mc-aggregation { display: flex; flex-direction: column; gap: 2px; font-size: 12px; margin-bottom: var(--sp-2); }
.mc-org-filter { margin-bottom: 4px; }
/* the chart keeps its natural size (scaling the SVG up enlarges its text too);
   the figure is as wide as the plot so the x title sits under it */
.mc-result .chart-xy { display: inline-block; }
.chart-x-title { text-align: center; font-size: 11px; margin: 2px 0 0; }

/* ===========================================================================
   Media Control — density from legacy's CSS, measured side by side
   (computed styles, production 2026-10-13). The restyle of DIVERGENCES §1
   keeps the clone's font family, colours and flat buttons; the sizes,
   weights, row heights and widths below are legacy's:
   base Arial 11.2px; legends and group bars Trebuchet 9.5px capitals; tabs
   10px bold capitals; field labels 11.2px bold; checkbox rows 17px; the side
   columns about 224px; tiles 18em x 12em at the base size.
   =========================================================================== */
.mc-section { font-size: 11.2px; }
.mc-section .mc-col-left { flex-basis: 240px; }
.mc-section .mc-col-right { flex: 0 0 240px; }
.mc-radio-group { padding: 5px; margin-bottom: 0; }
.mc-radio-group > legend { font-size: 9.5px; letter-spacing: 1px; padding: 0 2px; }
.mc-radio { font-size: 11.2px; padding: 0; min-height: 20px; }
.mc-brand-box .mc-brand-group > summary,
.mc-ag-block > summary { font-size: 9.5px; font-weight: 400; letter-spacing: .5px; padding: 2px 0; }
.mc-brand-box .msv-row { font-size: 11.2px; padding: 0; min-height: 17px; align-items: center; }
.mc-brand-box .msv-value { font-size: 11.2px; }
.mc-top-row { padding: 2px 6px; align-items: center; }
.mc-top-row .field { display: flex; align-items: center; gap: 4px; margin: 0; }
.mc-top-row .focus-label { width: auto; float: none; font-size: 11.2px; font-weight: 700; }
.mc-top-row select, .mc-top-row input { font-size: 11.2px; height: 20px; padding: 0 2px; }
.mc-top-row .w-date { width: 150px; }
.mc-manual { font-size: 11.2px; }
.mc-col-centre .subtabs { padding-top: 6px; }
.mc-col-centre .subtab, .mc-tabs .subtab { font-size: 10px; padding: 4px 8px 3px; }
.mc-col-centre .subtab.is-active, .mc-tabs .subtab.is-active { padding-bottom: 4px; }
.mc-redraw { font-size: 9px; padding: 4px 10px; letter-spacing: .5px; }
.mc-check-all { font-size: 11.2px; }
.mc-items { font-size: 11.2px; }
.mc-item { font-size: 11.2px; }
.mc-aggregation { font-size: 11.2px; }
.mc-aggregation > label { font-weight: 700; }
.mc-aggregation select { font-size: 11.2px; height: 20px; }
.mc-org-tree, .mc-ag-tree { font-size: 11.2px; }
.mc-org-node > summary, .mc-org-children li, .mc-ag-children li { min-height: 17px; padding: 0; line-height: 17px; }
.mc-org-filter input, .mc-ag-filter input { font-size: 11.2px; height: 18px; }
.mc-org-filter .btn-small, .mc-ag-filter .btn-small { font-size: 9px; padding: 2px 6px; }
/* legacy's top row puts each label right before its control (no label gutter) */
.mc-top-row .field > .focus-label { flex: 0 0 auto; font-size: 11.2px; font-weight: 700; }
.mc-top-row .field-input { flex: 0 0 auto; }
.mc-aggregation select { height: auto; min-height: 20px; padding: 1px 2px; }
/* legacy's tab frame is as wide as its tabs, not the whole column */
.mc-col-centre > .mc-tabset { width: fit-content; max-width: 100%; }
/* legacy's brand box is 60em at most (multiSearchContent) and shows all five groups */
.mc-brand-box { max-height: 60em; }
/* legacy's REDRAW CHARTS is 29px high (commandButtonLink: 6px 10px 2px) */
.mc-redraw { padding: 6px 10px 4px; min-height: 29px; }
/* legacy: the count line upright, Show raw result table a plain link */
.mc-result .paginator-info { font-style: normal; font-size: 11.2px; }
.mc-result .actionlinks .actionlink { text-transform: none; font-weight: 400; font-size: 11.2px;
  letter-spacing: 0; text-decoration: underline; }

/* Media Control colours, sampled from legacy's own panel and button images on
   production (2026-10-13), at vketman's request - the section reads as legacy
   at first sight: the side columns and the dates row #E9F1F4, the tab content
   white, an inactive tab #E0EAEE with #72898C text, the buttons grey. The
   chart palette stays the clone's (DIVERGENCES §1). */
.mc-columns > .mc-col-left, .mc-columns > .mc-col-right { background: #E9F1F4; }
.mc-columns > .mc-col-centre { background: #FFFFFF; }
.mc-top-row { background: #E9F1F4; }
.mc-col-centre .subtab, .mc-tabs .subtab { background: #E0EAEE; color: #72898C; }
.mc-col-centre .subtab.is-active, .mc-tabs .subtab.is-active { background: #FFFFFF; color: #3F4E56; }
.mc-section .btn.mc-redraw, .mc-section .btn-small {
  background: #E6E6E6; color: #707C7D; border: 1px solid #C8CDCE; border-radius: 12px; }
.mc-section .btn.mc-redraw:hover, .mc-section .btn-small:hover {
  background: #DADADA; color: #3F4E56; }
.mc-section .btn-small { color: #848889; border-radius: 8px; }
/* the Media Control charts at their own size - legacy's 700 px frame (§1.8);
   the shared rule caps every chart at 380 px high */
.mc-result .chart-scroll svg { height: auto; width: auto; }

/* ===========================================================================
   Media Control — legacy's skin (visual pass 3, 2026-10-13, at vketman's
   request: "do not only look at the screen, read the CSS and do the same").
   Legacy builds its frames, tabs and buttons from sliced images in table
   cells; tools/mc_legacy_skin.py assembles the same slices, cropped as
   legacy's cells crop them, into img/mc/*.png, and border-image draws them.
   Geometry measured on production (computed styles, viewport 2447 px):
     side column   white frame 268 px (content/, 10|11|10|13) around a
                   light-blue panel 248 px (suchmaske/, 12|12|12|13); 4 px gaps
     top row       the light-blue panel, 50 px high
     tab           subPTS tab (tabs-b/): 10 px left, 15 px right (4 px of it
                   transparent - the gap to the next tab), 9 px top, 18 px
                   row; label Interstate stack 0.9em bold, .5px, capitals;
                   the tab row sits 3 px down into the frame below
     tab content   grey-edged frame (content/*_grey, 10|11|11|13), as wide as
                   its content
     REDRAW CHARTS commandButtonLink: 19 px left, 27 px right with the arrow,
                   29 px high, label Arial 0.8em bold capitals #707C7D
     inputs        searchinput.png, no border, 3px 4px 2px, #515355, .5px
     trees         ICEfaces xp tree images, 16 x 18 per level
   Overrides the two passes above; the chart palette stays the clone's.
   =========================================================================== */
.mc-section { font-family: Arial, Helvetica, sans-serif; font-size: 11.2px;
  background: none; border: none; box-shadow: none; border-radius: 0; }
.mc-section > .panel-body { padding: 0; }
.mc-columns { gap: 4px; }
.mc-columns > .mc-col { border-radius: 0; padding: 0; background: none; box-sizing: border-box;
  border-style: solid; border-width: 11px 10px 13px 10px;
  border-image: url("img/mc/frame-white.png") 11 10 13 10 fill / 11px 10px 13px 10px stretch; }
.mc-columns > .mc-col-left, .mc-columns > .mc-col-right { flex: 0 0 268px; width: 268px; }
.mc-columns > .mc-col-centre { background: none; }
.mc-pane, .mc-top-row { box-sizing: border-box; background: none; border-radius: 0;
  border-style: solid; border-width: 12px 12px 13px 12px;
  border-image: url("img/mc/frame-panel.png") 12 12 13 12 fill / 12px 12px 13px 12px stretch; }
.mc-pane { display: flex; flex-direction: column; gap: 0; }
.mc-top-row { min-height: 50px; padding: 0; margin: 0 0 3px; align-items: center; }

/* tab sets: the tab row, then the active tab's frame */
.mc-section-tabs { width: fit-content; max-width: 100%; margin-left: 3px; }
.mc-section .mc-tabset, .mc-col-centre > .mc-tabset { border: none; border-radius: 0; padding: 0;
  margin: 0; width: fit-content; max-width: 100%; }
.mc-panel > .mc-tabset { margin-top: 0; }
.mc-section .subtabs, .mc-section .mc-tabs { display: flex; gap: 0; padding: 0; margin: 0;
  overflow: visible; position: relative; top: 3px; align-items: flex-end; }
.mc-section .subtab, .mc-section .mc-tab {
  font-family: "Interstate Regular", Interstate, "Lucida Grande", Calibri, "Lucida Sans Unicode",
    "Trebuchet MS", Arial, Helvetica, sans-serif;
  font-size: 10.08px; font-weight: 700; letter-spacing: .5px; text-transform: uppercase;
  line-height: 16px; height: 27px; box-sizing: border-box; padding: 1px 3px; margin: 0;
  color: #72898C; background: none; box-shadow: none; border-radius: 0; opacity: 1;
  text-decoration: none; white-space: nowrap; cursor: pointer; transition: none;
  border-style: solid; border-width: 9px 15px 0 10px;
  border-image: url("img/mc/tab-off.png") 9 15 0 10 fill / 9px 15px 0 10px stretch; }
.mc-section .subtab.is-active, .mc-section .mc-tab.is-active {
  color: #3F4E56; padding-bottom: 1px; box-shadow: none; background: none;
  border-image-source: url("img/mc/tab-on.png"); }
.mc-section .subtab:hover, .mc-section .mc-tab:hover { color: #3F4E56; background: none; }
/* a legacy tab set is one table: the frame is as wide as the wider of the tab
   row and the content */
.mc-frame { box-sizing: border-box; width: auto; min-width: 120px; padding: 0;
  border-style: solid; border-width: 11px 11px 13px 10px;
  border-image: url("img/mc/frame-grey.png") 11 11 13 10 fill / 11px 11px 13px 10px stretch; }
.mc-frame[hidden] { display: none; }

/* REDRAW CHARTS: legacy's glossy button */
.mc-section .btn.mc-redraw {
  font-family: Arial, Helvetica, "Lucida Grande", Calibri, "Trebuchet MS", sans-serif;
  font-size: 8.96px; font-weight: 700; letter-spacing: 0; text-transform: uppercase;
  line-height: 14.56px; color: #707C7D; background: none; border-radius: 0; box-shadow: none;
  height: 29px; min-height: 29px; box-sizing: border-box; padding: 6px 10px 2px; margin: 0;
  border-style: solid; border-width: 0 27px 0 19px;
  border-image: url("img/mc/button-find.png") 0 27 0 19 fill / 0 27px 0 19px stretch; }
.mc-section .btn.mc-redraw:hover { background: none; color: #3F4E56; }
/* the tree filters' Search: legacy shows the word only */
.mc-section .btn-small { background: none; border: none; border-radius: 0; padding: 0 2px;
  font: 700 11.2px Arial, Helvetica, sans-serif; color: #000; cursor: pointer; text-transform: none;
  letter-spacing: 0; }
.mc-section .btn-small:hover { background: none; color: #3F4E56; }

/* inputs and selects on legacy's grey input strip */
.mc-section input[type="text"], .mc-section select {
  background: transparent url("img/mc/searchinput.png") right top repeat-x;
  border: 0; border-radius: 0; box-shadow: none; color: #515355; letter-spacing: .5px;
  font: 11.2px Arial, Helvetica, sans-serif; margin: 0 2px; }
.mc-section input[type="text"] { height: 19px; box-sizing: border-box; padding: 3px 4px 2px; width: 118px; }
.mc-section .mc-ag-filter input[type="text"] { border: 1px solid #fff; height: 21px; width: 120px; flex: 0 0 auto; }
.mc-section select { height: 19px; box-sizing: border-box; padding: 2px 4px; background-position: right 50%; }
.mc-top-row select, .mc-top-row .w-date { width: 188px; height: 19px; }
.mc-aggregation select { width: 188px; min-height: 0; }
.mc-top-row .focus-label, .mc-aggregation > label { font: 700 11.2px Arial, Helvetica, sans-serif; color: #000; }

/* the left column: legacy mediaControlRadioGroup and collapsible panels */
.mc-radio-group { border: 1px solid #CACACA; border-radius: 5px; padding: 5px; margin: 0 0 5px; }
.mc-radio-group > legend { font-family: "Trebuchet MS", "Lucida Grande", Calibri, Arial, Helvetica, sans-serif;
  font-size: 9.52px; font-weight: 400; letter-spacing: 1px; color: #3F4E56; padding: 0 2px; }
.mc-brand-box { border: none; background: none; }
.mc-brand-box .mc-brand-group > summary, .mc-ag-block > summary {
  background: #007D8F; color: #fff; border-radius: 5px 5px 0 0; padding: 2px;
  font-family: "Trebuchet MS", "Lucida Grande", Calibri, Arial, Helvetica, sans-serif;
  font-size: 9.52px; font-weight: 400; letter-spacing: 1px; text-transform: uppercase; line-height: 12px; }
.mc-section details.msv-block { background: none; border: none; box-shadow: none; }

/* trees: ICEfaces' xp tree images, one 16 x 18 cell per level */
/* a tree row is 22 px in legacy (the 18 px images, 2 px above and below) */
.mc-org-tree, .mc-ag-tree { font-size: 11.2px; line-height: 22px; }
.mc-org-children, .mc-ag-children { list-style: none; margin: 0; padding: 0; }
.mc-org-tree > .mc-org-node, .mc-org-children > li, .mc-ag-children > li {
  padding: 0 0 0 16px; min-height: 22px; line-height: 22px;
  background: url("img/mc/tree_line_middle_node.gif") no-repeat 0 2px,
              url("img/mc/tree_line_vertical.gif") repeat-y 0 0; }
.mc-org-tree > .mc-org-node:last-child, .mc-org-children > li:last-child, .mc-ag-children > li:last-child {
  background: url("img/mc/tree_line_last_node.gif") no-repeat 0 2px,
              url("img/mc/tree_line_vertical.gif") no-repeat 0 -16px; }
.mc-org-node > summary, .mc-ag-node > summary {
  list-style: none; margin-left: -16px; padding: 0 0 0 16px; min-height: 22px; line-height: 22px;
  background: url("img/mc/tree_nav_middle_open.gif") no-repeat 0 2px; }
.mc-org-node[open] > summary, .mc-ag-node[open] > summary {
  background-image: url("img/mc/tree_nav_middle_close.gif"); }
.mc-org-tree > .mc-org-node:last-child > summary, li:last-child > .mc-ag-node > summary {
  background-image: url("img/mc/tree_nav_bottom_open.gif"); }
.mc-org-tree > .mc-org-node[open]:last-child > summary, li:last-child > .mc-ag-node[open] > summary {
  background-image: url("img/mc/tree_nav_bottom_close.gif"); }
/* the first root node has no line above it (tree_nav_top_*) */
.mc-org-tree > .mc-org-node:first-child, .mc-ag-top > li:first-child {
  background: url("img/mc/tree_line_vertical.gif") repeat-y 0 11px; }
.mc-org-tree > .mc-org-node:first-child > summary, .mc-ag-top > li:first-child > .mc-ag-node > summary {
  background-image: url("img/mc/tree_nav_top_open.gif"); }
.mc-org-tree > .mc-org-node[open]:first-child > summary, .mc-ag-top > li:first-child > .mc-ag-node[open] > summary {
  background-image: url("img/mc/tree_nav_top_close.gif"); }
.mc-org-node > summary::-webkit-details-marker, .mc-ag-node > summary::-webkit-details-marker { display: none; }
.mc-org-node > summary::marker, .mc-ag-node > summary::marker { content: ""; }
.mc-ag-leaf { padding-left: 0; }
.mc-org-children li, .mc-ag-children li { padding-top: 0; padding-bottom: 0; }
.mc-brand-box .msv-row { min-height: 19px; }
.mc-section input[type="checkbox"], .mc-section input[type="radio"] { margin: 0 3px 0 0; vertical-align: -2px; }
/* a running Media Control search: the busy pointer everywhere, legacy's
   "searching" text where the result will be (app.js §12) */
html.mc-waiting, html.mc-waiting * { cursor: progress !important; }
.mc-busy { margin: 8px 0; font-weight: 700; color: #707C7D; }

/* Media Control's grids, pagers and charts as legacy draws them (side by side
   on production, 2026-10-13; computed styles of the CI report and the chart
   grid): Arial 10.54px rows on #D0E0E6 / #E4ECEF with #3F4E56 text, a teal
   header in Trebuchet 8.96px bold capitals with legacy's white corner images,
   page numbers as small white boxes (the current one teal) and legacy's arrow
   images, the record line upright under and over the grid. */
.mc-section table.datatable { font: 10.54px Arial, Helvetica, sans-serif; letter-spacing: .25px;
  width: auto; background: none; }
.mc-section table.datatable td { color: #3F4E56; padding: 1px 3px; border-right: 1px solid #FDFDFE; vertical-align: middle; }
.mc-section table.datatable tbody tr:nth-child(odd) td { background: #D0E0E6; }
.mc-section table.datatable tbody tr:nth-child(even) td { background: #E4ECEF; }
.mc-section table.datatable th { font: 700 8.96px "Trebuchet MS", "Lucida Grande", Calibri, Arial, sans-serif;
  letter-spacing: 1px; text-transform: uppercase; padding: 2px; background-color: #007D8F;
  border-right: 1px solid #fff; border-bottom: 0; white-space: normal; }
.mc-section table.datatable th:first-child {
  background: #007D8F url("img/mc/weisseck-left.gif") no-repeat left top; }
.mc-section table.datatable th:last-child {
  background: #007D8F url("img/mc/weisseck-right.gif") no-repeat right top; }
.mc-section .table-scroll { border: none; border-radius: 0; background: none; }
.mc-section .paginator-info { font: italic 11.2px Arial, Helvetica, sans-serif; color: #000; margin: 2px 0; }
.mc-section .paginator-pages { gap: 0; margin: 2px 0; align-items: center; }
.mc-section .paginator-pages button.pg {
  all: unset; box-sizing: border-box; cursor: pointer; text-align: center;
  font: 11.2px Arial, Helvetica, sans-serif; color: #666; background: #fff;
  border: 1px solid #D0E0E6; border-radius: 3px; padding: 0 5px; min-width: 25px; height: 16px;
  line-height: 14px; margin: 0 6px; }
.mc-section .paginator-pages button.pg.current { background: #007D8F; border-color: #007D8F; color: #fff; }
.mc-section .paginator-pages button.pg-arrow {
  width: 27px; min-width: 27px; height: 21px; margin: 0 2px; padding: 0; border: 0; border-radius: 0;
  color: transparent; font-size: 0; background: no-repeat left top; }
.mc-section .paginator-pages button.pg-previous, .mc-section .paginator-pages button.pg-next {
  width: 22px; min-width: 22px; }
.mc-section .paginator-pages button.pg-first { background-image: url("img/mc/arrow-first.png"); }
.mc-section .paginator-pages button.pg-fr { background-image: url("img/mc/arrow-fr.png"); }
.mc-section .paginator-pages button.pg-previous { background-image: url("img/mc/arrow-previous.png"); }
.mc-section .paginator-pages button.pg-next { background-image: url("img/mc/arrow-next.png"); }
.mc-section .paginator-pages button.pg-ff { background-image: url("img/mc/arrow-ff.png"); }
.mc-section .paginator-pages button.pg-last { background-image: url("img/mc/arrow-last.png"); }
.mc-section .paginator-pages button.pg:disabled { cursor: default; }
.mc-section .mc-leaflet-icon { display: block; border: 0; }
.mc-section td.leaflet-cell { padding: 0 2px; }
.mc-section .leaflet-link { background: none; border: 0; padding: 0; cursor: pointer; display: inline-block; }

/* the raw grid in place of the chart grid, one toggle link (app.js §12) */
.mc-result-body [data-mc-raw-hide] { display: none; }
.mc-result-body.mc-raw-on [data-mc-raw-hide] { display: inline; }
.mc-result-body.mc-raw-on [data-mc-raw-show], .mc-result-body.mc-raw-on .mc-chart-view { display: none; }
.mc-raw-toggle { margin: 0; }
.mc-section .actionlinks .actionlink::before { content: none; }

/* the chart: legacy's 700 px image - the legend in a framed box inside that
   width, line-and-marker keys, the axes without gridlines */
.mc-result .chart-xy { max-width: 700px; margin: 4px 0; }
.mc-result .chart-xy .chart-legend { display: flex; flex-wrap: wrap; gap: 1px 8px; width: fit-content;
  max-width: 696px; border: 1px solid #000; padding: 1px 4px; margin: 0 0 4px 4px;
  font: 10px Arial, Helvetica, sans-serif; color: #000; }
.mc-result .chart-xy .chart-legend li { gap: 3px; }
.chart-legend svg.chart-key { flex: none; overflow: visible; width: 14px; height: 10px; max-width: none; display: inline-block; }
.chart-key .chart-line { stroke-width: 1.5; }
.chart-axis { stroke: #808080; stroke-width: 1; }
.chart-y-title { font: 10px Arial, Helvetica, sans-serif; fill: #000; text-anchor: middle; }
.mc-result .chart-tick { font: 9px Arial, Helvetica, sans-serif; fill: #444; }
.mc-result .chart-line { stroke-width: 1.5; }
.mc-result .chart-pt { stroke: none; }
.mc-result .chart-x-title { font: 10px Arial, Helvetica, sans-serif; color: #000; }

/* legacy nodeWithSelectedChild: the nodes above a ticked one (app.js §12) */
.mc-section summary.mc-sel-child > label { background: #C1D4DC; font-weight: 700; color: #3b545a;
  border-radius: 4px; padding: 0 3px; }

/* Activity overview (ACTIVITY_OVERVIEW.md stage 3): legacy's one download link
   after FIND, drawn as a link although it posts the export job */
.ao-result { margin: 8px 0; }
.ao-download { display: inline; }
button.link-button { background: none; border: 0; padding: 0;
  cursor: pointer; color: var(--teal-dark); text-decoration: underline; font: inherit;
  text-transform: none; letter-spacing: normal; }
.ao-example img { max-width: 100%; }
/* Settings -> Data: legacy's three-column spread of the zero-price boxes */
.settings-multi { display: grid; grid-template-columns: repeat(3, minmax(0, max-content)); gap: 2px var(--sp-4); }
.settings-multi-item { display: flex; gap: var(--sp-1); align-items: center; white-space: nowrap; }
/* Settings: the staff's temporary-tree line above the sub-tabs */
.settings-agtree-override { display: flex; gap: var(--sp-2); align-items: center; padding: var(--sp-2) var(--sp-4) 0; flex-wrap: wrap; }
/* Settings -> Relevant sets: the tree, the edit form, the active-set line */
.rs-children { list-style: none; margin: 0; padding-left: var(--sp-4); }
.rs-top { padding-left: 0; max-height: 60vh; overflow: auto; }
.rs-node.has-ticked-child > summary { font-weight: 600; }
.rs-item > details.rs-node { display: inline-block; vertical-align: top; }
.rs-filter, .rs-active { display: flex; gap: var(--sp-2); align-items: center; flex-wrap: wrap; margin: var(--sp-2) 0; }
.rs-edit, .rs-tree { margin-top: var(--sp-3); }
.header-rs label { margin-right: var(--sp-1); }
/* Settings -> Personal info: legacy's expired-password line and the strip notices */
.password-expired { color: var(--danger, #b00020); font-weight: 600; }
.password-notice { color: var(--danger, #b00020); }
/* Settings: legacy's controls are as wide as their content, not the panel */
.settings-form select, .settings-agtree-override select { width: auto; max-width: 30rem; flex: 0 0 auto; }
.settings-form .field-input { flex: 0 1 auto; }
/* the header's active-set tooltip (legacy relSetTooltip): a small table on hover */
.header-rs { position: relative; }
.header-rs-tip { display: none; position: absolute; top: 100%; right: 0; z-index: 30;
  background: var(--surface, #fff); border: 1px solid var(--border); padding: var(--sp-2);
  box-shadow: 0 2px 6px rgba(0,0,0,.15); max-height: 60vh; overflow: auto; white-space: nowrap; }
.header-rs:hover .header-rs-tip, .header-rs:focus-within .header-rs-tip { display: block; }
.header-rs-tip td { padding: 0 var(--sp-2) 0 0; }

/* ---- Home (incWelcome.xhtml, docs/HOME.md stage 3) ------------------------
   Read off production 2026-10-16 (HOME.md §2a): the welcome line upper case,
   22 px bold, the Start picture beside it; the news in one 579 px column at the
   left, each headed by a light blue band with a 5 px black top rule (date and
   headline, 17 px), the body in 11 px Arial as the editors wrote it. */
.home { padding: var(--sp-4) var(--sp-5); }
.home-welcome { display: flex; align-items: center; gap: var(--sp-5); margin-bottom: var(--sp-4); }
.home-title {
  font: 700 22px/1.15 "Helvetica Neue", Helvetica, Arial, sans-serif;
  text-transform: uppercase;
  max-width: 210px;  /* legacy wraps it into three lines */
  margin: 0;
}
.home-start img { display: block; width: 160px; height: 160px; }
.home-news { width: 579px; max-width: 100%; }
.news-item { margin-bottom: var(--sp-4); }
.news-head {
  display: flex; gap: var(--sp-4);
  background: rgb(224, 236, 244);
  border-top: 5px solid #000;
  padding: 4px 2px 3px 5px;
  font: 17px/1.3 Arial, Helvetica, sans-serif;
}
.news-headline { font-weight: 700; }
.news-body {
  background: rgb(224, 236, 244);
  padding: var(--sp-2) 5px;
  font: 11px/1.4 Arial, Helvetica, sans-serif;
  overflow-wrap: anywhere;
}
/* the editors paste pictures up to 1 150 px wide */
.news-body img { max-width: 100%; height: auto; }
.news-body table { max-width: 100%; }
.home-logo { display: block; margin: var(--sp-5) auto; max-width: 100%; }

/* the customer's logo, top right of the header (legacy main.xhtml:311;
   FOCUS's own is 107 x 55 px there, HOME.md 2a). Logos are stored at any size
   (up to 2 MB), so the box is fixed and the picture scales into it. */
.header-user-logo {
  display: block;
  max-height: 55px;
  max-width: 160px;
  width: auto;
  height: auto;
  margin-left: var(--sp-3);
  object-fit: contain;
}

/* the note for a browser the clone does not run in (HOME.md stage 6):
   hidden unless /static/browser-check.js finds the browser below the floor.
   Plain properties only — the browsers that see it may know no grid or
   custom properties. */
.browser-notice { display: none; }
.browser-notice.is-shown {
  display: block;
  margin: 0;
  padding: 8px 16px;
  background: #fff3cd;
  color: #5a4300;
  border-bottom: 1px solid #e0c870;
  font: 13px/1.4 Arial, Helvetica, sans-serif;
  text-align: center;
}
