/* ============================================================================================
   OKIS 2026 — ONE ordered stylesheet.

   Replaces, in their former AppAsset load order:
       web/css/okis-2026.css        the designer's canonical layer
       web/css/okis-2026-home.css   the home (Uvod) companion
       web/css/okis-2026-fixes.css  the app-wiring layer

   ORDER IS THE CONTRACT.  Sections run tokens -> base -> components -> page areas, and inside a
   section the rules keep the relative order they had in the sources.

   * `!important` here is AUDITED, not pruned: 170 declarations, each justified at its own block.
     Dropping one is a cascade change that has to be measured on its own.
   * NEVER write a star-slash pair inside a comment (a shell glob in a path does it) — it closes
     the comment early and silently kills the rules that follow.  scripts/check-css-comments.php
     scans this file for exactly that; run it after every edit.

   Sections
     1  TOKENS ................ the single :root registry (Figma 1:1)
     1b FONTS ................. self-hosted Inter
     2  BASE ................... type, form controls, select2, buttons, boxes, tables, chrome
     3  COMPONENTS ............. the shared patterns + the canonical paint / shape classes
     4  AREAS .................. what is genuinely page-specific, grouped by area
   ============================================================================================ */


/* ============================================================================================
   1. TOKENS — the design-token registry for this layer, mirroring the Figma variable registry
   1:1 (Figma `group/name` -> CSS `--group-name`).  Figma stays the human-readable registry of
   record; this is its CSS projection, and the ONLY :root block in this file.

   * okis-2026.css was frozen when this file was assembled, so where a Figma name and an older
     token mean the same value the new name is an ALIAS (`var(--old)`): one computed value, two
     vocabularies, either may be used.
   * Custom properties resolve at computed-value time, so the position of a :root declaration
     relative to the rules that read it is irrelevant.
   ============================================================================================ */
:root {
  /* Retuned legacy tokens */
  --main-bg-color: #F4FFFB;  /* was #F5F5FA Cool Grey — dropped from 2026 manual */
  --border-radius: 6px;  /* was 3px */
  --opacity: 1;  /* retire the opacity-hover hack; real hover shades below */
  /* Legacy palette names still consumed OUTSIDE this file — the form-generator widget sheets
     (okforms-v2.css, jsonForms.css), which also self-define them at the same values — aliased to
     the 2026 value they equal. */
  --main-color: var(--ok-green);
  --main-color-light: var(--ok-mint);
  --supplement-color: var(--ok-orange);

  /* ---------- 2026 additions ---------- */
  --ok-green: #005046;
  --ok-green-dark: #003B33;
  --ok-mint: #AECCC5;
  --ok-orange: #FF6A42;
  --ok-orange-dark: #E85835;
  --ok-orange-light: #FFBFAC;
  --ok-gold: #F5B82E;
  --ok-lavender: #D4C5F0;
  --ok-blush: #F5D5D0;
  --ok-cream: #F8E6A0;
  --ok-sky: #CEDAF6;  /* RGB 206/218/246 per client; manual HEX misprint */
  --radius-sm: 4px;
  --radius-control: 6px;
  --radius-modal: 12px;
  --surface-tint: #EDF7F2;
  --border-default: #DCE9E4;
  --border-strong: #B7CFC8;
  --ring-focus: 0 0 0 3px rgba(0, 80, 70, .14);
  --ring-focus-strong: 0 0 0 3px rgba(0, 80, 70, .6);  /* where the ring is the ONLY focus cue (links, tabs)
                                      — the .14 ring composites to 1.28:1 against white (audit 2026-09) */
  --shadow-box: 0 1px 2px rgba(0, 60, 50, .06), 0 2px 8px rgba(0, 60, 50, .05);
  --shadow-dropdown: 0 4px 16px rgba(0, 60, 50, .12);
  --radius-box: 12px;
  accent-color: var(--ok-green);

  /* ---------- Brand ---------- */
  --brand-green:        var(--ok-green);  /* #005046 */
  --brand-green-dark:   var(--ok-green-dark);  /* #003B33 */
  --brand-mint:         var(--ok-mint);  /* #AECCC5 */
  --brand-mint-bg:      #DDEBE6;  /* the mint FILL: hover/selected tints, bands, chips */
  --brand-orange:       var(--ok-orange);  /* #FF6A42 — the CTA family */
  --brand-orange-dark:  var(--ok-orange-dark);  /* #E85835 */
  --brand-orange-light: var(--ok-orange-light);  /* #FFBFAC */
  --brand-gold:         var(--ok-gold);  /* #F5B82E */
  --brand-gold-dark:    #E3A417;

  /* ---------- Pastels ---------- */
  --pastel-lavender:    var(--ok-lavender);  /* #D4C5F0 */
  --pastel-blush:       var(--ok-blush);  /* #F5D5D0 */
  --pastel-blush-hover: #EFC2BA;  /* one step darker: the blush family's hover fill */
  --pastel-cream:       var(--ok-cream);  /* #F8E6A0 */
  --pastel-cream-hover: #F3DC85;  /* Figma only — no call site in this layer yet */
  --pastel-sky:         var(--ok-sky);  /* #CEDAF6 */
  --pastel-sky-light:   #E9F0FB;  /* info panel, unread row tint, dialog icon disc */
  --pastel-sky-light-hover: #DFEAF8;  /* one step darker: the unread row's hover */
  --pastel-sky-text:    #2C4270;  /* the sky family's ink */

  /* ---------- Surfaces ---------- */
  --surface-page:       var(--main-bg-color);  /* #F4FFFB */
  --surface-card:       #FFFFFF;
  --surface-input:      #FAFDFB;
  --surface-disabled:   #F3F7F5;  /* the small disabled marks: checkbox, radio, ghost button */
  --surface-disabled-field: #E9EDEB;  /* a FULL-SIZE disabled/readonly control. Deliberately darker than
                                         --surface-disabled: 3 points off white is cue enough on a 24px
                                         checkbox, not on a 34x168 field. REGISTRY GAP: no Figma name. */
  --surface-row-hover:  var(--surface-tint);  /* the Figma registry's #F5F8F7 is superseded */
  --surface-inactive-row:       #DFE5E2;  /* a grid row whose record is no longer live; the ramp is derived
                                             at the inactive-row block in 3e. REGISTRY GAP: no Figma name. */
  --surface-inactive-row-hover: #D5DBD8;
  --overlay-backdrop:   #002A25;  /* the modal backdrop, used at 45% opacity */

  /* ---------- Ink ---------- */
  --text-body:          #1F2A28;
  --text-label:         #33413D;
  --text-muted:         #5E6E6A;
  --text-placeholder:   #5E7871;  /* audit 2026-09: 4.66:1 on white; SPLIT from the disabled ink so the
                                      two states stop being indistinguishable */
  --text-disabled:      #93A6A1;
  --text-not-set:       #587D73;  /* audit 2026-09: the old #9CBFB6 measured 1.99:1 */
  --link-ink:           #C2411F;  /* audit 2026-09: base link — brand orange #FF6A42 measured 2.84:1 on
                                      white; buttons keep the brand orange (client ruling) */
  --text-inverse:       #FFFFFF;

  /* ---------- Borders ---------- */
  --border-focus:       var(--ok-green);  /* #005046 */

  /* ---------- Status ---------- */
  --status-success:     var(--ok-green);  /* #005046. NOTE: this name once held #00A65A, which is the
                                             risk-card edge and now lives under --status-risk-edge. */
  --status-warning:     var(--ok-gold);  /* #F5B82E */
  --status-danger:      #D93025;
  --status-danger-text: #B3261E;  /* the deeper danger: destructive ink + hover fills */
  --status-danger-muted:#B3401F;  /* alarm ink on the light-orange chips */
  --status-danger-border:#E4B5B0;  /* the tinted border of the quiet destructive button */
  --status-danger-wash: #FBEDEB;  /* its hover fill */
  --status-risk-edge:   #00A65A;  /* the risk-card left edge, and nothing else */

  /* ---------- Tinted state panels ---------- (the alert/callout anatomy, and the risk cards' skin) */
  --panel-success-bg:     #DDEBE6;
  --panel-success-border: #B7D4CA;
  --panel-warning-bg:     #FFF7E0;
  --panel-warning-border: #EDDFB3;
  --panel-warning-text:   #8A6A00;
  --panel-danger-bg:      #FFF6F4;
  --panel-danger-border:  #F0D6D0;
  --panel-danger-text:    #7A2E17;
  --panel-danger-row-bg:  #FFE3D9;  /* the light-orange alarm chip fill */
  --panel-info-bg:        var(--pastel-sky-light);  /* #E9F0FB — completes the success/warning/danger */
  --panel-info-border:    #C4D4F0;  /* family with the Sky Blue info panel */
  --panel-info-text:      var(--pastel-sky-text);  /* #2C4270 */

  /* ---------- Grid ROW tints ---------- (`tr.success` / `.warning` / `.danger` / `.info`)
     A user-chosen colour, not a state: Comment::$color and Alert::$type are written straight onto the
     <tr> as its class name, and these four fills are what the comment grid and the alert grid paint.
     They are tokens because the colour-swatch PICKERS have to sample exactly them; the rules that
     consume them are in 2f (rows) and 4c (swatches). */
  --row-tint-success:     var(--panel-success-bg);  /* #DDEBE6 */
  --row-tint-warning:     #FCF8E3;
  --row-tint-danger:      #FFDDD2;
  --row-tint-info:        #D9EDF7;

  /* ---------- Spacing ---------- (the compact vertical rhythm) */
  --spacing-form-group-gap:  10px;
  --spacing-label-gap:       3px;
  --spacing-box-gap:         16px;
  --spacing-inline-gap:      6px;  /* the gap between adjacent controls in a button row */
  --spacing-btn-icon-gap:    var(--spacing-inline-gap);  /* 6px — glyph to label INSIDE a button.
                                      An alias, not a second literal: this gap replaces a text node
                                      (`Icon::createIcon($i, true)`) that measured 3.66px, and the one
                                      rule that already had to restate it — the filter bar's action
                                      row — chose the control row's 6. One rhythm, one number; split
                                      them only with a reason. */
  --spacing-section-gap:     14px;  /* Figma; no unambiguous call site in this file yet */
  --spacing-cell-pad-y:      10px;  /* Figma; the grid cell padding is stated in 3e */
  --spacing-kv-row-pad-y:    9px;
  --spacing-box-body-x:      18px;
  --spacing-box-body-top:    12px;
  --spacing-box-body-bottom: 14px;
  --spacing-box-header-y:    8px;  /* Figma; as built the box header takes a single 8px on ALL FOUR sides,
                                      so the `-y` name would misdescribe the rule — left unapplied */
  --spacing-content-x:       24px;
  --spacing-content-y:       14px;

  /* ---------- Radius ---------- */
  --radius-dropdown: 8px;
  --radius-box-2026: var(--radius-box);  /* = 12px — the Figma name for the box radius */
  --radius-pill:     999px;

  /* ---------- Size ---------- */
  --control-height:         34px;  /* inputs, selects, the whole select2 metric chain and the input-group
                                      buttons are expressed in it, or in calc() off it */
  --size-control-height:    var(--control-height);  /* the Figma name for the same token (34px) */
  --size-btn-height:        36px;  /* the BLESSED free-standing button height. Named rather than left a
                                      literal for the same reason --size-toolbar-icon-tile is: plain `.btn`
                                      is deliberately OFF --control-height, so a bare 36 would drift the day
                                      that token moves again. See the height ladder in 3h. */
  --size-checkbox-form:     28px;
  --size-checkbox-grid:     20px;
  --size-radio-form:        20px;  /* deliberately NOT --size-checkbox-form: the client asked for the
                                      bigger box on checkboxes only */
  --size-boxheader-icon-btn: 28px;  /* the box-header / sub-section icon tile in body.toolbar-icons */
  --size-grid-action-btn:   28px;
  --size-filter-reset-btn:  28px;  /* Figma; as built the filter-reset tile rides --control-height, so this has no call site here */
  --size-title-trio-btn:    34px;
  --size-chip-add-btn:      28px;
  --size-input-addon:       40px;  /* ONE width for every icon-only input-group addon or button. Not a
                                      Figma variable and deliberately not --control-height: a 34px square
                                      would shrink the 43px icon buttons by 9px, and 40 is an as-built
                                      value already. */
  --size-toolbar-icon-tile: 36px;  /* NOT a Figma variable — the designer's toolbar spec, and the reason
                                      this token exists at all: plain `.btn` is deliberately OFF
                                      --control-height, so every icon-mode tile and the attached entity "+"
                                      segment used to carry a bare literal 36 and silently drifted the day
                                      the control token moved 36 -> 34. One name, five call sites. */
  --offset-modal-top:       110px; /* every modal opens below the title row (its bottom measures 101px);
                                      the Subject viewer's inline body height in incident/_form.php is
                                      sized against this same offset. */
}

/* ============================================================================================
   1b. FONTS — Inter, self-hosted (no CDN — OKIS-3253 precedent)
   ============================================================================================ */
@font-face { font-family: "Inter"; font-weight: 400; font-style: normal; font-display: swap;
  src: url("font/inter-regular.woff2") format("woff2"); }
@font-face { font-family: "Inter"; font-weight: 500; font-style: normal; font-display: swap;
  src: url("font/inter-medium.woff2") format("woff2"); }
@font-face { font-family: "Inter"; font-weight: 600; font-style: normal; font-display: swap;
  src: url("font/inter-semibold.woff2") format("woff2"); }
@font-face { font-family: "Inter"; font-weight: 700; font-style: normal; font-display: swap;
  src: url("font/inter-bold.woff2") format("woff2"); }

/* ============================================================================================
   2a. BASE / type
   ============================================================================================ */
/* Compact vertical rhythm.  The control height is a TOKEN, so re-declaring it is enough: inputs,
   selects, the select2 selection/arrow/chip metrics, input-group buttons and the datepicker addons
   are all expressed in it (or in `calc()` off it) and follow by themselves.  Plain `.btn` is
   deliberately NOT keyed to the token (okis-2026.css pinned it via `line-height: 20px`) and stays
   at 36px: a toolbar is a row of buttons only, and 36px is the designer's toolbar-tile spec. */
body { color: var(--text-body); line-height: 1.4; }
/* The app's base link colour. Audit ruling 2026-09-22: --link-ink, the AA-passing dark orange —
   the brand #FF6A42 measured 2.84:1 and is reserved for button FILLS (client ruling). Context
   rules deeper in this sheet keep recolouring links. */
a, a:focus, a:hover, a:active,
.s2-togall-button,
.content-header > .breadcrumb > li > a { color: var(--link-ink); }
hr { border-color: var(--border-default); }
/* "(není zadáno)" — `app\components\Formatter::asNotSet` plus five views wrap the placeholder in
   `.not-set`.  Quiet mint italic, and never bold: declared on `.not-set` ITSELF rather than scoped
   to `.kv-grid`, because a direct declaration beats inheritance from any ancestor, so this one line
   settles every context the placeholder can land in (in a Přehled row it would otherwise inherit
   `.kv-grid__value`'s 600). */
.not-set { color: var(--text-not-set); font-style: italic; font-weight: 400; }
/* A standalone warning glyph (the fee-missing icon on Sady předpisů) is a WARNING, so it takes
   the gold family — the palette reserves orange for the CTA family. */
.icon-warning { color: var(--status-warning); }

/* ============================================================================================
   2b. BASE / form controls
   ============================================================================================ */
/* ---------- Controls: one geometry for everything ---------- */
.form-control, select.form-control {
  height: var(--control-height);
  border-radius: var(--radius-control);
  border-color: var(--border-default);
  box-shadow: none;
  transition: border-color 120ms, box-shadow 120ms;
}
.input-group-addon {
  border-radius: var(--radius-control);
  border-color: var(--border-default);
  background-color: var(--surface-tint);
  color: var(--ok-green);
}
.form-group.required label:after { color: var(--status-danger); }
/* THE SAME ASTERISK, kartik's address for it.  The rule above only reaches a label that kartik did NOT
   render: on a `has-star` label activeform.css:286
   (`.required .has-star:not(.custom-control-label):not(.custom-file-label)::after`, (0,4,0)) supplies
   both the glyph and `color: tomato`, and out-specifies the (0,2,1) above — so every required marker on
   a kartik field was rgb(255,99,71) beside a #D93025 label.  Repeating kartik's shape with the element
   it actually lands on makes this (0,4,1). */
.required label.has-star:not(.custom-control-label):not(.custom-file-label)::after { color: var(--status-danger); }
.input-group-btn .btn, .navbar-custom-menu .input-group .input-group-btn .btn { height: var(--control-height); }
.label, .badge { border-radius: var(--radius-sm); font-weight: 600; }
.navbar-custom-menu .form-group .input-group { border-radius: var(--radius-control); }
.navbar-custom-menu .form-control { border-radius: var(--radius-control) 0 0 var(--radius-control); }
.navbar-custom-menu .input-group .input-group-btn .btn { border-radius: 0 var(--radius-control) var(--radius-control) 0; }
.form-group { margin-bottom: 18px; }
/* A readonly <select> has no native readonly behaviour; blocking pointer events is what makes the
   attribute hold. */
select[readonly] {
    pointer-events: none;
}
label.control-label { font-weight: 400; color: var(--text-label); }
.form-control:focus { border-color: var(--ok-green); box-shadow: var(--ring-focus); background: var(--surface-card); }
/* Inputs: calm placeholder, smooth focus */
.form-control::placeholder { color: var(--text-placeholder); }
/* Input-group buttons: square inner edge, rounded outer edge, tinted like addons */
.input-group .form-control:first-child { border-top-right-radius: 0; border-bottom-right-radius: 0; }
.input-group .form-control:last-child { border-top-left-radius: 0; border-bottom-left-radius: 0; }
/* Checkboxes: bigger, rounded, tinted like inputs (accent-color keeps them green when checked) */
input[type="checkbox"], input[type="radio"] { width: 20px; height: 20px; margin: 0; vertical-align: middle; }
.checkbox input[type="checkbox"], .form-group input[type="checkbox"] {
  appearance: none; -webkit-appearance: none;
  position: static; margin: 0; /* overrides skin.css absolute hack + BS3 -20px margin-left */
  width: 28px; height: 28px; margin: 4px 0; cursor: pointer;
  background: var(--surface-input); border: 1px solid var(--border-default); border-radius: var(--radius-sm);
  transition: border-color 120ms, box-shadow 120ms, background-color 120ms;
  display: inline-grid; place-content: center;
}
.checkbox input[type="checkbox"]:hover, .form-group input[type="checkbox"]:hover { border-color: var(--border-strong); }
.checkbox input[type="checkbox"]:focus-visible, .form-group input[type="checkbox"]:focus-visible { outline: none; border-color: var(--ok-green); box-shadow: var(--ring-focus); }
.checkbox input[type="checkbox"]:checked, .form-group input[type="checkbox"]:checked { background: var(--ok-green); border-color: var(--ok-green); }
.checkbox input[type="checkbox"]:checked::before, .form-group input[type="checkbox"]:checked::before {
  content: ""; width: 14px; height: 14px;
  clip-path: polygon(14% 44%, 0 65%, 40% 100%, 100% 16%, 84% 0%, 38% 68%);
  background: var(--text-inverse);
}
.input-group.date .input-group-addon + .form-control { border-radius: 0 var(--radius-control) var(--radius-control) 0; }
/* okis-2026.css resets checkbox positioning via `.checkbox input[type=checkbox]` (0,2,1),
   but adminlte.css:288 positions it via `.checkbox label input[type=checkbox]` (0,2,2),
   and Yii2/BS3 nests the input INSIDE the label — so the reset loses.  Re-assert at (0,2,2): */
.checkbox label input[type="checkbox"],
.checkbox-inline input[type="checkbox"] {
  position: static;
  left: auto; top: auto;
  margin: 4px 0;
}
.radio input[type="radio"]:hover, .form-group input[type="radio"]:hover { border-color: var(--border-strong); }
.radio input[type="radio"]:focus-visible, .form-group input[type="radio"]:focus-visible {
  outline: none; border-color: var(--ok-green); box-shadow: var(--ring-focus);
}
.radio input[type="radio"]:checked, .form-group input[type="radio"]:checked,
.radio label input[type="radio"]:checked {
  background: var(--ok-green); border-color: var(--ok-green);
}
.radio input[type="radio"]:checked::before, .form-group input[type="radio"]:checked::before,
.radio label input[type="radio"]:checked::before { content: ""; border-radius: 50%; background: var(--text-inverse); width: 9px; height: 9px; }
/* APPROVED visual change: `#eee` -> --surface-disabled (#F3F7F5), a slightly greener, lighter
   disabled grey, so the three legacy disabled fills (this radio, the inline checkbox below, the
   disabled select2) agree with the token every other disabled surface in this layer uses. */
.radio input[type="radio"]:disabled, .form-group input[type="radio"]:disabled { background: var(--surface-disabled); cursor: not-allowed; }
/* Addon restyle lost to vendored `.input-group .input-group-addon { border-radius:0; border-color:#d2d6de;
   background-color:#fff; }` at (0,2,0), same specificity as this rule — tie resolves in our favor by load order. */
.input-group .input-group-addon {
  border-color: var(--border-default);
  background-color: var(--surface-tint);
  color: var(--ok-green);
}
/* Checkbox polish: a crisp SVG check instead of clip-path (which rendered ragged on some
   displays), plus full state coverage for the -inline and label.form-label variants. */
.checkbox input[type="checkbox"], .form-group input[type="checkbox"],
.checkbox label input[type="checkbox"], .checkbox-inline input[type="checkbox"] {
  width: 24px; height: 24px;
}
.checkbox input[type="checkbox"]:checked, .form-group input[type="checkbox"]:checked,
.checkbox label input[type="checkbox"]:checked, .checkbox-inline input[type="checkbox"]:checked {
  background: var(--ok-green) url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath fill='none' stroke='%23fff' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round' d='M3.5 8.5l3 3 6-7'/%3E%3C/svg%3E") center/70% no-repeat;
  border-color: var(--ok-green);
}
.checkbox input[type="checkbox"]:checked::before, .form-group input[type="checkbox"]:checked::before,
.checkbox label input[type="checkbox"]:checked::before, .checkbox-inline input[type="checkbox"]:checked::before {
  display: none;
}
.checkbox-inline input[type="checkbox"]:hover { border-color: var(--border-strong); }
.checkbox-inline input[type="checkbox"]:focus-visible { outline: none; border-color: var(--ok-green); box-shadow: var(--ring-focus); }
/* `#eee` -> --surface-disabled.  See the radio note above. */
.checkbox-inline input[type="checkbox"]:disabled { background: var(--surface-disabled); cursor: not-allowed; }
label:has(> input[type="checkbox"]) {
  display: inline-flex; align-items: center; gap: 8px;
}

/* ---------- Custom radio ----------
   20px, not the checkbox's 28: 24/28px read oversized next to the 13px type, and 20 matches the
   native 20px grid row-select.  The client asked for the bigger box on checkboxes only. */
.radio input[type="radio"], .form-group input[type="radio"],
.radio label input[type="radio"], .radio-inline input[type="radio"] {
  appearance: none;
  -webkit-appearance: none;
  position: static;
  margin: 4px 0;
  cursor: pointer;
  background: var(--surface-input);
  border: 1px solid var(--border-default);
  border-radius: 50%;
  transition: border-color 120ms, box-shadow 120ms, background-color 120ms;
  display: inline-grid;
  place-content: center;
  vertical-align: middle;
  width: var(--size-radio-form);
  height: var(--size-radio-form);
}
/* Block checkboxes (Yii `->checkbox()` in vertical forms, e.g. Cizinec): restore the
   label-above-box layout — the original look came from the removed absolute-position hack.
   Inline variants (.checkbox-inline, contract's label.form-label) keep the row layout.
   Default is the classic inline row — box left, sentence label right, one line (the checkbox DOM
   is normalized input-first via container.php's checkboxTemplate). */
.checkbox label:has(> input[type="checkbox"]) {
  display: inline-flex;
  flex-direction: row;
  align-items: center;
  gap: 8px;
}
/* Two legacy leftovers stacked up between a checkbox and the field under it: skin.css's
   `.form-group .checkbox { margin-bottom: 30px }` and adminlte.css:258's `.checkbox label
   { margin-bottom: 15px }` (15px of dead space INSIDE the checkbox row).  Both existed to
   compensate for the absolutely-positioned checkbox hack that okis-2026.css removed, so they are
   now pure padding.  Zero them and let the form-group's own margin govern the rhythm. */
.form-group .checkbox { margin-bottom: 0; }
.checkbox label { margin-bottom: 0; }
/* Form checkboxes are 28px, the canonical size, with `margin: 4px 0` centring them on the control
   line.  Grid checkboxes are a separate canonical rule (`.kv-grid-table input[type=checkbox]`,
   20px) and are out of reach here — no grid checkbox sits inside a `.form-group` or a `.checkbox`.
   Radios stay at 20px: the client asked about checkboxes only. */
.checkbox input[type="checkbox"], .form-group input[type="checkbox"],
.checkbox label input[type="checkbox"], .checkbox-inline input[type="checkbox"] {
  width: var(--size-checkbox-form); height: var(--size-checkbox-form);
}
/* Block checkbox (Cizinec): centre the box on the control line of its row. */
.checkbox label:has(> input[type="checkbox"]) > input[type="checkbox"] { margin: 4px 0; }
/* OKIS-3342 composer upgrade: yii2-widget-activeform 1.6.4 renders kartik field labels
   as .form-label (BS5 vocabulary) even in BS3 mode — canonical label.control-label rules
   stopped matching, labels fell back to bootstrap's bold + inherited green.  Alias them. */
label.form-label {
  font-weight: 400;
  color: var(--text-label);
}
/* THE SAME ALIAS for the label of a checkbox or a radio.  `label.control-label` is what carries the
   canonical weight and ink, and the label a Yii checkbox is wrapped in never gets that class — so the
   only rule it matched was bootstrap.css's base `label { font-weight: 700 }`, and its colour fell
   through to whatever the surrounding box inherits (`--ok-green` inside `.content-wrapper`).  On the
   same form that put a bold dark-green "Požadované datum" beside a regular "Název".
   Three shapes, because the app emits three: the bare `<label><input></label>` of
   yii\widgets\ActiveField, the `.checkbox` / `.radio` div of container.php's bootstrap checkboxTemplate,
   and BS3's own inline variants.  `:has()` is the only way to reach the bare one — it carries no class
   at all — and it is kept inside `.form-group` so a labelled checkbox outside a form (a grid's
   select-all, say) keeps whatever its own context gives it. */
.checkbox label,
.radio label,
.checkbox-inline,
.radio-inline,
.form-group label:has(> input[type="checkbox"]),
.form-group label:has(> input[type="radio"]) {
  font-weight: 400;
  color: var(--text-label);
}
/* Hidden value carriers must not reserve vertical space.  Each `hiddenInput()` field is a block
   `.form-group`: zero height, but its own bottom margin, so a run of them inserts a dead gap before
   the next section heading.  Listed explicitly rather than matched structurally — these are the only
   hidden-only field wrappers on the page, and `:has()` guesswork would also catch the (visible)
   `hidden`-class premium fields. */
.field-subject-api_data,
.field-subject-insurance_premium_hidden,
.field-subject-insurance_premium_term_hidden,
.field-subject-payment_frequency_month_count,
.field-subjectvehicle-notset_id_hidden,
.field-subjectvehicle-vehicle_brand_detail_jine_id_hidden,
.field-subjectvehicle-show_description,
.field-subjectrisc-risc_ids_hidden {
  margin-bottom: 0;
}
.list-filter-bar__field {
  margin: 0;
  flex: 1 1 200px;
  min-width: 200px;
}
.form-group { margin-bottom: var(--spacing-form-group-gap); }
label { margin-bottom: var(--spacing-label-gap); }
/* A checkbox / radio box is centred on the control line, so its centring margin is
   (control-height - box) / 2 — 3px at the 34px control height.  Four selector families, all the
   same arithmetic. */
.checkbox label input[type="checkbox"],
.checkbox-inline input[type="checkbox"],
.checkbox label:has(> input[type="checkbox"]) > input[type="checkbox"],
.radio input[type="radio"], .form-group input[type="radio"],
.radio label input[type="radio"], .radio-inline input[type="radio"] {
  margin: 3px 0;
}
/* ---------- Validation success state ----------
   Brand green with no glow, and the addon quiet instead of AdminLTE's solid green fill.
   SPECIFICITY: AdminLTE.min.css states the whole state as `.form-group.has-success X` (0,3,0), so
   `.has-success X` alone (0,2,0) loses to it — every selector below repeats AdminLTE's shape and
   wins the tie on load order. */
.form-group.has-success .form-control,
.form-group.has-success .form-control:focus,
.has-success .form-control, .has-success .form-control:focus {
  border-color: var(--ok-green);
  box-shadow: none;
}
/* The addon keeps its RESTING fill and glyph and moves only its border, exactly as the error and
   warning twins below do — one philosophy for all three states.  It used to carry
   `--panel-success-bg`, which made success the only state that also repainted the addon's fill.
   (0,4,0) for the same two reasons the error twin needs it: it has to clear kartik's
   `.has-success.highlight-addon .input-group-addon` (0,3,0) AND the date picker's own
   `.input-group.date .input-group-addon` (0,3,0), stated LATER in this file — at (0,3,0) the
   calendar addon of a valid date field kept a pale border while the input went green. */
.form-group.has-success .input-group .input-group-addon {
  background-color: var(--surface-tint);
  border-color: var(--ok-green);
  color: var(--ok-green);
}
.form-group.has-success .input-group-addon,
.has-success .input-group .input-group-addon {
  border-color: var(--ok-green);
}
/* The state must not repaint the label or the hint — the border is the whole signal. */
.form-group.has-success label, .form-group.has-success label.form-label,
.has-success .control-label { color: var(--text-label); }
.form-group.has-success .help-block, .has-success .help-block { color: var(--text-muted); }
/* ---------- The error twin of the success state — quiet validation, in the 2026 red ----------
   ROOT CAUSE of the last legacy red on screen: the error state was painted by AdminLTE.min.css, a
   VENDORED file that cannot be edited (`.form-group.has-error` on the label, the control's border and the
   help-block, all #dd4b39).  The whole state now sits on --status-danger.

   SPECIFICITY: AdminLTE states it as `.form-group.has-error X`, so every selector here repeats that shape
   and wins the tie on load order, exactly as the success block above does.  `.has-error X` is added
   alongside for the cases where the state lands on something that is not a `.form-group`.

   THE ADDON IS NOT A VALIDATION SURFACE — user ruling, same philosophy as the tinted success addon and the
   quiet clipboard button: the field's own border and message carry the state.  The addon keeps its resting
   fill and glyph, with only its border moving to the danger colour so the ring is one continuous colour.

   ITS FILL AND INK HAVE TO BE STATED, not just left alone.  Two vendored rules wait underneath with
   bootstrap's pink error skin, and one of them out-specifies the resting 2026 rule —

     bootstrap.css      .has-error .input-group-addon                      (0,2,0)
     activeform.css     .has-error.highlight-addon .input-group-addon      (0,3,0)   <- wins
         both: color #A94442; background-color #F2DEDE; border-color #A94442
     this file          .input-group .input-group-addon                   (0,2,0)   resting tint

   `highlight-addon` is on EVERY field that has an addon (kartik ActiveField:1247), so without the two
   lines below an errored addon swaps AdminLTE's red for kartik's pink.  And
   `.form-group.has-error .input-group .input-group-addon` is (0,4,0) deliberately: it has to clear
   activeform's (0,3,0) AND the date picker's own `.input-group.date .input-group-addon` (0,3,0), stated
   LATER in this file — a (0,3,0) here would lose to both and leave every errored date field with a green
   border on its calendar.  The two shorter selector lists are for the shapes (0,4,0) cannot match. */
.form-group.has-error label, .form-group.has-error label.form-label,
.has-error .control-label { color: var(--status-danger); }
.form-group.has-error .help-block, .has-error .help-block { color: var(--status-danger); }
.form-group.has-error .form-control,
.form-group.has-error .form-control:focus,
.has-error .form-control, .has-error .form-control:focus {
  border-color: var(--status-danger);
  box-shadow: none;
}
.form-group.has-error .input-group .input-group-addon {
  background-color: var(--surface-tint);
  border-color: var(--status-danger);
  color: var(--ok-green);
}
.form-group.has-error .input-group-addon,
.has-error .input-group .input-group-addon {
  border-color: var(--status-danger);
}
/* The errored addon BUTTON's input joins the family. */
.has-error .input-group .input-group-btn input { border-color: var(--status-danger); }
/* ---------- The warning twin — the third validation state, same philosophy ----------
   `web/js/modules.js` puts `has-warning` on eight field groups (a VIN or a number plate that is
   already on another record, an IČO the ARES/OKK lookup could not confirm, an unverified data-box
   id): a soft "check this" that must not read as a hard error.  Before this block the state was
   AdminLTE's #F39C12 on the label, the control border and the hint, plus a SOLID #F39C12 addon with
   white ink — the last solid-fill validation addon in the app.

   THE ADDON IS NOT A VALIDATION SURFACE, exactly as in the success and error twins above: resting
   fill and glyph, border on the state colour.  (0,4,0) for the same two reasons, and bootstrap waits
   underneath here too (`.has-warning .input-group-addon` (0,2,0), #fcf8e3 fill / #8a6d3b ink), so the
   fill and the ink have to be stated rather than left alone.

   TWO WARNING TOKENS, NOT ONE.  `--status-warning` (#F5B82E) is a FILL/BORDER gold: on white it
   measures ~1.9:1, so a label or a message written in it would be unreadable.  The ink is therefore
   `--panel-warning-text` (#8A6A00, ~4.9:1), the same ink the warning alert/callout anatomy uses.
   The label and the hint DO carry the ink — a warning always arrives with a message, so this state
   follows the error twin here rather than the success one. */
.form-group.has-warning label, .form-group.has-warning label.form-label,
.has-warning .control-label { color: var(--panel-warning-text); }
.form-group.has-warning .help-block, .has-warning .help-block { color: var(--panel-warning-text); }
.form-group.has-warning .form-control,
.form-group.has-warning .form-control:focus,
.has-warning .form-control, .has-warning .form-control:focus {
  border-color: var(--status-warning);
  box-shadow: none;
}
.form-group.has-warning .input-group .input-group-addon {
  background-color: var(--surface-tint);
  border-color: var(--status-warning);
  color: var(--ok-green);
}
.form-group.has-warning .input-group-addon,
.has-warning .input-group .input-group-addon {
  border-color: var(--status-warning);
}
/* ---------- A custom checkbox must never shrink ----------
   Where a label is `display: inline-flex` the box beside it is a flex item: its 28px is only a
   BASIS and `flex-shrink: 1` is the initial value, so beside a two-line label the row is
   over-constrained and the box gives way first — it reads as a dented rectangle.  `flex: none`
   restores "28px, non-negotiable"; the label wraps instead, which it was already doing.  Written
   across the same selector list as the rule that sets the 28px so the two cannot drift apart, and
   inert wherever the container is not a flex container. */
.checkbox input[type="checkbox"], .form-group input[type="checkbox"],
.checkbox label input[type="checkbox"], .checkbox-inline input[type="checkbox"],
label > input[type="checkbox"] {
  flex: none;
}
/* ---------- The navbar search field's focus state ----------
   skin.css paints the field mint on both axes — `.navbar-custom-menu form input[type="text"]
   { background-color: var(--main-color-light); border-color: var(--main-color-light) }` — and at
   (0,2,2) that outranks `.form-control:focus` (0,2,0).  So the border never changed on focus: the
   field kept its mint edge, and the green ring underneath it (`--ring-focus`, an 8%-alpha green) is
   all but invisible against the dark green navbar.  The rule below pins all three parts of the
   state: the UA/legacy ring off, the border to the focus green, the standard ring.  Specificity is
   skin.css's selector plus `.form-control` and `:focus`, so it wins on both count and order.
   The mint FILL is left alone on purpose — the border is what carries the state everywhere else. */
.navbar-custom-menu form input[type="text"].form-control:focus {
  outline: none;
  border-color: var(--ok-green);
  box-shadow: var(--ring-focus);
}

/* ---------- A lone .form-control in an .input-group keeps its corners ----------
   ROOT CAUSE of the square read-only fields on the dashboard, and of every other square field that
   sits alone inside an `.input-group`.  Bootstrap flattens the side of a control that ABUTS a
   neighbour:

     .input-group .form-control:first-child { border-top-right-radius: 0; border-bottom-…: 0 }
     .input-group .form-control:last-child  { border-top-left-radius: 0;  border-bottom-…: 0 }

   An only child is both `:first-child` and `:last-child`, so BOTH rules fire and all four corners
   go to 0 — a perfectly square field next to 6px-rounded ones.  The pair is written for a control
   that has an addon or a button beside it; with nothing to abut there is nothing to flatten.
   `:first-child:last-child` is the whole condition and matches nothing else, so every group with an
   addon, a button or a second control keeps its joined-corner geometry exactly.  (0,3,0) against
   their (0,2,0), so it needs no `!important`. */
.input-group > .form-control:first-child:last-child {
  border-radius: var(--radius-control);
}
/* ---------- An addon BUTTON stays quiet while the field validates ----------
   A STATE, not the resting look.  At rest these buttons render as the quiet addon; kartik repaints them
   the moment its client validation lands a class on the field wrapper:

     vendor/kartik-v/yii2-widget-activeform/src/assets/css/activeform.css:177 / :189
     .has-success.highlight-addon .input-group-btn .btn:not(:disabled):not(.disabled)
         { color: #fff; background-color: #449d44; border-color: #398439 }
     .has-error.highlight-addon   .input-group-btn .btn:not(:disabled):not(.disabled)
         { color: #fff; background-color: #c9302c; border-color: #ac2925 }

   `highlight-addon` is put on the field wrapper by kartik's ActiveField (src/ActiveField.php:1247) for
   every field that has an addon; the state class arrives from the ajax/client validation or from one of
   `web/js/modules.js`'s own toggles.  The three state blocks above address `.input-group-addon` — the
   SPAN — and never see `.input-group-btn .btn`, kartik's separate address for the same state.  (Worth
   knowing when measuring this: `.btn` carries a 120ms background transition, so a getComputedStyle taken
   right after the class lands still reads the OLD fill.)

   ALL addon buttons, not just the copy-to-clipboard one: an errored field could otherwise show a calm
   calendar or copy addon next to a blood-red "search in API" button in the same input group.  The values
   below ARE the resting look of an addon button (`.input-group-btn:last-child > .btn` in 2g, and its
   `:hover` twin), so the button keeps what it had and the field's own border carries the state.
   `has-warning` is in the list because the three states are one rule, not because kartik paints it —
   nothing does, which is why a warning already left these buttons alone.

   Specificity: kartik's is (0,6,0) resting and (0,7,0) on hover; the `:root` prefix repeats its shape
   one class higher, so each of ours wins outright. */
:root .has-success.highlight-addon .input-group-btn .btn:not(:disabled):not(.disabled),
:root .has-error.highlight-addon .input-group-btn .btn:not(:disabled):not(.disabled),
:root .has-warning.highlight-addon .input-group-btn .btn:not(:disabled):not(.disabled) {
  background: var(--surface-tint);
  border-color: var(--border-default);
  color: var(--ok-green);
}
:root .has-success.highlight-addon .input-group-btn .btn:not(:disabled):not(.disabled):hover,
:root .has-success.highlight-addon .input-group-btn .btn:not(:disabled):not(.disabled):focus,
:root .has-error.highlight-addon .input-group-btn .btn:not(:disabled):not(.disabled):hover,
:root .has-error.highlight-addon .input-group-btn .btn:not(:disabled):not(.disabled):focus,
:root .has-warning.highlight-addon .input-group-btn .btn:not(:disabled):not(.disabled):hover,
:root .has-warning.highlight-addon .input-group-btn .btn:not(:disabled):not(.disabled):focus {
  background: var(--brand-mint-bg);
  border-color: var(--border-strong);
  color: var(--ok-green);
}
/* The addon overlaps the input by -1px and both sit at z-index 2, so at rest the LATER node —
   the addon — paints its pale left border over the input's coloured right border, and the
   error/success ring visibly loses its fourth side the moment focus (z-index 3) leaves.
   While the field carries a validation state, the input stays one step above its addon; focus
   still outranks everything at 3. */
.has-error .input-group .form-control,
.has-success .input-group .form-control {
  z-index: 3;
}
/* The base hint container: `.hint-block` is what ActiveField writes and NO vendor sheet styles the
   bare class (kartik only re-touches it under `.kv-form-bs3`), so this is its whole anatomy. */
.hint-block {
    display: block;
    margin-top: 5px;
    color: #999;
}
/* ---------- A field hint gets air under its control ----------
   Both hint containers sit 5px below the control: `.hint-block` (the base rule above, which is what
   kartik's ActiveField writes too) and `.help-block` (bootstrap's `margin: 5px 0 10px`, which is what plain Yii
   uses and where an inline validation message lands).  5px is closer than the control is to its own
   LABEL once the line-box leading is counted, so the hint read as sitting ON the input.

   8px, and the number is bounded on both sides rather than picked: it has to stay BELOW the 10px
   `--spacing-form-group-gap` that separates this field from the next, or the hint stops obviously
   belonging to the control above it; and above 5, or nothing changes.  8 is the largest value that
   satisfies both.

   `.form-group` keeps it to a field's own hint: `.help-block` is also used outside form groups
   (the login page's blocks, the filter bar's empty ones, which are hidden elsewhere).  A DESCENDANT
   and not a child, because a checkbox field puts both its control and its hint inside a `.checkbox`
   div (container.php's checkboxTemplate), so a child combinator reached the hint of every field
   EXCEPT a checkbox's — the one shape whose hint is furthest from what it describes. */
.form-group .hint-block,
.form-group .help-block {
  margin-top: 8px;
}
/* THE SAME AIR once the field is in a dialog.  kartik's activeform.css states
   `.kv-form-bs3 .hint-block { margin-top: -5px }` at the same (0,2,0) as the pair above, and a
   bsdialog loads its body over AJAX, which appends that stylesheet AFTER this file — so inside a
   dialog the vendor rule won the tie on load order and the hint sat ON the control it belongs to.
   Restated one class deeper, so the outcome stops depending on when the dialog was opened.
   The group-assign popup further down keeps its own 4px: a select2 carries more slack than an
   input, and that rule is later in this file, so it still decides its own case. */
.bsdialog .form-group .hint-block,
.bsdialog .form-group .help-block {
  margin-top: 8px;
}
/* ---------- A DISABLED checkbox stops looking like a live one ----------
   A checked AND disabled checkbox came out in full brand green with a white tick, indistinguishable
   from the live ones beside it, so the one control the user cannot change was the loudest on the
   form.  Nothing in the checkbox block covered `:disabled` except `.checkbox-inline`, and the
   `:checked` fill outranked even that.  Both disabled states, drawn as one family:

     unchecked  --surface-disabled fill on --border-default   (the same pair the disabled radio and
                .checkbox-inline already use, so nothing new)
     checked    --brand-mint-bg fill, --brand-mint border, and the SAME check glyph re-inked to
                --text-muted.  Mint at reduced saturation rather than a grey box, because a disabled
                checkbox still has to answer "is it on?" at a glance; #5E6E6A on #DDEBE6 is ~5:1,
                whereas the white tick would have been ~1.9:1 on that fill.

   The glyph is the live data-URI with `stroke='%235E6E6A'` in place of `%23fff` — same path, same
   70% sizing, so the tick cannot drift from the live one.  `cursor: not-allowed` on the box AND on
   its label: the label is the bigger hit target and is what a user actually clicks at.

   Specificity: the fill to beat is `.checkbox label input[type="checkbox"]:checked` (0,3,2).  The
   `:disabled` in each selector below takes the same shapes to (0,4,1)/(0,4,2)/(0,5,2), so every one
   of them wins on count and none needs an `!important`. */
:is(.checkbox, .form-group, .checkbox-inline) input[type="checkbox"]:disabled,
.checkbox label input[type="checkbox"]:disabled,
label > input[type="checkbox"]:disabled {
  background-color: var(--surface-disabled);
  border-color: var(--border-default);
  cursor: not-allowed;
}
label:has(> input[type="checkbox"]:disabled) { cursor: not-allowed; }
/* WARNING — do not re-add a corner fix for the "Číslo bankovního účtu a IBAN" addon here.  A pair of
   `.input-group-btn:last-child > .btn:has(+ .btn:last-child[style*="display:none"])` rules used to
   live at this spot; the markup fix replaced them.  The sometimes-hidden 🗑 is now rendered BEFORE
   the always-visible ✎ in both writers of that addon (BankAccountNumberWithIban::run() and
   widgets/views/bank-account-fields/_create.php), so bootstrap's `:not(:last-child)` never lands on
   a button the user can see and the corners are correct with no CSS at all.  The ordering comment in
   those two files is the one place the constraint is stated. */
/* ---------- One width for every icon-only input-group addon ----------
   The icon-only addons came in five widths (a 22px DatePicker calendar, 43/41/40px icon buttons, a
   34px "Search In API"), so an "addon column" that ought to read as one repeated tile read as three
   sizes.

   40px, and the reasons in order: it is inside the range the ruling names (38-40); it is an as-built
   value, not an invention — two of the five families already computed it; and it is the choice that
   moves the fewest pixels overall.  --control-height (34) was the other candidate and was rejected:
   it would be a true square against the control line but would shrink the app's most common addon
   button by 9px.  Named --size-input-addon so the value cannot drift.

   Text addons ("kg", "kW", "cm3", 38-92px, whose width IS their content), dropdown togglers, which
   carry a caret as a second child, and labelled buttons are all excluded and untouched.

   `:has(> .fa:only-child)` is what "icon-only" means in this markup: every one of these buttons
   renders a single `<i class="fa …">` and nothing else, while a labelled button carries a text node
   beside its icon and a toggler carries a `.caret` as a second ELEMENT child.  The calendar addon is
   addressed by its own class because its glyph is a `.glyphicon`, not a `.fa`.
   Scoped to `.content-wrapper` so the header's global-search group — a labelled "Vyhledat" plus a
   caret toggle — is out of reach by construction.

   BOTH KARTIK CALENDAR CLASSES, and that is the round-36 fix.  `DatePicker` builds its addon class as
   `kv-date-{picker|remove}` (DatePicker.php:368) but `DateTimePicker` builds `kv-datetime-{type}`
   (DateTimePicker.php:316) — a different string for the identical box.  Naming only the first left
   every DateTimePicker addon at bootstrap's own `padding: 6px 12px` around a 14px glyphicon, which
   measures 22px: an 18px-narrower tile in the same addon column, and the "squashed calendar" the
   Poznámka dialog's Termín field was reported for.  Same defect on `/comment/comment/index`'s two
   due-date filters, so it was never about the dialog.  Ten DateTimePicker call sites app-wide, one
   rule for both. */
.content-wrapper .input-group > .input-group-addon.kv-date-picker,
.content-wrapper .input-group > .input-group-addon.kv-datetime-picker,
.content-wrapper .input-group > .input-group-btn > .btn:has(> .fa:only-child) {
  width: var(--size-input-addon);
  min-width: var(--size-input-addon);
  padding-left: 0;
  padding-right: 0;
  text-align: center;
}
/* ---------- A date picker fills its field, in a filter, a form or a dialog ----------
   ONE ROOT CAUSE, THREE REPORTS.  kartik's DatePicker renders `<div class="input-group date">`, and
   bootstrap 3 gives `.input-group` `display: table` with NO width.  A table box shrinks to fit, so
   the control is as wide as its input's default size plus the 40px addon and IGNORES the column it
   sits in — 214px inside a 272px `col-md-3`, ~300px inside a full-width form row — while the selects
   and text inputs around it (plain `.form-control`, `width: 100%`) fill theirs.  It showed up in a
   filter panel, in a create form and in a bsdialog.

   SCOPED TO `.input-group.date` ON PURPOSE — `date` is kartik's own marker class on the picker and
   nothing else in the app uses it.  Widening every `.input-group` would also widen the ones that are
   deliberately content-sized (the bank-account prefix/number pair, amount + currency), so the rule
   names the control that is wrong rather than the box class it happens to use.  `.form-group` keeps
   it to a rendered field. */
.form-group .input-group.date { width: 100%; }

/* ---------- One disabled treatment for every full-size control ----------
   Two disabled fills stood side by side on the same form: bootstrap's own `#EEEEEE` on a native
   `select.form-control[disabled]`, which no rule in this layer had ever restated, and
   `--surface-disabled` on a select2 — a token sized for a 24px checkbox.  12 points apart; one read
   as disabled, one did not.  Both now take --surface-disabled-field, so a disabled input, select,
   textarea and select2 are one surface.  The ink is --text-muted (was bootstrap's off-palette #555)
   and the cursor is not-allowed on all of them.

   `[readonly]` is in the list because a readonly field makes the reader the same promise a disabled
   one does, and because bootstrap already paints the two together — keeping them apart would
   re-create the split this closes.  It reaches nothing today, so it is a guard, not a change.

   NOT TOUCHED, on purpose: the disabled CHECKBOX / RADIO and the disabled BUTTON (`.btn.disabled`).
   They keep --surface-disabled — a 24px mark and a ghost tile do not need the heavier fill, and the
   checkbox's checked-disabled state has its own mint answer that a grey fill would undo.

   Specificity: (0,2,0) for the native half, which beats bootstrap's own (0,2,0) on order and the
   base `.form-control` background (0,1,0) outright.  The select2 half is stated in 2c itself, on the
   rule that already owned that fill, rather than as a competitor. */
.form-control[disabled], .form-control[readonly], fieldset[disabled] .form-control {
  background-color: var(--surface-disabled-field);
  color: var(--text-muted);
  cursor: not-allowed;
  opacity: 1;
}

/* ============================================================================================
   2c. BASE / select2
   ============================================================================================ */

/* ---------- Select2: revive the 13 dead krajee overrides.
   v2.2.5 renders .select2-container--krajee-bs3; old rules targeted --krajee.
   [class*=] matches bs3/bs4/bs5. ---------- */
[class*="select2-container--krajee"] .select2-selection {
  height: var(--control-height);
  border: 1px solid var(--border-default);
  border-radius: var(--radius-control);
  box-shadow: none;
}
[class*="select2-container--krajee"] .select2-selection--single .select2-selection__arrow {
  height: calc(var(--control-height) - 2px);
}
[class*="select2-container--krajee"].select2-container--focus .select2-selection,
[class*="select2-container--krajee"].select2-container--open .select2-selection {
  border-color: var(--ok-green);
  box-shadow: var(--ring-focus);
}
.select2-dropdown {
  border-color: var(--ok-green);
  border-radius: var(--radius-control);
  box-shadow: var(--shadow-dropdown);
}
.has-error [class*="select2-container--krajee"] .select2-selection { border-color: var(--status-danger); }
.has-success [class*="select2-container--krajee"] .select2-selection { border-color: #00A65A; }
.form-control, select.form-control, [class*="select2-container--krajee"] .select2-selection { background: var(--surface-input); }
/* Select2 dropdown: brand states (selected = tint, highlighted = mint).  Both selectors are
   needed and both need the container class: select2 4.1 marks the chosen row with the
   `--selected` CLASS, and krajee-bs3 paints both shapes bootstrap info-blue (#d9edf7) at (0,3,0)
   — a bare `[aria-selected="true"]` rule loses to it and the selected row went BLUE the moment
   the highlight moved off it. */
[class*="select2-container--krajee"] .select2-results__option--selected,
[class*="select2-container--krajee"] .select2-results__option[aria-selected="true"] {
  background: var(--surface-tint);
  color: var(--ok-green);
}
.select2-results__option { padding: 7px 12px; }
.select2-search--dropdown .select2-search__field { border: 1px solid var(--border-default); border-radius: var(--radius-sm); }
/* ---------- One placeholder ink ----------
   Three controls, three greys before this block: the single select2's placeholder span (krajee's
   own grey), the multiple select2's search-textarea ::placeholder (UA grey at the textarea's
   size), and the native select prompt (body ink under base-select). All take the token. The
   vendor rule is (0,3,0), hence the container-class depth. */
[class*="select2-container--krajee"] .select2-selection .select2-selection__placeholder { color: var(--text-placeholder); }
[class*="select2-container--krajee"] .select2-search__field::placeholder { color: var(--text-placeholder); opacity: 1; }
/* ---------- The native <select> picker takes the select2 dropdown anatomy ----------
   The CLOSED control already shares .form-control's geometry; the OPEN list is browser-drawn and
   unreachable by CSS — except in Chromium 134+, where opting a select into `appearance: base-select`
   makes the picker an author-stylable box (`::picker(select)`).  The values below are the
   .select2-dropdown / .select2-results__option values above, restated for the picker's own
   pseudo-elements, so the two dropdown kinds read as one component.  Firefox/Safari ignore the
   whole block (@supports) and keep today's native picker.  A `multiple`/`size` listbox is exempt
   from base-select by definition — but only from the appearance itself: the block's `display:flex`
   and `overflow:hidden` still applied and crushed the admin assignment listboxes to one control
   row, so every selector here excludes them explicitly. */
@supports (appearance: base-select) {
  select.form-control:not([multiple]):not([size]),
  select.form-control:not([multiple]):not([size])::picker(select) {
    appearance: base-select;
  }
  /* bootstrap's `.form-control { display: block }` overrides the UA's flex layout for the
     base-select button, and the chevron then WRAPS under the text as a second line. */
  select.form-control:not([multiple]):not([size]) {
    display: flex;
    align-items: center;
    white-space: nowrap;
    overflow: hidden;
  }
  /* The UA's base-select icon is a FILLED triangle; the app's dropdown glyph is the 1.5px
     chevron in currentColor (see the select2 arrow reshape in 2b) — redrawn here the same way. */
  select.form-control:not([multiple]):not([size])::picker-icon {
    margin-left: auto;
    content: '';
    width: 8px;
    height: 8px;
    border-right: 1.5px solid currentColor;
    border-bottom: 1.5px solid currentColor;
    transform: rotate(45deg) translate(-1px, -1px);
    transition: transform 120ms;
  }
  select.form-control:not([multiple]):not([size]):open::picker-icon { transform: rotate(225deg) translate(-2px, -2px); }
  select.form-control:not([multiple]):not([size])::picker(select) {
    border: 1px solid var(--ok-green);
    border-radius: var(--radius-control);
    box-shadow: var(--shadow-dropdown);
    background: var(--surface-card);
    padding: 0;
    /* select2 scrolls its list inside 200px (measured live); without the cap the picker grows to
       the viewport and shows the page-length scrollbar the list beside it never has. */
    max-height: 200px;
    overflow-y: auto;
  }
  select.form-control:not([multiple]):not([size]) option {
    padding: 7px 12px;
    /* The option would inherit the CONTROL's green; select2 options read in body ink. */
    color: var(--text-body);
    /* The row highlight is the marker — the UA focus ring reads as a second, foreign one. */
    outline: none;
  }
  /* Selected = tint, hovered/keyboard row = mint — the select2 pair above.  Order matters:
     the hover rule is later, so a hovered selected row reads as the highlight, like select2.
     `:focus-visible`, NOT `:focus`: opening the picker focuses the selected option, so plain
     :focus painted the selected row mint permanently and it was indistinguishable from hover;
     only the keyboard's moving row should take the highlight. */
  select.form-control:not([multiple]):not([size]) option:checked {
    background: var(--surface-tint);
    color: var(--ok-green);
  }
  select.form-control:not([multiple]):not([size]) option:hover,
  select.form-control:not([multiple]):not([size]) option:focus-visible {
    background: var(--brand-mint-bg);
    color: var(--ok-green);
  }
  /* After the hover pair: a disabled row must not take the mint highlight — select2 greys
     disabled rows out, and without the grey the row merely refuses the click and reads enabled. */
  select.form-control:not([multiple]):not([size]) option:disabled {
    color: var(--text-disabled);
    background: transparent;
    cursor: not-allowed;
  }
  /* select2 marks the selected row by tint alone; the UA checkmark would be a second marker. */
  select.form-control:not([multiple]):not([size]) option::checkmark { display: none; }
  select.form-control:not([multiple]):not([size]):open {
    border-color: var(--ok-green);
    box-shadow: var(--ring-focus);
  }
  /* The prompt option (value="") is a placeholder, not a value — same ink as select2's. */
  select.form-control:not([multiple]):not([size]):has(> option:checked[value=""]) {
    color: var(--text-placeholder);
    font-weight: 400;
  }
}
.select2-dropdown .select2-search__field:focus,
.select2-search--inline .select2-search__field:focus { border-color: var(--ok-green); }
.select2-search--dropdown .select2-search__field:focus { outline: none; border-color: var(--ok-green); box-shadow: var(--ring-focus); }
[class*="select2-container--krajee"] .select2-selection__clear { color: var(--text-placeholder); }
[class*="select2-container--krajee"] .select2-selection__clear:hover { color: var(--status-danger-text); }
/* Form grid: never let a select truncate — inputs fill their column */
.form-group .form-control, .form-group .select2-container { min-width: 0; max-width: 100%; }
[class*="select2-container--krajee"] .select2-selection--single .select2-selection__clear {
  position: absolute; right: 26px; left: auto; top: 50%;
  transform: translateY(-50%); margin: 0; padding: 0 4px;
  font-size: 15px; font-weight: 700; line-height: 1; color: var(--text-placeholder);
}
[class*="select2-container--krajee"] .select2-selection--single .select2-selection__clear:hover { color: var(--status-danger-text); }
/* Select2 single geometry: pin the clear ✕ to the right, vertically centred (site.css's legacy rule
   floated it above), and undo krajee's 6px vertical padding, which pushed the text line 7px down. */
[class*="select2-container--krajee"] .select2-selection--single { position: relative; padding: 0 24px 0 12px; }
[class*="select2-container--krajee"] .select2-selection--single .select2-selection__rendered { padding: 0 24px 0 0; line-height: calc(var(--control-height) - 2px); }
[class*="select2-container--krajee"] .select2-selection--multiple .select2-selection__choice__remove {
  float: none; margin: 0; order: 2;
  font-size: 14px; line-height: 1;
}
[class*="select2-container--krajee"] .select2-selection--multiple .select2-search--inline .select2-search__field { margin: 0; height: calc(var(--control-height) - 6px); }
/* The MULTI placeholder is a floated span (krajee-bs3:343) — it wrapped to two lines inside the
   flexified selection.  Single line, ellipsized, vertically centred. */
[class*="select2-container--krajee"] .select2-selection--multiple .select2-selection__placeholder {
  float: none; margin: 0; max-width: 100%;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  line-height: calc(var(--control-height) - 4px);
}
/* Multiselect chips: text + ✕ centred on one line, and one chip row must not exceed the control
   height (it was ~38px). */
[class*="select2-container--krajee"] .select2-selection--multiple .select2-selection__choice {
  border-radius: var(--radius-sm);
  background: var(--surface-tint);
  border: 1px solid var(--border-default);
  color: var(--ok-green);
  display: inline-flex;
  align-items: center;
  gap: 6px;
  line-height: 1.4;
  float: none;
  margin: 3px 4px 3px 0;
  padding: 1px 8px;
  /* An inline-flex chip does not wrap its own text, and an ellipsis would not do either: precept
     types differ only in their trailing account numbers, so truncated chips are indistinguishable.
     The text wraps, the multi-select grows (see below), the ✕ stays a flex sibling beside the run. */
  max-width: 100%;
}
[class*="select2-container--krajee"] .select2-selection--multiple .select2-selection__choice > span:last-child {
  white-space: normal;
  overflow-wrap: anywhere;
  min-width: 0;
}
/* Client ruling, overriding the designer's documented intent: the solid dark-green dropdown
   highlight band reads too heavy — soften to mint. */
[class*="select2-container--krajee"] .select2-results__option--highlighted[aria-selected] { background-color: var(--brand-mint-bg); color: var(--ok-green); }
/* A multi-select must GROW instead of holding a fixed height, or wrapped chips break it — and the
   root cause of both "one chip makes it a two-liner" and "a long placeholder is still a two-liner"
   is a select2 VERSION difference.  The app ships select2 4.1, whose multi-select markup is

       .select2-selection--multiple
         > ul.select2-selection__rendered        (the chips, li.select2-selection__choice)
         > span.select2-search--inline
             > textarea.select2-search__field    (carries the PLACEHOLDER as an attribute)

   i.e. the search box is a SIBLING of the chip list, not an item inside it, and there is no
   `li.select2-selection__placeholder` element at all.  Two consequences:

   (a) a `…__rendered { display: flex; width: 100% }` written for the 4.0 markup (where the search
       really was inside the ul) makes the chip list claim the full row, so the sibling search span
       is pushed onto a second line the moment a single chip exists;
   (b) the search field is a <textarea> that select2 leaves at its intrinsic cols=20 width with
       `white-space: pre-wrap`, so a long placeholder wraps and gets clipped.

   Fix: hoist the chips into the selection's own flex context with `display: contents` so chips and
   the search share one wrapping row, let the search span take the remaining width, and make the
   textarea behave like a single-line input. */
[class*="select2-container--krajee"] .select2-selection--multiple {
  height: auto;
  min-height: var(--control-height);
  padding: 1px 6px;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
}
/* …and the rendered UL itself must be a flex row, or the un-floated placeholder/search items stack
   vertically. */
[class*="select2-container--krajee"] .select2-selection--multiple .select2-selection__rendered {
  padding: 0;
  line-height: normal;
  flex-wrap: wrap;
  align-items: center;
  width: 100%;
  display: contents;  /* supersedes the earlier flex/width:100% on this element */
}
[class*="select2-container--krajee"] .select2-selection--multiple .select2-search--inline {
  float: none;
  display: inline-flex;
  align-items: center;
  flex: 1 1 65px;  /* 65px = select2's own min-width for the field */
  min-width: 0;
}
[class*="select2-container--krajee"] .select2-selection--multiple .select2-search__field {
  width: 100% !important;   /* beats the inline width select2's JS writes on the textarea */
  /* select2 pins its own `sans-serif` on this textarea — the one control on the form that was
     not in the app face. */
  font-family: inherit;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  resize: none;
}
/* Select2 inside an AJAX-loaded dialog looked foreign, for two reasons.
   (a) The arrow was never actually restyled: only its HEIGHT was re-set, so the rest still came from
       select2-krajee-bs3.css:71-94 — `border-left: 1px solid #aaa` plus right-side border radii (the
       boxed "segment") and a solid CSS-triangle `b` in #888.  Every other dropdown in OKIS is a
       native <select>, which the browser draws as a thin chevron in the control's own text colour;
       that is why this one stood out.  Reshaped below to the same thing: no segment, no divider, a
       1.5px chevron in currentColor.
   (b) THE WHOLE WIDGET reverts to vendor styling inside a dialog, and this cannot be fixed by load
       order.  A form loaded by AJAX into a bsdialog registers its asset bundles for the first time
       THERE, so select2.css / select2-addl.css / select2-krajee-bs3.css / kv-widgets.css are
       appended into the modal body — i.e. AFTER every stylesheet in <head>.  Document order then
       favours the vendor and every override of ours that merely TIED on specificity silently lost.
       The select2 layer is therefore restated ONE class level higher via a `:root ` prefix: a
       pseudo-class counts as class-level specificity, and `:root X` matches exactly what `X`
       matched, so ties become wins and the widget renders identically whenever the vendor sheet
       loads. */
:root [class*="select2-container--krajee"] .select2-selection {
  border-color: var(--border-default);
  border-radius: var(--radius-control);
  box-shadow: none;
  background: var(--surface-input);
  font-size: 13px;
}
:root [class*="select2-container--krajee"] .select2-selection--single {
  height: var(--control-height);
  padding: 0 24px 0 12px;
}
:root [class*="select2-container--krajee"] .select2-selection--single .select2-selection__rendered {
  padding: 0 24px 0 0;
  line-height: calc(var(--control-height) - 2px);
}
:root [class*="select2-container--krajee"] .select2-selection--multiple {
  height: auto;
  min-height: var(--control-height);
  padding: 1px 6px;
}
:root [class*="select2-container--krajee"] .select2-selection--single .select2-selection__arrow {
  border: none;
  border-radius: 0;
  background: transparent;
  width: 22px;
  right: 1px;
  height: calc(var(--control-height) - 2px);
}
:root [class*="select2-container--krajee"] .select2-selection--single .select2-selection__arrow b {
  border: solid currentColor;
  border-width: 0 0 1.5px 1.5px;
  border-radius: 1px;
  width: 7px;
  height: 7px;
  margin: 0;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -70%) rotate(-45deg);
}
:root [class*="select2-container--krajee"].select2-container--open .select2-selection--single .select2-selection__arrow b {
  border-width: 0 0 1.5px 1.5px;
  transform: translate(-50%, -20%) rotate(135deg);
}
/* State rules must stay ABOVE the base block's new specificity, so they are prefixed too. */
:root [class*="select2-container--krajee"].select2-container--focus .select2-selection,
:root [class*="select2-container--krajee"].select2-container--open .select2-selection {
  border-color: var(--ok-green);
  box-shadow: var(--ring-focus);
}
/* A disabled select2 is a full-size control, so it takes --surface-disabled-field and the muted ink
   and matches the disabled native select beside it — see the disabled block at the end of
   `2b. BASE / form controls`. */
:root [class*="select2-container--krajee"].select2-container--disabled .select2-selection { background-color: var(--surface-disabled-field); cursor: not-allowed; }
:root [class*="select2-container--krajee"].select2-container--disabled .select2-selection__rendered { color: var(--text-muted); }
:root .has-error [class*="select2-container--krajee"] .select2-selection { border-color: var(--status-danger); }
:root .select2-dropdown {
  border-color: var(--ok-green);
  border-radius: var(--radius-control);
  box-shadow: var(--shadow-dropdown);
}
:root .select2-results__option { padding: 7px 12px; }
:root [class*="select2-container--krajee"] .select2-selection .select2-selection__placeholder { color: var(--text-placeholder); }
:root [class*="select2-container--krajee"] .select2-search__field::placeholder { color: var(--text-placeholder); opacity: 1; }
:root [class*="select2-container--krajee"] .select2-results__option--selected,
:root [class*="select2-container--krajee"] .select2-results__option[aria-selected="true"] { background: var(--surface-tint); color: var(--ok-green); }
:root [class*="select2-container--krajee"] .select2-results__option--highlighted[aria-selected] {
  background-color: var(--brand-mint-bg);   /* client decision: mint, not solid green */
  color: var(--ok-green);
}
/* Select2 in a Bootstrap `.input-group` read as two controls: the field kept all four 6px corners
   while the appended button kept its own `0 6px 6px 0`, so the rounded right edge of the field pushed
   into the square left edge of the tinted addon.  ROOT CAUSE: every corner-flattening rule in the
   input-group system keys off `.form-control`, but krajee renders the visible control as
   `span.select2-selection` and leaves the real `.form-control` (the source <select>) hidden at 1px —
   so nothing ever flattened the widget's corners.  The geometry was already right; the radius was the
   whole artifact. */
:root .input-group > [class*="select2-container--krajee"]:not(:last-child) .select2-selection {
  border-top-right-radius: 0;
  border-bottom-right-radius: 0;
}
/* Symmetric case (prepended addon). No live instance found to measure — the hidden <select>
   always precedes the widget, so the prepend has to be matched on the group, not the widget. */
:root .input-group > .input-group-addon:first-child ~ [class*="select2-container--krajee"] .select2-selection,
:root .input-group > .input-group-btn:first-child ~ [class*="select2-container--krajee"] .select2-selection {
  border-top-left-radius: 0;
  border-bottom-left-radius: 0;
}
:root .has-success [class*="select2-container--krajee"] .select2-selection { border-color: var(--ok-green); }
/* ---------- A multi-select's inline search draws no box of its own ----------
   With the container wearing the focus ring, a SECOND green rectangle sat 3px inside it: the inline
   search input.  Two concentric green boxes for one control, from a stack of three stylesheets nobody
   meant to combine — select2's own CSS gives the inline field `border: none`; AdminLTE.min.css puts it
   back (`.select2-dropdown .select2-search__field, .select2-search--inline .select2-search__field
   { border: 1px solid #D2D6DE }` with a #3C8DBC focus variant); skin.css then recolours THAT focus
   border to `--main-color`, so the app's own brand green is what draws the inner box.

   The DROPDOWN half of AdminLTE's pair is deliberately left alone: in a single-select's dropdown that
   input is a real field standing on white, and its box is the only thing that says so.  Only the
   INLINE half is wrong, because there the container is already the field.
   (0,3,0) / (0,4,0) against AdminLTE's (0,2,0) / (0,3,0) and skin.css's (0,3,0), and later than all
   three. */
[class*="select2-container--krajee"] .select2-search--inline .select2-search__field,
[class*="select2-container--krajee"] .select2-search--inline .select2-search__field:focus {
  border: 0;
  outline: none;
  box-shadow: none;
  background: transparent;
}

/* ============================================================================================
   2d. BASE / buttons - geometry, states and the paint families
   ============================================================================================ */
/* Consistent height, real hover shades */
.btn {
  border-radius: var(--radius-control);
  padding: 7px 14px;
  line-height: 20px;
  transition: background-color 120ms, border-color 120ms, transform 80ms, box-shadow 120ms;
}
.btn-xs { padding: 2px 8px; line-height: 18px; }
.btn-sm { padding: 4px 10px; line-height: 18px; }
/* The login page's two large submits, back to the height they had.  `.btn-lg` is a no-op in this
   app: the canonical `.btn` above restates padding AND line-height at the same (0,1,0) and loads
   later — the very reason `--xs` and `--sm` are restated two lines up — and on top of that the
   `.btn-cta` these buttons also carry pins a 36px height in the height ladder below.  Both submits
   therefore came out 36px, the height of a regular button, against the 46px they used to be.
   Scoped and not restated globally: the only other SIZE_LARGE in the tree sits in a box header,
   where adminlte's `.box-header-buttons a { padding: 3px 6px }` (0,1,1) governs the box and a
   global `.btn-lg` would change nothing but the line-height — breaking the one-height header row
   (measured 28px -> 25px, beside a 26px sibling).  Bootstrap's own large padding and its 24px line
   box at the 18px `.login-box .btn` size; `height: auto` hands the box back to them.  Not the 6px
   radius bootstrap also gives `.btn-lg` — radii belong to the design system now. */
.login-box .btn-lg {
  padding: 10px 16px;
  line-height: 24px;
  height: auto;
}
/* THE BARE `btn-default` PAIR STAYS, and the population is no longer first-party markup: kartik
   writes it (bootstrap-fileinput's remove/cancel, the kv-toolbar's five, the kv-navigate pair), so
   does bootstrap3-dialog (the modal header, `.btn-modal-close`), so do the two
   `data-toggle="buttons"` radio-label groups and the discussion status/tag chips.  Nine of the
   twenty are appended to <body>, OUTSIDE `.content-wrapper`, where these two rules are the whole
   palette — measured on kartik's kv-toolbar and kv-navigate: #FFF / #005046 / #B7CFC8 from here. */
.btn-default { color: var(--ok-green); border-color: var(--border-strong); background-color: var(--surface-card); }
.btn-default:hover, .btn-default:focus { background-color: var(--brand-mint-bg); border-color: var(--border-strong); color: var(--ok-green); }
/* A `.btn-primary:hover/:focus/:active/:active:focus { --ok-orange-dark }` block was deleted here.
   It painted exactly one thing: the CONFIRM button of a `bootbox.confirm(message, callback)` call,
   which bootbox class-names itself when the caller does not.  Both such call sites now name their
   buttons, and no markup left in the app carries a bare `btn-primary` outside `.content-wrapper`;
   inside it, `.content-wrapper .btn-primary`'s state block out-ranks every one of those
   declarations at (0,3,0) with the same importance.  A raw `btn-primary` out there would now come
   out bootstrap blue, which is the signal that one has appeared. */
/* A `.btn-success:hover/:focus/:active/:active:focus` pair to `--ok-green-dark` was deleted here:
   every `btn-success` left in the app is inside `.content-wrapper` (the colour-swatch pickers, the
   discussion status chip, the `admin` module's vendor views), where the `.content-wrapper
   .btn-success` state block out-ranks this at (0,3,0) with the same importance, and neither dialog
   engine can produce one outside it — bootbox's fallback only ever writes `btn-primary` /
   `btn-default`, and krajee's header type is hard-coded `type-default`.  Its `opacity: 1` was a
   restatement of the initial value: no rule in any sheet lowers opacity on an enabled button. */
.btn-box-tool { color: var(--ok-green); }
/* THE PAIR STAYS: these are `.btn-ghost-danger`'s values under the legacy key, and they are the
   white-ghost fallback for a raw `btn-danger` rendered outside `.content-wrapper`.  Inside it the
   blush pair in 4a out-ranks both declarations at (0,2,0)/(0,3,0) with the same importance, so the
   measured population here is empty today — but `background: #fff !important` at (0,1,0) is also
   what makes bootstrap's red `.btn-danger` unreachable, and the blush pair's own comment leans on
   it to explain what a pressed raw danger button falls to. */
.btn-danger { background: var(--surface-card) !important; border-color: var(--status-danger-border); color: var(--status-danger-text); }
.btn-danger:hover { opacity: 1; background: var(--status-danger-wash) !important; border-color: var(--status-danger-text); color: var(--status-danger-text); }
.btn-group:has(> .btn-entity) { display: inline-flex; align-items: center; }
.btn-group > .btn-entity { border-radius: var(--radius-control) 0 0 var(--radius-control) !important; }
.btn:active { transform: translateY(1px); }
/* Box-header collapse tool rotates */
.btn-box-tool .fa { transition: transform 200ms; }
.collapsed-box .btn-box-tool .fa-plus { transform: rotate(0deg); }
/* Focus visible everywhere (keyboard users). The STRONG ring, not --ring-focus: on a link or a
   tab the ring is the only cue (no border flip like form controls), and the .14 ring is
   near-invisible (audit 2026-09). Tabs are stated explicitly — bootstrap's own `.nav-tabs a:focus`
   restyle otherwise leaves them with no indicator at all. */
a:focus-visible, .btn:focus-visible { outline: none; box-shadow: var(--ring-focus-strong); }
.nav-tabs > li > a:focus-visible { outline: none; box-shadow: var(--ring-focus-strong); border-radius: var(--radius-control); }
.btn.disabled:active, .btn[disabled]:active { transform: none; }
/* Navbar search: quiet ghost — bell/user/logout stay orange, search recedes */
.navbar-custom-menu .input-group-btn .btn {
  background: var(--surface-card) !important; border-color: var(--border-strong) !important; color: var(--ok-green) !important;
}
/* ---------- Toolbar entity "+" segment ----------
   The attached "+" beside Smlouvy / Obchodní případ / Pojistné události / Předměty, in brand green:
   it is not a page CTA but one segment of a navigation tile, it repeats three or four times in the
   same row, and at that count orange stops meaning "the one action here".  Green is also what makes
   the segment read as part of the `.btn-entity` tile it is attached to.  (The orange CTA ruling covers
   the title-row Uložit disc and the page-level "Nový X" actions, which are the only orange left.)

   THE BLOCK MUST STAY, even though it looks like a plain restatement: three earlier variants of this
   segment (a detached round green dot, a gold one, and an orange `!important` one) all share this
   exact selector in the merged sources, so the LAST declaration in the cascade decides and it has to
   be this one.  It wins that tie on load order.

   Keyed to `.btn-cta` alone; the `+ .btn-success` part it also carried is deleted.  The segment is
   `CreateButton`, i.e. a widget button, and the only markup left that writes `btn-success` by hand is
   the colour-swatch pickers and the discussion status chip — none of them a `.btn-entity` sibling in a
   `.btn-group`, so that part matched nothing.  This rule carries the segment's BOX (width, padding,
   negative margin, radius, `align-self`, `height: auto`), not only its fill: a segment that fell out
   of it would detach from the entity tile and round into a 36px CTA, not merely change colour. */
.btn-group > .btn-entity + .btn-cta {
  align-self: center;
  margin-left: -1px;
  height: auto;
  padding: 7px 10px;
  border-radius: 0 var(--radius-control) var(--radius-control) 0 !important;
  line-height: 20px;
  background: var(--brand-green) !important;
  border-color: var(--brand-green);
  color: var(--text-inverse);
  /* ONE size in both toolbar modes (user ruling).  Text mode sized the segment from `.btn` padding
     (~33px) while icon mode pins the 36px tile, so once the segment gained a `title` it flipped
     between the two.  Pinned to the icon-mode tile. */
  width: var(--size-toolbar-icon-tile);
  padding-left: 0;
  padding-right: 0;
  text-align: center;
}
/* (0,4,0) is what keeps the segment green while it is pressed: `.btn-cta:active:focus` in the
   canonical block is (0,3,0), and a segment pressed with the pointer (which focuses it) would
   otherwise take the orange CTA's dark fill for exactly that state. */
.btn-group > .btn-entity + .btn-cta:hover,
.btn-group > .btn-entity + .btn-cta:focus,
.btn-group > .btn-entity + .btn-cta:active {
  background: var(--brand-green-dark) !important;
  border-color: var(--brand-green-dark);
  color: var(--text-inverse);
}
/* ---------- Two banknotes, stacked — the "Rozdělené platby" glyph ----------
   Emitted by Icon::createMoneyStackIcon(): two `fa-money` glyphs in one wrapper, the second in normal
   flow and the first pulled up and left behind it.  FontAwesome 4 has no torn banknote and no
   two-banknote glyph, and the neighbouring Platby keeps the single note, so the pair has to be composed.

   THE CLIP IS WHAT MAKES IT READ AS A STACK.  `fa-money` is a HOLLOW glyph — a solid frame around a
   transparent bill — so the front note occludes nothing: laid over each other the two frames simply
   cross and the mark turns to mush (measured).  The offset note is therefore clipped
   to the L-shaped sliver that the front note does not cover: a strip along the top and a strip down the
   left, exactly the offset wide.  A knockout halo would have done the same job, but only against one
   background — a white halo is plainly visible once the ghost button hovers to --brand-mint-bg, and a
   clip is independent of whatever is behind it.

   THE THREE 4px VALUES MUST AGREE: two are the offset, one is the width of the strip the clip keeps.
   Change one and the back note either loses its peek or shows a frame line through the front note.

   THE WRAPPER MUST KEEP `line-height: 1`, and that is not free: the strut compensator hands the
   button's own line-height to every `.fa` child of a canonical button, and on THIS child it would push
   the in-flow note 7.5px below the absolute one instead of 4px — the clip would then keep a strip that
   never reaches the front note, and the stack would show 3.5px of blank between the two banknotes.  The
   compensator excludes this class by name; the note is there too.

   The translate is the optical centring, and it is measurable, not taste.  Only the FRONT note is a
   flex item, so `justify-content: center` centres THAT note and the back note's peek then hangs
   outside the tile's middle: measured 2px left and 2px high of centre against the single `fa-money`
   of the Platby tile beside it.  Layout-neutral on purpose — a margin would be re-halved by the
   centring it is trying to correct.  After it, both tiles' ink centres agree exactly — 18.00, 17.75 in
   a 36px tile, read off the pixels of both tiles in the same screenshot. */
.btn-glyph-money-stack {
  position: relative;
  transform: translate(2px, 2px);
}
.btn-glyph-money-stack > .fa-money:first-child {
  position: absolute;
  left: -4px;
  top: -4px;
  clip-path: polygon(0 0, 100% 0, 100% 4px, 4px 4px, 4px 100%, 0 100%);
}

/* ============================================================================================
   2e. BASE / boxes and surfaces
   ============================================================================================ */
.popover { border-radius: var(--radius-box); }
.popover-content { background-color: var(--surface-card); color: var(--ok-green); }
/* Tabs: quiet text on page bg, active = green with gold underline (pairs with title bar) */
.nav-tabs-custom { background: transparent; box-shadow: none; border-radius: 0; }
.nav-tabs-custom > .nav-tabs, .nav-tabs { border-bottom: 1px solid var(--border-default); }
.nav-tabs > li { margin-right: 26px; margin-bottom: -1px; }
.nav-tabs > li > a, .nav-tabs-custom > .nav-tabs > li > a {
  border: none !important; border-bottom: 3px solid transparent !important;
  border-radius: 0; background: transparent !important; color: var(--text-muted); padding: 10px 2px; margin: 0;
}
.nav-tabs > li > a:hover { color: var(--ok-green); }
.nav-tabs > li.active > a, .nav-tabs > li.active > a:hover, .nav-tabs > li.active > a:focus,
.nav-tabs-custom > .nav-tabs > li.active > a {
  border-bottom-color: var(--ok-gold) !important; color: var(--ok-green); font-weight: 600;
}
.nav-tabs-custom > .tab-content { background: transparent; padding: 16px 0 0; border-radius: 0; }
/* Dropdowns fade in */
.dropdown-menu {
  border-radius: var(--radius-dropdown);
  box-shadow: var(--shadow-dropdown);
  border-color: var(--border-default);
  animation: ok-pop 140ms cubic-bezier(.2,.6,.3,1);
  transform-origin: top;
}

/* ============================================================================================
   2f. BASE / tables and pagination
   ============================================================================================ */
/* ---------- Grid / tables / pagination (kills stock-BS3 blue leak) ---------- */
.table > thead > tr > th { border-bottom-color: var(--border-strong); }
.table-hover > tbody > tr:hover { background-color: var(--surface-row-hover); }
.table > thead > tr > th, .table > tbody > tr > td { border-color: var(--border-default); }
.table-bordered, .table-bordered > thead > tr > th, .table-bordered > tbody > tr > td { border-color: var(--border-default); border-left: none; border-right: none; }
.table > thead > tr > th { border-bottom: 2px solid var(--border-default); }
/* Row states in grids: success = mint, danger = orange light.
   All four fills come from the `--row-tint-*` group so the colour-swatch pickers can sample the same
   token — see that group in 1. TOKENS.  `warning` and `info` carry no `!important` because they do
   not need one — the only declaration at their (0,2,2) is bootstrap's, and this file loads last —
   whereas success/danger keep theirs.  Hover: success and danger have their own; warning and info
   keep bootstrap's. */
/* The state is the tint alone; the ink stays the body ink. */
.table > tbody > tr.success > td { background-color: var(--row-tint-success) !important; }
.table-hover > tbody > tr.success:hover > td { background-color: var(--ok-mint) !important; }
.table > tbody > tr.warning > td { background-color: var(--row-tint-warning); }
.table > tbody > tr.danger > td { background-color: var(--row-tint-danger) !important; }
.table-hover > tbody > tr.danger:hover > td { background-color: var(--ok-orange-light) !important; }
.table > tbody > tr.info > td { background-color: var(--row-tint-info); }
/* `success-dark`: the collection grid's "payment belongs to the selected precept" row
   (collection/_grid.php rowOptions) — one step stronger than the `success` tint. */
.table > thead > tr > td.success-dark, .table > tbody > tr > td.success-dark, .table > tfoot > tr > td.success-dark, .table > thead > tr > th.success-dark, .table > tbody > tr > th.success-dark, .table > tfoot > tr > th.success-dark, .table > thead > tr.success-dark > td, .table > tbody > tr.success-dark > td, .table > tfoot > tr.success-dark > td, .table > thead > tr.success-dark > th, .table > tbody > tr.success-dark > th, .table > tfoot > tr.success-dark > th {
    background-color: var(--ok-mint);
}
/* The zebra kill used to stand here; it is now stated above the hover rule — see that note. */
.table > tbody > tr { transition: background-color 120ms; }
.table > tbody > tr > td { padding-top: 10px; padding-bottom: 10px; }
.table-bordered { border: none; }
.table-bordered > thead > tr > th, .table-bordered > tbody > tr > td { border-left: none; border-right: none; }

/* ============================================================================================
   2g. BASE / layout chrome
   ============================================================================================ */
/* `.main-header .navbar .btn-primary:hover` deleted: `.main-header .navbar .navbar-nav > li > .btn:hover`
   further down is (0,5,1) with the same `!important` and reaches every navbar button by structure, so
   the bell and the user chip keep their hover without any variant class in the selector. */
/* ---------- Chrome ---------- */
.main-header, .skin-blue .main-header .navbar { background-color: var(--ok-green); }
.skin-blue .main-header .navbar .sidebar-toggle:hover,
.skin-blue .main-header .logo:hover,
.skin-blue .main-header .logo { background: rgba(0, 0, 0, 0.1); }
.main-header .logo .logo-lg img { image-rendering: auto; }
.skin-blue .main-header .logo .logo-lg img { margin-right: 10px; margin-top: -5px; }
.content-wrapper, .right-side { background-color: var(--surface-page); }
.skin-blue .content-header { background-color: var(--surface-page); }
/* The label selector is the sub-header idiom: a BARE label (no class, no wrapped input) renders
   bootstrap-bold and acts as a section label (profile's Interval avíza, SUS/KNZ ID), unlike
   classed field labels (label.control-label/.form-label, calm --text-label) and checkbox labels
   (input inside). */
.content-header > h1, .box-title, .modal-title,
.box-body > h4,
.box-body label:not([class]):not(:has(input)) { color: var(--ok-green); }
.progress, .progress-bar { border-radius: var(--radius-pill); }
.main-footer { border-top: none; background: var(--ok-green); font-size: 11px; color: var(--ok-mint); }
/* audit 2026-09: the base link ink reads 3.31:1 on the green band — the light brand orange passes at 5.95:1 */
.main-footer a { color: var(--brand-orange-light); }
/* Quiet buttons: `btn-primary` is the white ghost in the content area.
   THE PAIR STAYS, and its population is vendor now: kartik bootstrap-fileinput's browse button
   (`browseClass: "btn btn-primary"`, four of them on the import pages) and the `admin` module's
   views, both rendering inside `.content-wrapper` with no canonical class.  These two rules are their
   only white fill; delete the pair and they drop to skin.css's bare orange `.btn-primary`.  No
   first-party markup and no widget writes the class any more. */
.content-wrapper .btn-primary {
  background: var(--surface-card) !important;
  border-color: var(--border-strong);
  color: var(--ok-green);
}
.content-wrapper .btn-primary:hover, .content-wrapper .btn-primary:focus,
.content-wrapper .btn-primary:active, .content-wrapper .btn-primary:active:focus {
  background: var(--surface-tint) !important;
  border-color: var(--ok-green);
  color: var(--ok-green);
}
.skin-blue .main-sidebar .sidebar .sidebar-menu > li:hover > a { background: var(--surface-tint); border-left-color: transparent; color: var(--ok-green) !important; }
.skin-blue .main-sidebar .sidebar .sidebar-menu > li.active > a { background: var(--surface-tint); border-left-color: var(--brand-gold); color: var(--ok-green) !important; font-weight: 600; }
.main-header { box-shadow: 0 1px 4px rgba(0,40,35,.18); }
/* User ruling (skin retirement R7): the wordmark adopts Inter, at the legacy 25px. The family
   must be stated, not inherited: vendor AdminLTE.min.css pins `.main-header .logo` to the
   Helvetica stack at the same specificity, and only file order puts Inter back. */
.main-header .logo { font-size: 25px; font-family: "Inter", Helvetica, sans-serif; }
/* The sidebar keeps the brand mint, so the left edge stays recognisably OKIS.  The green ink is
   sidebar-only: `.wrapper` spans the whole app, and inking it would make green the inherited
   default of every element no rule colors. */
.skin-blue .wrapper, .skin-blue .main-sidebar, .skin-blue .left-side { background: var(--brand-mint-bg); }
.skin-blue .main-sidebar, .skin-blue .left-side { color: var(--ok-green); }
.main-sidebar { border-right: none; }
.skin-blue .main-sidebar .sidebar .sidebar-menu > li > a { border-left: 3px solid transparent; background: transparent; color: var(--ok-green); }
.skin-blue .sidebar-menu > li > a:link, .skin-blue .sidebar-menu > li > a:visited { color: var(--ok-green); }
.skin-blue .main-sidebar .sidebar .sidebar-menu > li:hover > a { background: rgba(255,255,255,.55); color: var(--ok-green) !important; }
.skin-blue .main-sidebar .sidebar .sidebar-menu > li.active > a { background: var(--surface-card); border-left-color: var(--brand-gold); color: var(--ok-green) !important; }
/* A solid-red `.content-wrapper .btn-danger` pair was deleted here: the blush pair further down
   opens with the same two selectors, character for character, at the same (0,2,0) / (0,3,0), with
   the same `!important` on `background` and the same two unimportant properties, later in this
   sheet — so it has never painted anything. Measured over the corpus: every danger button inside
   `.content-wrapper` computes the blush #F5D5D0, never #D93025. */
.content-wrapper .btn.btn-star { background: var(--surface-card) !important; border-color: var(--border-strong); color: var(--ok-gold); font-weight: 700; }
.content-wrapper .btn.btn-star:hover { background: var(--surface-tint) !important; border-color: var(--ok-gold); color: var(--brand-gold-dark); }
.content-wrapper .btn.btn-entity:hover, .content-wrapper .btn.btn-entity:focus,
.content-wrapper .btn.btn-entity:active { background: var(--ok-mint) !important; border-color: var(--ok-mint); color: var(--ok-green); }
/* The label half of the entity toolbar duo — Smlouvy / Obchodní případ / Pojistné události, each
   attached to the ⊕ `.btn-cta` beside it.  Quiet white ghosts. */
.content-wrapper .btn.btn-entity {
  background: var(--surface-card) !important;
  border-color: var(--border-strong);
  color: var(--ok-green);
  font-weight: 400;
}
.content-wrapper .btn.btn-entity:hover { background: var(--brand-mint-bg) !important; border-color: var(--border-strong); color: var(--ok-green); }
.main-header .btn-user { background: var(--ok-gold) !important; border-color: var(--ok-gold); color: var(--ok-green); font-weight: 600; }
/* `.main-header .btn-primary .badge` deleted: the "Unread count" block below declares the same two
   properties at the same (0,3,0), also `!important`, and later in the sheet — so the chipless green
   number already won. Measured on the unread bell: `rgba(0,0,0,0)` fill, `rgb(0,80,70)` digit, i.e.
   neither of the white-pill/orange values this rule asked for. */
.main-header .btn-logout { background: var(--ok-mint) !important; border-color: var(--ok-mint); color: var(--ok-green); }
/* The `.btn-primary.btn-user` and `.btn-primary.btn-logout` halves of these rules are deleted, and
   the two `:hover` rules that had no other half with them.  Only two elements in the app can carry
   `btn-user` / `btn-logout` — `views/layouts/header.php`'s chip and `LogoutButton`, which is
   TYPE_NONE — and neither writes a paint class any more, so the combination has no member. */
.main-header .btn-user { background: var(--ok-orange) !important; border-color: var(--ok-orange); color: var(--text-inverse); font-weight: 400; }
.main-header .btn-logout { background: var(--ok-orange) !important; border-color: var(--ok-orange); color: var(--text-inverse); }
/* Active sidebar item matches page bg */
.skin-blue .main-sidebar .sidebar .sidebar-menu > li.active > a { background: var(--main-bg-color) !important; }
::selection { background: var(--ok-mint); color: var(--ok-green); }
/* Slim brand scrollbars */
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-thumb { background: var(--border-strong); border-radius: var(--radius-pill); border: 2px solid transparent; background-clip: content-box; }
::-webkit-scrollbar-thumb:hover { background-color: var(--ok-mint); }
::-webkit-scrollbar-track { background: transparent; }
/* Sidebar: icons align, active item slides */
.sidebar-menu > li > a { transition: background-color 150ms, border-color 150ms, padding-left 150ms; }
.sidebar-menu > li:hover > a { padding-left: 18px; }
.sidebar-menu > li > a > .fa { width: 22px; }
/* Notification badge: subtle pulse ring on load */
.main-header .badge { position: relative; }
/* A field can append several buttons (Klient: eye + pencil) and each sits in its own
   .input-group-btn span — the tint belongs to all of them, not only the closing one;
   the :last-child pair below keeps owning the corner radius. */
.input-group-btn > .btn { height: var(--control-height); background: var(--surface-tint); border-color: var(--border-default); color: var(--ok-green); }
.input-group-btn > .btn:hover { background: var(--brand-mint-bg); border-color: var(--border-strong); color: var(--ok-green); }
.input-group-btn:last-child > .btn { border-radius: 0 var(--radius-control) var(--radius-control) 0; height: var(--control-height); background: var(--surface-tint); border-color: var(--border-default); color: var(--ok-green); }
.input-group-btn:last-child > .btn:hover { background: var(--brand-mint-bg); border-color: var(--border-strong); color: var(--ok-green); }
.input-group-btn:first-child > .btn { border-radius: var(--radius-control) 0 0 var(--radius-control); height: var(--control-height); }
/* A unit TOGGLE in an input group (the fee Kč/% switch): the selected segment must read selected.
   The uniform appended-button tint above erased bootstrap's pressed state, leaving two identical
   tiles. Keyed on the data-toggle wrapper so plain appended buttons stay out of it. */
.btn-group[data-toggle="buttons"] > .btn {
  background: var(--surface-tint);
  border-color: var(--border-default);
  color: var(--ok-green);
  box-shadow: none;
}
.btn-group[data-toggle="buttons"] > .btn:hover { border-color: var(--border-strong); }
.input-group-btn[data-toggle="buttons"] > .btn.active,
.input-group-btn[data-toggle="buttons"] > .btn.active:hover,
.btn-group[data-toggle="buttons"] > .btn.active,
.btn-group[data-toggle="buttons"] > .btn.active:hover {
  background: var(--ok-green);
  border-color: var(--ok-green);
  color: var(--text-inverse);
  box-shadow: none;
}
/* The fee group stacks TWO inputs (Kč amount, % amount) and shows one at a time; the % input is
   bootstrap's MIDDLE child, whose corners are all zeroed — in % mode the group's left end went
   sharp. Both inputs open the group, so both take the first-child shape. One class deeper than
   bootstrap's (0,4,1) zeroing rule. */
.form-group.field-precept-fee .input-group > .form-control:not(:last-child) {
  border-radius: var(--radius-control) 0 0 var(--radius-control);
}
/* Bootstrap leaves the outer segments at the 4px .btn base; the group's outer corners must match
   the 6px control radius, which the dark active fill makes visible. */
.input-group-btn[data-toggle="buttons"] > .btn:last-child,
.btn-group[data-toggle="buttons"] > .btn:last-child {
  border-top-right-radius: var(--radius-control);
  border-bottom-right-radius: var(--radius-control);
}
.btn-group[data-toggle="buttons"] > .btn:first-child {
  border-top-left-radius: var(--radius-control);
  border-bottom-left-radius: var(--radius-control);
}
/* Navbar search hover: keep white text on orange (dark-green leak fix).
   The `.main-header .navbar .btn-primary:hover` and `:focus` parts this selector opened with are
   deleted: `.main-header .navbar .navbar-nav > li > .btn:hover/:focus` is (0,5,1) and `!important` on
   all three of background / border-color / color, and it reaches every navbar button by structure
   rather than by variant class. */
.navbar-custom-menu .input-group-btn .btn:hover { background: var(--ok-orange-dark) !important; border-color: var(--ok-orange-dark); color: var(--text-inverse) !important; }
.navbar-custom-menu .input-group-btn .btn:hover, .navbar-custom-menu .input-group-btn .btn:focus {
  background: var(--brand-mint-bg) !important; border-color: var(--border-strong) !important; color: var(--ok-green) !important;
}
/* The navbar search field keeps its solid mint fill and dark-green placeholder by user ruling
   (skin retirement R6, 2026-09-21) — deliberately NOT the app's white input treatment: it is the
   one input sitting on the dark header. */
.navbar-custom-menu form input[type="text"] { background-color: var(--ok-mint); border-color: var(--ok-mint); }
.navbar-custom-menu form ::placeholder { color: var(--ok-green); }
/* Submenu (treeview): stays light — no dark slab. Slightly recessed mint panel,
   indented items, active = white pill + gold dot; parent keeps its open state */
.skin-blue .sidebar-menu > li.treeview.menu-open > a,
.skin-blue .main-sidebar .sidebar .sidebar-menu > li.active.treeview > a { background: rgba(255,255,255,.55); color: var(--ok-green) !important; }
.skin-blue .sidebar-menu > li > .treeview-menu, .skin-blue .sidebar-menu .treeview-menu { background: rgba(0,80,70,.07) !important; padding: 4px 0; margin: 0; }
.skin-blue .sidebar-menu > li > .treeview-menu > li > a, .skin-blue .sidebar-menu .treeview-menu > li > a {
  color: var(--text-label); padding: 7px 5px 7px 38px; font-size: 13px;
  border-left: 3px solid transparent; transition: background-color 120ms, color 120ms;
  /* adminlte nowraps every sidebar link; a long submenu label then ran under the sidebar's edge
     and read cut off ("Typy sledovaných povinnos…").  Flex, not a text-indent hanging indent: the
     glyph and the <span> label become separate boxes, so a wrapped line starts where the label
     does — after the dot — with no font-dependent magic offsets. */
  white-space: normal;
  display: flex;
  /* baseline, not flex-start: the glyph's box is shorter than the text line, so top-aligning
     floats the dot above the first line's optical center. */
  align-items: baseline;
}
.skin-blue .sidebar-menu > li > .treeview-menu > li > a > .fa { flex: 0 0 16px; }
.skin-blue .sidebar-menu > li > .treeview-menu > li > a > span { flex: 1 1 auto; min-width: 0; }
.skin-blue .sidebar-menu .treeview-menu > li > a:hover { color: var(--ok-green) !important; background: rgba(255,255,255,.7); }
.skin-blue .sidebar-menu > li > .treeview-menu > li.active > a, .skin-blue .sidebar-menu .treeview-menu > li.active > a {
  color: var(--ok-green) !important; font-weight: 600; background: var(--surface-card) !important; border-left-color: transparent;
}
/* THE SUBMENU'S GLYPH COLUMN IS NOT BULLETS-ONLY, so it cannot be sized like a bullet: five of the
   routes under Řízení přístupu carry real marks (`fa-user-circle`, `fa-users`, `fa-key`,
   `fa-shield`) next to the `fa-circle-o` the rest of the menu uses, and a size chosen for a dot
   turns those four into specks. The glyph therefore INHERITS the row's 13px — the same size the
   parent-level sidebar icons render at, which is the column's reference — exactly as the
   parent-level rule does by declaring no size at all. No `vertical-align` for the same reason:
   the +2px that optically centred a 7px dot against 13px text lifts a same-size glyph off the
   line (measured 1.5px high; baseline lands it within 0.5px).

   16px, not the parent's 22: the widest glyph here measures 13.94px (`fa-users` at 13px), and the
   narrower box is what keeps the indented labels on one left edge.

   The ACTIVE item keeps its OWN glyph, re-inked gold (the disc swap that used to replace it was
   ruled out once the glyphs became legible: the mark identifies the item, the gold carries the
   state). Rows whose stored icon IS the bullet ring still read as before. */
.skin-blue .sidebar-menu .treeview-menu > li > a > .fa { width: 16px; color: var(--ok-mint); }
.skin-blue .sidebar-menu .treeview-menu > li.active > a > .fa { color: var(--brand-gold); }
/* The legacy navbar metrics. The rail's flex block below out-ranks them on its own tiles
   (`> .navbar-nav > li > .btn` is (0,3,1)), but a .btn or input-group reached by any other path
   under the menu still measures by these. */
.navbar-custom-menu li > .btn { margin-top: 9px; padding: 6px 10px; }
.navbar-custom-menu .form-inline .form-group { vertical-align: inherit; }
.navbar-custom-menu .form-group .input-group { min-width: 200px; width: 25vw; background-color: var(--surface-card); }
.navbar-custom-menu .input-group .input-group-btn { width: 1%; }
.navbar-custom-menu .help-item-icon { color: var(--surface-card); }
/* The SSO shortcut tiles (views/layouts/header.php) — the one navbar tile with a border and the
   CTA fill.  Every declaration is important: the tile is an <a class="btn"> on the vendor navbar,
   outside .content-wrapper, where the canonical paint families do not reach and vendor sheets can
   re-register after this file. */
.sso-btn {
    display: inline-block !important;
    border-style: solid;
    border-width: 2px;
    border-color: var(--ok-orange-light) !important;
    background-color: var(--ok-orange) !important;
    height: 34px !important;
}
.sso-move-left {
    margin-left: -4px !important;
}
/* The <hr> separators inside the SSO dropdown menu (header.php builds them into the items). */
.hr-margin {
    margin-top: 2px !important;
    margin-bottom: 2px !important;
}
/* The user chip's two inline parts (header.php): the name keeps air before the caret, the glyph
   rides one step larger than the label. */
.user-display-name { margin-right: 20px; }
.user-icon { font-size: 1.25em; }
/* The `li > .btn` metrics above and `.sso-btn`'s `height: 34px !important` fight the
   new 36px control height — unify the navbar: */
.navbar-custom-menu .btn { height: var(--control-height); }
.navbar-custom-menu .btn.sso-btn { height: var(--control-height) !important; } /* beats .sso-btn's own !important above on specificity */
/* One rhythm between navbar items. The legacy sheet spaced them with padding-LEFT only on each
   <li> and the search form added its own 10px side padding, so the bell had ~20px on its left and
   ~10px on its right (an SSO <li> that renders empty for most users contributed phantom width
   too). The flex gap owns the spacing; the li paddings and the form's side padding are zeroed so
   they cannot add to it, and a li with no rendered content stops occupying a gap slot. */
.navbar-custom-menu > .navbar-nav {
  display: flex;
  align-items: center;
  gap: 10px;
  height: 50px; /* adminlte's fixed navbar height; the rail must fill it or the centering has no axis */
}
.navbar-custom-menu > .navbar-nav > li { padding-left: 0; }
/* The vertical axis belongs to the flex centering too: skin/adminlte centered each item by hand
   (margin-top 9px on the tiles, 3px + 6/6 padding on the search form), and those offsets survive
   inside flex items — the tiles sat 3-4px lower than the search group. Zeroed so one rule decides.
   The PADDING is stated here for the same reason, one level in: AdminLTE gives
   `.navbar-custom-menu .navbar-nav > li > a` `padding-top/bottom: 15px` with `line-height: 20px`
   BELOW 992 only, and that cannot fit the tile's fixed 34px `height` — border-box leaves a 2px
   content box, so the 20px line box starts at the padding edge and the GLYPH's centre lands 9.44px
   below the tile's own.  Measured on the bell and on the logout at every width from 360 to 991, in
   both sidebar states and on both routes: the tile BOXES are on the axis, their INK is 9.44 under
   it.  The chip escaped only because it is a flex container below 992 (a flex container has no line
   box to displace) and the field only because it is sized by its input-group.  6px/10px is skin.css's
   own `.navbar-custom-menu li > .btn` padding — the value the >=992 band already computes — restated
   at (0,3,1), where AdminLTE's (0,2,2) cannot outrank it, so the tile's box model stops depending on
   the viewport.  That is also why it needs no media query: above 992 it declares what is already
   computed there. */
.navbar-custom-menu > .navbar-nav > li > .btn {
  margin-top: 0;
  margin-bottom: 0;
  padding: 6px 10px;
}
/* The SSO tile is the one navbar tile with a 2px border (the `.sso-btn` rule above), so the same 34px
   box has 2px less room for padding: (34 - 20 line-height - 4 border) / 2 = 5, and at 6 its glyph
   rides 1.44px under the axis at EVERY width, the desktop included.  (0,4,1) so it outranks the
   6px rule above rather than fighting it. */
.navbar-custom-menu > .navbar-nav > li > .btn.sso-btn {
  padding-top: 5px;
  padding-bottom: 5px;
}
.navbar-custom-menu > .navbar-nav > li > form {
  margin-top: 0;
  padding: 0;
}
.navbar-custom-menu > .navbar-nav > li:not(:has(a, form, button, .btn, .dropdown)) { display: none; }
/* ---------- The devel branch badge ----------
   `widgets/devel/DevelInfo.php` prints the checked-out branch, and it is the one navbar item that is
   NOT inside `.navbar-custom-menu`: it sits between the floated `.sidebar-toggle` and the floated
   rail.  Given a float of its own it takes a column out of the rail's `calc(100% - 42px)`, and the
   rail is a float, so it cannot give the column back — measured at 700px, it dropped to a second
   50px deck below the badge.  So the bar itself becomes a flex row, which is the same move the rail's
   own width rule makes one level down: as a flex item the rail's width is a basis rather than a
   floor, and the `flex-shrink: 100` search field inside it absorbs the badge's column (700px: rail
   622 -> 530, field 115, bar still 50).  `margin-left: auto` restates the `float: right` that flex
   ignores, and `min-width: 0` is what lets the shrink reach the field at all.  `:has()` keeps every
   deployed bar on the float layout untouched — the widget needs YII_DEBUG and a branch file, so the
   badge exists only on a developer's own machine.
   The shape is the app's `.status-chip`, composed in the markup rather than restated here, so
   4 + 16 + 6 = 26px; the row's centring puts it on the bar's axis, and `nowrap` keeps the bar 50px
   for a long branch name.  Cream fill on `--panel-warning-text` is the warning chip pair
   `.comment-status--pending` and `.cbl-state--validate` already use; on the dark green bar it needs
   no border to separate. */
.main-header .navbar:has(> .devel-info-chip) { display: flex; align-items: center; }
.main-header .navbar:has(> .devel-info-chip) > .navbar-custom-menu { margin-left: auto; min-width: 0; }
.devel-info-chip {
  flex: none;
  margin-left: 8px;
  background: var(--ok-cream);
  color: var(--panel-warning-text);
  white-space: nowrap;
}
/* The user chip carries BOTH of its forms in the markup — the display name plus the settings gear,
   and the user glyph that stands in for them — and exactly one of them renders.  The name form is
   the default, so the glyph is off here and the width stages further down turn it on and the name
   off; stated BEFORE them because they are the same or higher specificity and file order is what
   decides between equals. */
.navbar-custom-menu .btn-user .user-avatar { display: none; }
/* ---------- The header rail fits every width from 360px up ----------
   The rail is a flex row, but until this block it had no width to shrink INSIDE: both
   `.navbar-custom-menu` and its `<ul>` are floats, a float is shrink-to-fit, so every item kept its
   max-content size and the surplus left the viewport instead.  Measured on the corpus rail at 360px:
   `.navbar-custom-menu` at x = -171.4 — the search group entirely off-screen to the left and the row
   itself pushed to a third deck below `.main-header`'s 100px max-height, over the page content.
   The same arithmetic, one bar up: AdminLTE gives `.main-header .navbar` `margin-left: 230px` for the
   expanded sidebar, so the toggle (41.2) plus the rail (531.4) need 572.6 in `vw - 230`, and 768-802
   could not seat them either.

   Giving the float a definite width is what makes flex-shrink real, and it is ONE declaration for
   all four regimes because `100%` already carries the sidebar: the navbar's content box is `vw - 230`
   expanded, `vw - 50` collapsed and `vw` in the <=767 two-deck header.  `- 42px` is the floated
   `.sidebar-toggle` (41.16, padding 15/15 around an 11.16 glyph) that the rail must not sit on top
   of.  `float: none` on the `<ul>` is required or the inner float would go on sizing itself to its
   content inside the now-fixed outer box.

   The rail's 10px end insets were never declared anywhere: bootstrap's `.nav:before/:after` clearfix
   pair are flex items in a flex container, so with `gap: 10px` each donated one gap at one end — and
   two phantom gaps to the float's intrinsic width, which is why the rail measured 531.38 when its
   items and real gaps come to 511.37.  A flex container needs no clearfix, so they are dropped here
   and the inset is stated as `padding-right`, which both makes it explicit and returns 20px of the
   budget to the narrowest widths.  `justify-content: flex-end` then holds the rail against that
   inset whatever the surplus.

   The search group is the only item that gives way, and it gives way by WEIGHT rather than by
   breakpoint, so that stays true for any unread count and any of the three SSO shapes:
   `flex-shrink: 100` against everything else's `flex: none` means it absorbs essentially the whole
   deficit until it is frozen at its floor.  Its base stays `max(200px, 25vw)`, which is exactly what
   skin.css's `min-width: 200px` + `width: 25vw` pair computes, so nothing moves at a width that was
   already fitting.  Bell, SSO, chip and logout never give way — they are tiles or they are whole,
   and they are what the shrinking is FOR.  What happens once the field is frozen is the chip's own
   two-form flip, and that is the block after this one.
   The chip becomes a real flex row here: its `d-flex align-items-center` names no rule in this app
   (bootstrap 4 markup, bootstrap 3 sheet), and centring its items on the tile's own centre is what
   keeps both of its forms on the bar's axis — the name's box hung 1px BELOW the chip's bottom border
   at every width under 992 before it, its baseline set by the `padding: 15px 10px` AdminLTE gives
   `.navbar-custom-menu .navbar-nav > li > a` below 992 and the 34px `height` then overrode.
   `min-width` states the ruling's 36px floor for the tap target; the glyph form measures 39.34, so
   it is a stated constraint rather than a load-bearing one.  `white-space: nowrap` is what keeps the
   bar 50px tall whatever the name's length: a name too wide for its rail overflows the rail
   sideways, it never wraps and deepens the header.

   `max-width: 991px` is the app's existing sub-desktop boundary (adminlte.css splits at 991/992) and
   keeps every declaration here out of the desktop census. */
@media (max-width: 991px) {
  .navbar-custom-menu { width: calc(100% - 42px); }
  .navbar-custom-menu > .navbar-nav {
    float: none;
    justify-content: flex-end;
    padding-right: 10px;
  }
  .navbar-custom-menu > .navbar-nav::before,
  .navbar-custom-menu > .navbar-nav::after { display: none; }
  .navbar-custom-menu > .navbar-nav > li { flex: none; }
  /* 140px is the field's floor: the two input-group buttons measure 86.94 together, so it leaves a
     measured 55.1px of typing room. Below 480 the rail can no longer hold even that — next block. */
  .navbar-custom-menu > .navbar-nav > li:has(form) {
    flex: 0 100 max(200px, 25vw);
    min-width: 140px;
  }
  .navbar-custom-menu .form-group .input-group { width: 100%; min-width: 0; }
  /* Below 768 bootstrap's own `.form-inline .form-group { display: inline-block; margin-bottom: 0 }`
     stops applying (it is inside `@media (min-width: 768px)`), so the group kept a 10px bottom
     margin, made its <li> 44px tall in a 50px rail and the flex centring then sat the field 5px
     ABOVE the tiles beside it. Zeroing it is a no-op from 768 up, where bootstrap already does. */
  .navbar-custom-menu .form-inline .form-group { margin-bottom: 0; }
  .navbar-custom-menu > .navbar-nav > li:has(.btn-user) { min-width: var(--size-toolbar-icon-tile); }
  .navbar-custom-menu .btn-user {
    display: flex;
    align-items: center;
  }
  .navbar-custom-menu .btn-user .user-display-name { white-space: nowrap; }
}
/* ---------- The chip is the whole name or the user glyph, and nothing in between ----------
   A shortened name is the one thing the chip must not render, so it is not a shrinkable item: it
   inherits `flex: none` from the rail above, and which of its two forms renders is decided here,
   by width.  That has to be a breakpoint, because CSS has no "does this
   text fit" predicate and the two mechanisms that look like one cannot be one — a flex item that
   shrinks truncates by definition, and `container-type: inline-size` removes the container's own
   contents from its intrinsic size, so a chip sized BY its contents collapses to its floor and the
   query then answers "too narrow" at every width.

   Each boundary is therefore a measured crossover: the narrowest viewport that seats the field at
   its 140px floor, the bell with a FOUR-digit count (a count is invisible to a selector, so it is
   taken at its worst), the SSO shape, the logout, the 188.19 full-name chip and the 10px gaps
   between them — inside that regime's budget, which is `vw` less the sidebar or the logo, less the
   42px toggle, less the 10px end inset:

     regime                          no SSO   single    menu    split
     <=767            (logo 36)      562.45   613.79   621.79   641.79
     768-991 collapsed (sidebar 50)  576.45   627.79   635.79   655.79
     768-991 expanded  (sidebar 230) 756.45   807.79   815.79   835.79

   So the constrained band needs ONE stage — expanded, with an SSO tile, under 860 — because every
   other cell in it seats the name at 768 already; and the phone regime needs two, because its two
   crossovers are 80px apart and a selector can see whether the rail HAS an SSO tile even though it
   cannot see which shape.  Each boundary is its crossover rounded up to the next multiple of 20 that
   leaves at least one gap (10px) of slack, less one: 580 -> 579, 660 -> 659, and 840 would leave
   4.21 so 860 -> 859.  The glyph form is never the form that runs out of room — it seats the widest
   rail this header can emit from 492.94 up.

   >=992 has no stage: the name is whole there at every width, which is why the glyph is switched
   OFF outside these queries rather than on.  What follows from a breakpoint is one exposure: the
   crossovers are calibrated on 188.19, the widest display name the corpus holds, and a chip N px
   wider needs N px more room, so between a boundary and N px above it the rail runs out and its
   first item slides under the toggle.  The bar's height is not at risk (see `nowrap` above), and
   >=992 has carried the same exposure since it was pinned. */
@media (max-width: 579px) {
  .navbar-custom-menu .btn-user .user-display-name,
  .navbar-custom-menu .btn-user .user-icon { display: none; }
  .navbar-custom-menu .btn-user .user-avatar { display: block; }
}
@media (max-width: 659px) {
  .navbar-custom-menu > .navbar-nav:has(.sso-btn) .btn-user .user-display-name,
  .navbar-custom-menu > .navbar-nav:has(.sso-btn) .btn-user .user-icon { display: none; }
  .navbar-custom-menu > .navbar-nav:has(.sso-btn) .btn-user .user-avatar { display: block; }
}
@media (min-width: 768px) and (max-width: 859px) {
  body:not(.sidebar-collapse) .navbar-custom-menu > .navbar-nav:has(.sso-btn) .btn-user .user-display-name,
  body:not(.sidebar-collapse) .navbar-custom-menu > .navbar-nav:has(.sso-btn) .btn-user .user-icon { display: none; }
  body:not(.sidebar-collapse) .navbar-custom-menu > .navbar-nav:has(.sso-btn) .btn-user .user-avatar { display: block; }
}
/* ---------- 767 and down: ONE 50px bar, not two decks ----------
   AdminLTE splits the header below 768 — `.main-header .logo, .main-header .navbar { width: 100%;
   float: none }` gives the wordmark its own 50px deck and the controls a second one — and the header
   is one line at every width.  Undoing the split takes four declarations, because everything else
   about this bar is already width-independent:

     · the logo keeps its float and becomes ONE TILE wide, showing the mark alone.  The markup
       already carries both (`views/layouts/header.php` emits `.logo-mini` and `.logo-lg` around the
       same 25px-high SVG), so this is AdminLTE's own collapsed-sidebar treatment, reused — and one
       tile renders it BETTER than that 50px box: the mark measures 21.52 wide, so AdminLTE's
       `padding: 0 15px` leaves it a 20px content box and `overflow: hidden` clips 0.76px off each
       side, while `padding: 0` centres it in 36 with 7.24 to spare;
     · the navbar starts where the logo ends, so `margin-left` carries the logo here exactly as it
       carries the sidebar above 768.  `.navbar-custom-menu { width: calc(100% - 42px) }` above then
       re-derives the rail's budget with no arithmetic of its own, because `100%` is the navbar's
       content box and that is now `vw - 36`;
     · the two offsets measured from the 100px deck follow the bar down: the fixed layout's
       clearance on `.content-wrapper` (this app runs `<body class="fixed">`, so `.main-header` is
       `position: fixed` and the content is cleared by padding, not by flow) and the off-canvas
       drawer's own `padding-top`, or the drawer's first menu item would sit 50px below the bar and
       every page 50px below where it belongs.  `.content-header` needs nothing: it is
       `position: fixed` with `top: auto`, so it rides its static position and follows that padding.

   50px is the bar: `--control-height` is the tile inside it, not the bar, and the rail above
   already states 50 for the same reason. */
@media (max-width: 767px) {
  .main-header .logo {
    float: left;
    width: var(--size-toolbar-icon-tile);
    padding: 0;
  }
  .main-header .logo .logo-lg { display: none; }
  .main-header .logo .logo-mini { display: block; }
  .main-header .navbar {
    width: auto;
    margin-left: var(--size-toolbar-icon-tile);
  }
  .fixed .content-wrapper, .fixed .right-side { padding-top: 50px; }
  .main-sidebar, .left-side { padding-top: 50px; }
}
/* ---------- Phone widths: every item is a tile, and the field is a tile that opens ----------
   Below this boundary the rail cannot hold a usable field for every user, and the reason is
   measurable rather than aesthetic.  The widest rail `views/layouts/header.php` can emit adds an SSO
   tile — synthesised into the corpus rail because the corpus login has none: 41.34 single, 49.34
   menu, 69.34 split — and a four-digit unread count widens the bell from 55.09 to 78.17.  With both,
   the items that cannot shrink plus their gaps come to 264.94px — the chip is in its glyph form at
   every width down here, 39.34 — and the bar's own budget is `vw - 36 - 42 - 10` once the logo is in
   it, so the 140px field floor is held only from 492.94px up.
   The field cannot merely be narrower below that — it has to stop being a field.  520 is the round
   guard above the crossover, and no mainstream device width falls between 481 and 599, so the step
   is one nobody stands on.  It also makes the phone header identical for every user, SSO tile or not.

   Three things happen here, and each is worth a measured number at 360px, where the rail has 272px:

     · the field becomes the magnifier tile with the input laid transparently ON TOP of it.  The
       input is the tap target, not the button, because tapping the button would submit an empty
       required field and nothing would appear to happen; focusing the input is what `:focus-within`
       needs anyway.  Everything collapsed is stated as `:not(:focus-within)`, so the open state
       needs no restoring rules — it simply falls back to the block above.  Out of flow the panel
       loses the rail's flex centring, so `top` restates it: (50 - 34) / 2 = 8px, the rail's height
       less the control height, and the panel opens exactly where the tile was.  `left: 0 /
       right: 10px` span the rail — from the hamburger's edge to the same end inset — without
       covering the hamburger itself;
     · the SSO group becomes ONE tile, which is what the logo costs the widest rail: 69.34 -> 32 in
       the split shape and 49.34 -> 41.34 in the menu shape.  NO destination is lost either time.
       The split shape is a shortcut anchor plus a caret-only toggle whose `Dropdown` lists every
       button INCLUDING the shortcut's, so the shortcut is the half to drop; the menu shape is one
       anchor carrying both the icon and the caret, so there the caret is the half to drop and
       `:has(i.fa)` is what tells the two toggles apart — dropping the caret in the split shape
       would leave an empty tile.  The shortcut leaves the row by `position: absolute` rather than
       `display: none` because `.sso-btn` (2g above) pins `display: inline-block !important` and this
       round adds no `!important`; `visibility: hidden` is what then takes it out of the paint, out
       of the tab order and out of the a11y tree, and out of flow it contributes no width.  The
       floor keeps the caret-only toggle tappable: 32 wide as a `<b class="caret">` on its own, one
       tile with this;
     · the rhythm tightens to 8px, which is the last 8 of the 16 the logo took.  The widest rail
       this bar can render — one SSO tile, a four-digit unread count — then occupies 270.03 of the
       272 the rail has at 360px; at a 10px rhythm it would need 278.03 and the first item would
       slide under the hamburger.

   The field's two rules stay keyed to `:not(:focus-within)` and the SSO fold and the gap are
   unconditional inside the block, because an open panel does not change what either should be. */
@media (max-width: 520px) {
  .navbar-custom-menu > .navbar-nav { position: relative; gap: 8px; }
  .navbar-custom-menu > .navbar-nav > li:has(.sso-btn.dropdown-toggle) > .sso-btn:not(.dropdown-toggle) {
    position: absolute;
    visibility: hidden;
  }
  .navbar-custom-menu .sso-btn.dropdown-toggle:has(i.fa) > .caret { display: none; }
  .navbar-custom-menu .sso-btn.dropdown-toggle { min-width: var(--size-toolbar-icon-tile); }
  .navbar-custom-menu > .navbar-nav > li:has(form):not(:focus-within) { flex: none; min-width: 0; }
  .navbar-custom-menu > .navbar-nav > li:has(form):not(:focus-within) .input-group {
    position: relative;
    width: auto;
  }
  .navbar-custom-menu > .navbar-nav > li:has(form):not(:focus-within) .form-control {
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    opacity: 0;
    z-index: 1;
  }
  .navbar-custom-menu > .navbar-nav > li:has(form):not(:focus-within) .input-group-btn .btn + .btn {
    display: none;
  }
  .navbar-custom-menu > .navbar-nav > li:has(form):not(:focus-within) .input-group-btn .btn {
    border-radius: var(--radius-control);
  }
  .navbar-custom-menu > .navbar-nav > li:has(form):focus-within {
    position: absolute;
    top: 8px;
    left: 0;
    right: 10px;
    z-index: 1;
  }
}
/* ---------- Type ----------
   The legacy metrics, by client decision, overriding the design manual's Inter 14px.  Same selector
   list as the designer's type rule; later file wins.  The Inter @font-face blocks above stay (an
   unreferenced font is never downloaded); delete them together with web/css/font/inter-*.woff2 if
   Inter is permanently off. */
body, .form-control, .btn, [class*="select2-container--krajee"] .select2-selection,
.dropdown-menu, .sidebar-menu .treeview-menu > li > a, .input-group-addon {
  font-family: "Inter", Helvetica, sans-serif;
  font-size: 13px;  /* the legacy size, by client decision; the family stays Inter */
}
/* Full-strength body ink over bootstrap's/kartik's washed #555. The select2 half is one class
   deeper than kartik's (0,3,0): the AJAX dialog path re-registers the vendor sheet AFTER this
   file, where order cannot decide. */
.form-control,
[class*="select2-container--krajee"] .select2-selection.select2-selection--single .select2-selection__rendered {
  color: var(--text-body);
}
/* The headings need their own rule because the one above also carries the 13px, and a heading's SIZE
   is not the body's.  `adminlte.css:40` lists `body, h1 … .h6` together on `Helvetica, sans-serif`, so
   the line above only ever recaptured the `body` half of it and every record title, box title and modal
   title still computed Helvetica while the app around them was Inter.  Same element/class selectors as
   that rule, so this wins on load order, and family only — the sizes stay wherever they are set. */
h1, h2, h3, h4, h5, h6, .h1, .h2, .h3, .h4, .h5, .h6 {
  font-family: "Inter", Helvetica, sans-serif;
}
/* U5) Dropdown-toggle open state rendered vendored white-on-pale text (Export button).
   Keyed to `.btn-ghost` alone now.  Every toggle in the app carries it: the three widget emitters
   (ActionColumn's bulk "Akce" and filter funnel, BsDialogCreateListLinks) and the two
   that used to be hand-written (`contract/_buttons.php`'s external-applications menu,
   `precept/_grid.php`'s print menu).  kartik's own grid export toggle is the one vendor toggle that
   would write `btn-default`, and it cannot render: `config/container.php` sets `export => false`,
   and neither `{export}` nor `{toggleData}` is in the app's grid layout. */
.content-wrapper .open > .dropdown-toggle.btn-ghost {
  background: var(--surface-tint) !important;
  border-color: var(--ok-green);
  color: var(--ok-green) !important;
}
/* Production sidebar markup renders submenu parents WITHOUT the .treeview class the canonical
   selectors expect (the li carries only menu-open/active), so the open/hover parent state was
   silently dead.  Re-stated structurally via `:has(.treeview-menu)`. */
.skin-blue .main-sidebar .sidebar .sidebar-menu > li.menu-open > a,
.skin-blue .main-sidebar .sidebar .sidebar-menu > li.active:has(> .treeview-menu) > a {
  background: rgba(255, 255, 255, .55);
  color: var(--ok-green) !important;
}
.content { padding: var(--spacing-content-y) var(--spacing-content-x); }
.main-header .navbar .navbar-nav > li > .btn:hover,
.main-header .navbar .navbar-nav > li > .btn:focus,
.main-header .btn-user:hover {
  background: var(--brand-mint-bg) !important;
  border-color: var(--border-strong) !important;
  color: var(--ok-green-dark) !important;
}
/* Unread count: a bold green number, no chip. `.badge` brings bootstrap's grey pill plus
   AdminLTE's palette !important, so all three of fill / radius / padding have to go. */
.main-header .navbar .badge, .main-header .btn .badge {
  background: transparent !important;
  background-color: transparent !important;
  color: var(--ok-green) !important;
  font-weight: 700;
  padding: 0 0 0 4px;
  border-radius: 0;
  box-shadow: none;
  min-width: 0;
}
/* ---------- The navbar bell with nothing unread ----------
   Client ruling: at zero the bell is not a button at all, just a white glyph and a white "0" on the
   green bar — the logout treatment — and the white chip is the signal that something is waiting.

   State by class, not by duplicated markup: views/layouts/header.php always emits
   `btn-notification` and adds `btn-notification--unread` only when the count is non-zero, so `:not()`
   on the modifier is what makes one selector cover both states.  The white-navbar block is (0,4,1)
   with its `> li >` chain, so the same chain plus these two classes lands at (0,6,1) and wins
   outright; `!important` throughout because that block declares it. */
.main-header .navbar .navbar-nav > li > .btn.btn-notification:not(.btn-notification--unread),
.main-header .navbar .btn-notification:not(.btn-notification--unread) {
  background: transparent !important;
  border-color: transparent !important;
  color: var(--text-inverse) !important;
  box-shadow: none;
}
.main-header .navbar .navbar-nav > li > .btn.btn-notification:not(.btn-notification--unread):hover,
.main-header .navbar .navbar-nav > li > .btn.btn-notification:not(.btn-notification--unread):focus,
.main-header .navbar .btn-notification:not(.btn-notification--unread):hover {
  background: rgba(255, 255, 255, .15) !important;
  border-color: transparent !important;
  color: var(--text-inverse) !important;
}
/* The count rides along: the navbar block above paints every `.badge` brand green, which would be
   invisible on the green bar. (0,5,0) against its (0,3,0). */
.main-header .navbar .btn-notification:not(.btn-notification--unread) .badge {
  color: var(--text-inverse) !important;
}
.separator { margin-right: 25px; }
/* THE FIRST TOOLBAR BUTTON.  `p.pull-left` opens with `<span class="separator"></span>` on the
   contract / subject / incident update pages — an empty inline span whose entire effect is the
   `.separator { margin-right: 25px }` above.  As the FIRST thing in the row it separates nothing and simply
   indents the toolbar 25px past every other row on the page.  `:first-child` is the whole condition:
   the same span used BETWEEN two groups keeps its 25px.
   `display: none`, not `margin-right: 0`: in `body.toolbar-icons` the row is a flex container, where
   even a zero-width span is a flex item and the row's gap buys it 6px — the KOOP tile sat 6px off
   the row edge with the margin merely zeroed. */
.content p.pull-left > .separator:first-child { display: none; }
/* A floated toolbar must never intrude into the record card below it: when the float's bottom
   edge touches the card's top, the card's OWN floated columns start beside the toolbar instead of
   at the row edge — the shifted first column then pushes the second below the fold (measured on
   /finance/precept/update: col-md-6 at x=152 behind a 78px toolbar, right column wrapped). The
   toolbar floats sit in a classless wrapper they escape, so the clear goes on the card — DIRECT
   child only: a .box inside a floated column clearing would push that column's content below its
   neighbour. */
.precept-update > .box,
.precept-update > .row { clear: both; }

/* ============================================================================================
   2h. BASE / global polish
   ============================================================================================ */
@keyframes ok-pop { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: none; } }
/* Tooltips carry whole field values (grid cells truncate into them); never clip at bootstrap's
   200px cap. */
.tooltip-inner {
    max-width: none !important;
}
/* scripts.js detaches an overflowing dropdown-menu to <body> (`.detached`); the separator above
   the `.first-item` link is a rule, not a sibling .divider <li>, so it survives the detach. */
ul.dropdown-menu.detached > li:has(> a.first-item) {
    border-top: 1px solid #eee;
    padding-top: 5px;
    margin-top: 5px;
}
/* The record toolbar's "Akce" dropdown floats with the pull-left buttons it stands among
   (contract/_buttons.php). */
.dropdown-pull-left {
    float: left !important;
    margin-right: 3px;
}

/* ============================================================================================
   3b. COMPONENTS / chips
   ============================================================================================ */

/* ---------- Úkoly "Status" chips ----------
   The three states (from Comment::getCommentStatus) used to be a bare Font Awesome glyph in the raw
   system colours, inline-styled in the model; they are tinted chips in the 2026 palette now, reusing
   the shared chip metrics so the app has one chip shape.

   SCOPE — deliberately narrow.  The repaint targets `.comment-status` only, and the Bootstrap
   `.label-*` classes are left alone app-wide: their two real consumers carry unrelated meaning (the
   `label-info` "Kontakt" chip in the digital-sign envelope form, the `label-danger` "document
   deleted" chip built in web/js/modules.js), and repainting those would make "orange" mean two
   different things.  The comment grids' bootstrap row tints (tr.success / tr.info, from
   Comment::$color) are a separate, user-chosen colour and are NOT touched. */
.comment-status-cell {
  text-align: center;
}
/* success -> mint; the same "quiet positive" fill selected states use. */
.comment-status--done { background: var(--panel-success-bg); color: var(--ok-green); }
/* warning -> cream: still inside the due date, so it is a notice, not a problem. */
.comment-status--pending { background: var(--ok-cream); color: var(--panel-warning-text); }
/* danger -> light orange. The 2026 palette has no saturated red for content; overdue reads as
   the same alarm tint the "documents missing" pill uses below. */
.comment-status--overdue { background: var(--panel-danger-row-bg); color: var(--status-danger-muted); }
/* ---------- One entity chip for every list ----------
   The Entita column exists on both Avíza and Úkoly and used to render two different things: a mint
   chip on one, a bare full-height 34px LinkButton on the other — wide enough to read as a control
   rather than a label.  Both views now emit `search-chip entity-chip`, so the two cannot drift and
   any further list that grows an entity column inherits the look.

   The chip WRAPS the LinkButton rather than replacing it, so the link — and on Avíza the "mark as
   read on click" behaviour riding on it — is untouched; only the button's own chrome is stripped,
   leaving the chip's border as the single frame.  `.search-chip` supplies the pill, the border and
   the inline-flex; everything here is the list-cell fit.

   The chip is 28px — NOT the chip family's 26 — and FULL COLUMN WIDTH: the user's ruling is that an
   entity column reads the same everywhere, and the columns it shares the app with are the bare
   small-ghost entity links (`action-client` / `action-contract` / `btn-extended`, the rule below),
   which compute 28 and now span their cell.  The RADIUS deliberately stays the pill and the type
   stays 13px: the pill plus the mint tag glyph is what makes a chip read as a label rather than as
   a button.  The left inset stays 2px wider than the right (12 vs 10) because the chip leads with
   the tag glyph and a pill needs more room on the side its corner cuts into.  Truncation is
   untouched — the inner `.btn` keeps nowrap + hidden + ellipsis.

   THE SELECTOR NEEDS BOTH CLASSES: `.search-chip`'s own `padding` shorthand is declared later in this
   file, so a single-class rule here loses the tie and the chip inherits the base's 5/5 — which is why
   it used to measure 30px.  Every emission point writes `search-chip entity-chip` together
   (notification/_grid.php, comment-duedate/_grid.php), so the compound selector always matches.

   THE VERTICAL PADDING IS 3/5, NOT 4/4 — the same optical compensation the state pill carries, see
   `.kv-grid__value > .kv-pill`; the +2 bottom bias is the one measured at the old 26px size (ink
   centre 1.382px below the chip's on an even split, 0.382px with the bias).  3 + 18 + 5 plus the
   borders is the 28 the entity links compute. */
.search-chip.entity-chip {
  width: 100%;
  padding: 3px 10px 5px 12px;
  font-size: 13px;
  line-height: 18px;
  gap: 4px;
}
.entity-chip > .fa-tag {
  color: var(--ok-mint);
  flex: none;
}
.entity-chip > .btn {
  min-width: 0;
  max-width: 100%;
  height: auto;
  padding: 0;
  border: 0;
  background: none;
  box-shadow: none;
  color: var(--ok-green);
  font-size: 13px;
  line-height: 18px;
  /* inline-block, not the ladder's inline-flex: `text-overflow` is inert on a flex container, so
     an overflowing name was CLIPPED at both ends around the ladder's centred content instead of
     ellipsised at its tail. */
  display: inline-block;
  text-align: left;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* The strip above removes the button chrome at REST; the ladder's hover would still paint its
   mint slab INSIDE the pill (an inset rounded box around the padded link), so the states are
   stripped with it and the hover tint moves to the pill itself — the chip is the control. */
.entity-chip > .btn:hover,
.entity-chip > .btn:focus,
.entity-chip > .btn:active {
  background: none;
  color: var(--ok-green);
}
/* The base `.search-chip:hover` darkens the border to full green and lifts a shadow — the look of
   the Úvod history chips it was written for. In a grid cell that ring is too loud: the entity
   chip's hover is the mint fill alone, the border stays at rest. */
.search-chip.entity-chip:hover,
.search-chip.entity-chip:focus-within {
  background-color: var(--brand-mint-bg);
  border-color: var(--border-strong);
  box-shadow: none;
}
/* ---------- The bare entity links take the same cell fit ----------
   The other face of an entity column: Smlouvy, Předpisy and the dashboard panels render the client /
   contract / entity link as a small ghost LinkButton with one of these three classes, not as the
   chip above.  Same ruling: full column width, label leading LEFT.  `justify-content` is what
   actually moves the label — these are the ladder's inline-flex buttons, where `text-align` is
   inert on the anonymous text item; the `text-align` is for adminlte.css's legacy
   `.btn.btn-extended { width: 100%; text-align: left }`, which this rule supersedes so that
   `action-client` / `action-contract` (Předpisy) stop being the one entity column sized by
   content. */
.grid-view td .btn.btn-extended,
.grid-view td .btn.action-client,
.grid-view td .btn.action-contract {
  width: 100%;
  justify-content: flex-start;
  text-align: left;
}
/* ---------- The Úkoly status chip is a square ----------
   Built from chip metrics meant for a chip holding a WORD (`min-width: 26px; padding: 3px 8px;
   font-size: 12px; line-height: 16px`), this chip holds a single 12px glyph — and those metrics then
   produce a flat pill whose width follows the glyph's advance width, three different widths, none of
   them square.

   Two separate causes, hence the two declarations.  The width came from `min-width` + 2x8px of side
   padding, i.e. from the TEXT metrics; the height came from the glyph alone (12 + 2x3), because in an
   inline-flex box the container's own `line-height` never applies — only the flex item's does.
   Stating both axes at 26px and dropping the padding makes the box a square that cannot follow the
   glyph, and `justify-content: center` keeps every glyph centred in it.  26px and not the 28px of the
   grid's action tiles next door: the chip is a status MARKER, not a control, and staying one step
   below the tiles keeps that reading.  `flex: none` is the "regardless of column squeeze" guarantee —
   the Status column is the narrowest in the grid.

   Same specificity as the palette rule above (one class), later in the file, so it wins on order —
   written as a separate block on purpose, so that rule stays the single place the colours live. */
.comment-status {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 26px;
  border-radius: var(--radius-sm);
  font-size: 12px;
  line-height: 16px;
  width: 26px;
  height: 26px;
  padding: 0;
  flex: none;
}

/* ============================================================================================
   3c. COMPONENTS / list filter bar — the inline bar that replaced the separate "Vyhledávání" box.
   SHARED by every index page: the values are the designer's, the per-page modifiers live further
   down and in the area sections, and the markup carries `list-filter-bar` plus the page's own class.
   This block stays AHEAD of the modifiers so they can override its basis.
   ============================================================================================ */

/* A one-line chip row fits the 50px fixed header, so no extra `.content` offset is needed.  Past
   roughly a dozen chips the row wraps and the fixed header grows — groupChipsModule.syncHeaderOffset()
   in web/js/modules.js measures that case and raises the offset at runtime. */
.list-filter-bar {
  display: flex;
  gap: 12px;
  align-items: flex-end;
  flex-wrap: wrap;
  padding-bottom: 14px;
  border-bottom: 1px solid var(--border-default);
  margin-bottom: 6px;
}
.list-filter-bar__field .input-group { width: 100%; }
/* ActiveForm always emits the error container, empty or not. Left in place it would sit under
   each input and defeat `align-items: flex-end` (the fields would hang above the buttons). */
.list-filter-bar__field .help-block:empty { display: none; }
.list-filter-bar__actions {
  display: flex;
  gap: 8px;
  margin-left: auto;
}
/* The square clear-filter tile, on the control line.  Its `display: inline-flex; align-items: center;
   justify-content: center` folded into the canonical ladder in 3h: all 26 in the corpus carry
   `.btn-ghost`, which now declares the trio for them. */
.list-filter-bar__actions .btn-clear-filter {
  width: var(--control-height);
  height: var(--control-height);
  padding: 0;
}
.list-filter-bar__actions .btn-tile {
  width: var(--control-height);
}
/* ---------- Filter bar field proportions ---------- */
.list-filter-bar__field--sm { flex: 1 1 170px; min-width: 170px; }
.list-filter-bar__field--lg { flex: 1.2 1 240px; min-width: 240px; }
/* ---------- The filter bar's action group stays on the input line ----------
   Two causes, both worth knowing.  (1) The bar is `align-items: flex-end`, which aligns MARGIN boxes:
   `.list-filter-bar__field` carries `margin: 0`, but `.form-group { margin-bottom: 10px }` is the
   same (0,1,0) and sits later in this file, so it won the tie and re-armed a 10px skirt under every
   field — the fields' border boxes ended 10px above the buttons.  (2) `Vyhledat` is a plain `.btn`
   (36px, deliberately off `--control-height`) while the inputs beside it are 34px; inside a filter
   bar the button IS a control on the input line, so here it takes the token.

   Restated at (0,2,0) rather than moved, so a later rhythm change to `.form-group` cannot take the
   bar with it again. */
.list-filter-bar .list-filter-bar__field { margin-bottom: 0; }
/* Only the height and the zeroed vertical padding are this bar's own.  The `display: inline-flex;
   align-items: center; justify-content: center; gap: 6px` this rule used to carry — the first place
   in the app that had to restate the glyph-to-label gap a flex container trims — folded into the
   canonical ladder in 3h, which now says the same four things for every button in the app.  All 64
   buttons in this container carry a canonical class; a future one that does not would lose the trio,
   and the fix is the markup. */
.list-filter-bar__actions .btn {
  height: var(--control-height);
  padding-top: 0;
  padding-bottom: 0;
}
/* ---------- The client list's two-row filter bar ----------
   Everything except the second line is the SHARED bar: the hairline, the 12px gap,
   `align-items: flex-end`, the zeroed field margins, the 34px action buttons, the square clear tile.
   `--rows` turns the form back into a block container and moves the flex layout down one level, onto
   `__row`, because eight fields plus three actions cannot read as one row at any usable width.

   WHY `display: block` on the form and not `flex-wrap` with full-width rows: the form also carries the
   `show_inactive` hidden input, and `.form-control` on it (an old blank-and-submit handler needed the
   class) sets `display: block` — as a flex ITEM that would become a 34px empty box on the line.  As a
   block child of a block form it stays invisible.  `#advanced-search` is a block child too, so the
   extended fields open BELOW the two rows and inside the same card. */
.list-filter-bar--rows { display: block; }
.list-filter-bar__row {
  display: flex;
  gap: 12px;
  align-items: flex-end;
  flex-wrap: wrap;
}
.list-filter-bar__row + .list-filter-bar__row { margin-top: 10px; }
/* ---------- The list filter bar's header row ----------
   The card gets its title back and the action group moves up beside it, instead of sitting at the end
   of field row 2 where it read as a fourth field.

   The row renders INSIDE the form ("Vyhledat" is the submit and the toggle drives `#advanced-search`,
   both of which have to be within `ActiveForm`), so it cannot be the card's real `.box-header` — that
   element is a SIBLING of `.box-body`.  What it can do is borrow the header's two ingredients, and it
   does: `box-title` for the text and the shared `.list-filter-bar__actions`, whose `margin-left: auto`
   is the whole right-alignment mechanism.  Metrics follow `.box > .box-header`: `align-items: center`
   and a hairline below in the same `--border-default` the `.with-border` variant uses.  No side
   padding — the row sits in `.box-body`, whose inset is the card's, exactly as a header's is its own.
   The bar's OWN bottom hairline is untouched and still separates the whole filter from the grid. */
.list-filter-bar__head {
  display: flex;
  align-items: center;
  gap: 12px;
  padding-bottom: 10px;
  margin-bottom: 12px;
  border-bottom: 1px solid var(--border-default);
}
/* `.box-title` is an <h3>; AdminLTE zeroes its margin only as a descendant of a box header. */
.list-filter-bar__head .box-title { margin: 0; }
/* The search submit is the FILLED action of a filter bar — all three actions used to be the same white
   ghost, so nothing said which one runs the search.  Green and not orange: orange is the CTA family
   for actions that create or commit a RECORD, and running a search commits nothing.
   Scoped to `.list-filter-bar__actions` so the ghost buttons beside this one are out of reach.
   `.btn-brand` states the same green at (0,1,0); this pair is the (0,3,0) restatement, so a context
   rule painting `.list-filter-bar__actions .btn` has something to lose to. */
.list-filter-bar__actions .btn.btn-brand {
  background: var(--ok-green) !important;
  border-color: var(--ok-green);
  color: var(--text-inverse);
}
/* The bootstrap `.row`s of `#advanced-search` open BELOW the field rows and inside the same card, so
   they need the same 10px the rows give each other.  (Klienti's `.client-filter__advanced` was the
   page-private twin of this name and is now folded into it — that page's advanced block was the only
   one the flex rule at the end of this section never reached, so it was still a float row.) */
.list-filter-bar__advanced { margin-top: 10px; }
/* Field widths.  `min-width` is what decides wrapping for a flex item and `max-width` is what stops a
   select from eating the line, so each is a basis/min/max triple rather than a width.
   --date a DatePicker (six fit one 1500px line, which is what Pojistné události needs) · --cap a
   labelled select or short text field · --sel a Select2 · --grow the wide search term. */
.list-filter-bar__field--date { flex: 0 1 160px; min-width: 148px; max-width: 175px; }
.list-filter-bar__field--cap { flex: 1 1 180px; min-width: 170px; max-width: 210px; }
.list-filter-bar__field--sel { flex: 1 1 220px; min-width: 200px; max-width: 280px; }
.list-filter-bar__field--grow { flex: 3 1 320px; min-width: 300px; }
/* `__grid` — the escape hatch for a filter that CANNOT be a flex row: the report-filter family pairs
   its fields with the 212px account TreeViewInput.  Those blocks stay bootstrap and need the same
   10px `__advanced` gives, for the same reason.  Its own selector rather than folded into `__advanced`
   so the two intents stay legible; the value should move together. */
.list-filter-bar__grid { margin-top: 10px; }
/* A widget that emits SEVERAL fields inside one wrapper still owes the row its fields, not a box:
   RefModelSelector wraps its class half and its name half together, and as a single flex item the
   pair could only ever be one column of the bar.  `display: contents` drops the wrapper's box and
   lets the two halves take their own places on the line — the element stays in the DOM, which is
   what matters, because the widget's own JS finds its partner through `.parents('.ref-model-selector')`. */
.list-filter-bar__passthrough { display: contents; }
/* A filter row FILLS the card.  Every width modifier states a `max-width` and `--date` carries
   `flex-grow: 0` — right for what a FIELD may become on its own, wrong for what a ROW must do.  The
   user's ruling is that a row ends flush with the card edge, so inside a `--rows` bar the cap comes
   off and the leftover goes to the existing flex-grow weights (`--grow` keeps its three shares, which
   is what keeps the search term the widest field on its line).
   Scoped to `--rows` because a single-line bar puts its ACTION GROUP on the same flex line, so its
   fields must NOT eat the space the buttons need.  (0,3,0) against the modifiers' (0,1,0). */
.list-filter-bar--rows .list-filter-bar__row > .list-filter-bar__field { max-width: none; }
.list-filter-bar--rows .list-filter-bar__row > .list-filter-bar__field--date { flex-grow: 1; }
/* ---------- Every advanced row steps by the same 10px ----------
   `#advanced-search > .row` is a bootstrap FLOAT row, so its height is its tallest column and the short
   columns leave dead space underneath: the step from a short field to the row below it is not owned by a
   margin at all.  A panel with one wrapping label therefore showed three different gaps in one row.

   The base bar never had the problem because `.list-filter-bar__row` is a flex row with a shared control
   line, so the advanced rows get the SAME behaviour: bottom-aligned flex items put every control on one
   line and leave exactly one 10px `.form-group` margin between any field and the row below it.  A flex
   item establishes an independent formatting context, so that margin stops collapsing out of its column
   and becomes the gap itself — which is why the step is 10 from EVERY field, not only from the tallest.
   `flex-end` and not `stretch`: a label may legitimately need two lines, and the line that must agree is
   the CONTROL line, which is the bottom one.

   MEDIA QUERY: below 992px bootstrap's `.col-md-*` have no width and stack as blocks; as flex items they
   would shrink to their content and sit side by side, so this is scoped to the breakpoint where the
   columns actually have widths. */
@media (min-width: 992px) {
  .list-filter-bar__advanced > .row {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
  }
}
/* ---------- …and by the same 12px sideways ----------
   The vertical step above matched the base bar; the HORIZONTAL one did not.  `.list-filter-bar__row`
   is a flex row with `gap: 12px`, while an advanced row is bootstrap's grid, whose gutter is 15px of
   column padding on each side — 30px between neighbours, two and a half times the rhythm the two rows
   directly above it keep.

   Halving the gutter to 6px per side is the whole fix: the column WIDTHS stay percentages (border-box,
   so padding is inside them) and the row's negative margin still cancels the outer half, which is what
   keeps an advanced field flush with the card edge and with the base rows' first and last field.

   NOT `gap: 12px` with zeroed padding, which is the obvious move and is wrong: a wrapping flex container
   breaks lines on the items' HYPOTHETICAL sizes, before any shrinking, so two `col-md-6`s (50% + 50%)
   plus a 12px gap overflow and the second one wraps to its own line.  The gutter mechanism has no such
   problem because the padding is already inside the 50%.

   Outside the media query on purpose: below 992px the columns are full-width blocks and the pair still
   agrees — a smaller inset, still cancelled by the row's own margin. */
.list-filter-bar__advanced > .row { margin-left: -6px; margin-right: -6px; }
.list-filter-bar__advanced > .row > [class*="col-"] { padding-left: 6px; padding-right: 6px; }

/* ============================================================================================
   3d. COMPONENTS / box anatomy and form sections
   ============================================================================================ */
/* ---------- Boxes ---------- */
.box {
  border-radius: var(--radius-box);
  border: 1px solid var(--border-default);
  border-top-width: 1px;  /* AdminLTE has 3px status border-top */
  box-shadow: var(--shadow-box);
  transition: box-shadow 200ms cubic-bezier(.2,.6,.3,1), transform 200ms cubic-bezier(.2,.6,.3,1);
  margin-bottom: var(--spacing-box-gap);
}
.box.collapsed-box > .box-header { border-radius: calc(var(--radius-box) - 1px); }
.box > .box-footer { border-radius: 0 0 calc(var(--radius-box) - 1px) calc(var(--radius-box) - 1px); }
.form-actions { border-radius: var(--radius-box); }
/* The base help "?" icon, wherever it stands; the navbar (2g), box (below) and box-default-header
   variants re-ink it at higher specificity.  The font-size is important against font-awesome's
   `font` shorthand on the same element. */
.help-item-icon {
    margin-left: 5px;
    font-size: 16px !important;
    color: var(--ok-green);
}
.box .help-item-icon { color: var(--ok-mint); }
/* White box headers with green titles */
.box.box-solid.box-primary > .box-header,
.main-header + * .box-header, .box > .box-header {
  background: var(--surface-card) !important;
  color: var(--ok-green) !important;
}
.box.box-solid.box-primary { border-color: var(--border-default); }
.box.box-solid.box-default > .box-header { background: var(--surface-card) !important; color: var(--ok-green); }
.box.box-solid.box-default > .box-header .help-item-icon { color: var(--ok-green); }
.box.box-solid.box-primary > .box-header a { color: var(--ok-orange); }
/* 600, not the design manual's 400 — user ruling (2026-09-21) after users reported the uniform
   weight makes sections hard to find over a full workday. */
.box-title { font-weight: 600; font-size: 16px; }
 /* pastel variant — was orange-light */
/* Five classes deep on purpose: AdminLTE's `.box.box-solid.box-primary > .box-header .btn { color:
   #fff }` is (0,5,0) too and only loses the tie because unified loads last, so anything shallower
   here leaves a white-on-white button in the 154 `box-primary box-solid` headers.  The canonical
   `.btn-ghost` is (0,1,0) and cannot reach that far, which is why the rule is keyed to it rather
   than deleted in its favour.  Same reason for the `:hover`: this rule's `background` also applies
   during hover, so `.btn-ghost:hover` (0,2,0) can never move the fill — only a hover at >= (0,5,0)
   can, and it carries the mint the rest of the app hovers to. */
.box.box-solid > .box-header .btn.btn-ghost {
  border-radius: var(--radius-control);
  background: var(--surface-card);
  border: 1px solid var(--border-strong);
  color: var(--ok-green);
}
.box.box-solid > .box-header .btn.btn-ghost:hover { background: var(--brand-mint-bg); }
/* The one real CTA is orange.
   THE PAIR STAYS, and after B12 it is the ONLY rule of ours keyed to `btn-success` — skin.css's bare
   brand-green `.btn-success` is gone, so this is what a raw `btn-success` gets and there is no
   fallback under it but bootstrap's own green.  Its population is markup whose paint class is its
   DATA rather than its role (`comment/_form.php`, `contract/risc/_form.php` and
   `console/alert/_form.php` build the class name from a colour value; `_discussions.php`'s
   open-status chip) plus the `admin` module's vendor views.  None of them may be renamed to `.btn-cta` without a
   designed chip palette, which is why the pair outlives the emission that used to feed it. */
.content-wrapper .btn-success, .form-actions .btn-success {
  background: var(--ok-orange) !important;
  border-color: var(--ok-orange);
  color: var(--text-inverse);
}
.content-wrapper .btn-success:hover, .form-actions .btn-success:hover,
.content-wrapper .btn-success:focus, .content-wrapper .btn-success:active {
  background: var(--ok-orange-dark) !important;
  border-color: var(--ok-orange-dark);
}
.box, .box.box-solid.box-primary { border: none !important; box-shadow: 0 1px 3px rgba(0,60,50,.07), 0 6px 20px rgba(0,60,50,.06); }
.box > .box-header.with-border { border-bottom: 1px solid #EEF5F1; }
.form-actions, .box-body .form-actions {
  box-shadow: 0 1px 3px rgba(0,60,50,.07), 0 6px 20px rgba(0,60,50,.06);
  background: var(--ok-cream) !important;
  border: none;
}
/* Adjacent pull-left buttons in the actions bar keep a seam between them. */
.form-actions .pull-left { margin-right: 5px; }
.box:hover { box-shadow: 0 2px 6px rgba(0,60,50,.08), 0 10px 28px rgba(0,60,50,.09); }
/* The collapse/expand tool was white-on-white once box headers went white.  AdminLTE.min.css sets
   `.box.box-solid.box-primary > .box-header .btn { color: #fff }` at (0,5,0), which beats a plain
   `.btn-box-tool` (0,1,0) regardless of load order.  Matched with an equal-specificity selector (five
   classes, via a `.content-wrapper` prefix); our file loads after AdminLTE.min.css, so the tie
   resolves in our favour. */
.content-wrapper .box .box-header .btn.btn-box-tool { color: var(--ok-green); }
.content-wrapper .box .box-header .btn.btn-box-tool:hover { color: var(--ok-green-dark); }
/* A bare header-less wrapper `.box` (legacy page scaffolding around the real boxes on the
   client/contract/dashboard update pages) must not render as a card — boxes float on the page
   background.  `:not(.form-card)` is the escape hatch: a CREATE page's box IS the card, and two of
   them happen to hold a sub-section box, which this rule cannot tell apart from scaffolding, so those
   views say so in markup.  See the `.form-card` note further down. */
.content .box:not(.form-card):not(:has(> .box-header)):has(.box) {
  background: transparent !important;
  box-shadow: none !important;
  border: none;
  border-radius: 0;
}
.content .box:not(.form-card):not(:has(> .box-header)):has(.box):hover { box-shadow: none !important; }
.content .box:not(.form-card):not(:has(> .box-header)):has(.box) > .box-body { padding: 0; }
.box > .box-header .box-header-buttons a { margin: 0; }
/* A dropdown standing among the box-header buttons joins their inline row and takes their compact
   3px/6px button metric (adminlte's `.box-header-buttons a` only reaches anchors). */
.box-header-buttons .dropdown {
    display: inline-block;
    background: transparent;
}
.box-header-buttons .dropdown .btn {
    padding: 3px 6px;
}
.box > .box-header .box-header-buttons + .box-tools,
.box > .box-header .box-tools:only-of-type { margin-left: auto; }
.box > .box-header .box-header-buttons ~ .box-tools { margin-left: 8px; }
/* ---------- Form sections ----------
   Section grouping inside a form box body (Subjekt / Zařazení / Adresa + the merged Korespondenční
   adresa sub-block).  The designer's mock carried these as inline styles; they are classes here. */
.form-section-label {
  font-size: 12px;
  font-weight: 600;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--text-muted);
  border-bottom: 1px solid var(--border-default);
  padding-bottom: 6px;
  margin: 6px 0 14px;
}
/* Quieter sub-section label: no rule, no uppercase. Declared after the block so it also wins
   when both classes are applied to the same element. */
.form-section-label--sub {
  font-size: 12px;
  font-weight: 400;
  letter-spacing: normal;
  text-transform: none;
  color: var(--text-muted);
  border-bottom: 0;
  padding-bottom: 0;
  margin: 2px 0 10px;
}
/* Collapsible section: the heading itself is the toggle, with a small +/− chip pinned to the
   right edge — same semantics as a box collapse (+ = hidden, − = open). <details>/<summary>
   carries the open state, so no JS is involved and the state survives a re-render. Used by the
   "Korespondenční adresa" sub-section of the client form (mocks 7a/7b). */
.form-section-collapse > summary.form-section-label--toggle {
  display: flex;
  align-items: center;
  gap: 8px;
  cursor: pointer;
  list-style: none;
}
.form-section-collapse > summary::-webkit-details-marker {
  display: none;
}
.form-section-collapse > summary:hover {
  color: var(--ok-green);
}
/* Collapse mark — Figma "Collapse caret — examples", pick C. The summary is a flex row, so the
   pseudo-element is its first item and the row's `gap` spaces it. */
.form-section-collapse > summary.form-section-label--toggle::before {
  content: "\f054";
  font: normal normal normal 10px/1 FontAwesome;
  color: var(--text-muted);
  transform: rotate(90deg);
  transition: transform .15s ease;
}
.form-section-collapse:not([open]) > summary.form-section-label--toggle::before {
  transform: none;
}
/* The collapse chip belongs on the header's RIGHT edge, and neither flex order nor a margin can put
   it there.  DOM order inside `.box-header` is title -> help icon(s) -> `.box-tools` ->
   `.box-header-buttons`, so once the header became a flex row the chip landed BEFORE the buttons and
   the `margin-left: auto` rules could never match that order.  Flex `order` is not enough either: in a
   narrow right-hand column the header WRAPS and the chip then drops to a second line and aligns left.
   So the chip comes out of the flex flow entirely and is pinned to the header's top-right corner —
   deterministic in both the wide and the wrapping case — with `padding-right` reserving its column so
   the header buttons can never collide with it.  `.box-header` is already position:relative
   (AdminLTE). */
/* Reserved only while `.box-tools` holds something that still PAINTS — the collapse button is
   display:none, and a column held for an invisible button strands the header buttons short of the
   box edge. The `>` keeps the probe off the button's own <i>. */
.box > .box-header:has(> .box-tools > :not([data-widget="collapse"])) { padding-right: 48px; }
.box > .box-header > .box-tools { position: absolute; right: 10px; top: 2px; margin: 0; }
/* The collapse button stays in the DOM unpainted — it is the element AdminLTE's BoxWidget listens
   on, and scripts.js forwards header clicks to it. One glyph rotates between the states so the
   flip animates; swapped content cannot. */
.box:has(> .box-header [data-widget="collapse"]) > .box-header { cursor: pointer; }
.box:has(> .box-header [data-widget="collapse"]) > .box-header .box-title::before {
  content: "\f054";
  font: normal normal normal 11px/1 FontAwesome;
  display: inline-block;
  margin-right: 10px;
  color: var(--text-muted);
  transform: rotate(90deg);
  transition: transform .15s ease;
}
/* Restates the base rule's `:has()` for specificity alone — the guard lifts the base selector to
   (0,5,0), so a plain `.box.collapsed-box` restyle at (0,4,0) could never win the transform. */
.box.collapsed-box:has(> .box-header [data-widget="collapse"]) > .box-header .box-title::before { transform: none; }
.box > .box-header .btn-box-tool[data-widget="collapse"] { display: none; }
/* Vertical rhythm above a sub-section heading.  The heading carries only `.form-section-label`'s own
   `margin: 6px 0 14px`, so a grid ending directly above it left a 6px gap.  Taken as margin rather
   than as the mock's <hr>: the <hr> was deliberately dropped when these sub-sections lost their nested
   `.box` chrome (`.form-section-label` already draws its own bottom rule, so an <hr> above would box
   the heading between two lines), and one rule cannot drift out of sync across the five _documents.php
   copies and the comments partial the way six <hr>s could.  26px = the mock's 20px hr margin + the
   heading's own 6px.  `:not(:first-child)` keeps the gap off a heading that OPENS a box body, where
   the body padding already provides it; the second rule keeps it off a heading that follows form
   fields, whose `.row` siblings already end in a form-group's bottom margin.  Equal specificity
   (0,4,2) — the reset wins on order. */
.box-body > details.form-section-collapse:not(:first-child) > summary.form-section-label {
  margin-top: 26px;
}
.box-body > .row + details.form-section-collapse > summary.form-section-label {
  margin-top: 6px;
}
.box-body { padding: var(--spacing-box-body-top) var(--spacing-box-body-x) var(--spacing-box-body-bottom); }
.box > .box-header .box-title { padding: 0; }
.box > .box-header .box-header-buttons {
  float: none;
  display: inline-flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--spacing-inline-gap);
  margin: 0 0 0 auto;
}
/* ---------- The sub-section create button ----------
   The button inside a collapsible sub-section heading rendered as a huge all-caps pill, because
   `text-transform` and `letter-spacing` are INHERITED properties and it picked up
   `.form-section-label`'s `uppercase` + `.06em` straight from its <summary> parent.  Font size and
   weight are restated as well so it can never inherit the label's 12px/600 either — `.btn` is (0,1,0)
   and would lose to any future rule on the heading.

   The padding matches the box-header buttons directly above it, which are 28px not by design of ours
   but by arithmetic: adminlte.css's `.box-header-buttons a { padding: 3px 6px }` (0,1,1) beats `.btn`'s
   `padding: 7px 14px`, and 3 + 20 line-height + 3 + 2 border = 28.  Stated as the same padding rather
   than a hard-coded height so a future change to the header buttons carries.  The icon-mode rule
   squares this button separately and outranks this one, so that mode is unaffected. */
.form-section-label--toggle .btn { font-size: 13px; font-weight: 400; letter-spacing: normal; text-transform: none; padding: 3px 6px; }
/* ---------- One box-header height ----------
   Box headers: title left, buttons on the same row right, wrapping gracefully in a narrow column
   instead of dropping to an awkward centred second line.  The header gets a real 8px padding box;
   until now it had none (adminlte.css's `.box-header { padding: 0 }`) and the title's own 10px stood in
   for it, which is why both move together — leaving the title's padding in place would double the
   inset.

   `min-height` and not padding, because the ask is that a header WITHOUT a button match one WITH it,
   so the padded ones must come out exactly as they are: a header carrying a 28px header button
   measures 8+28+8+1 = 45px while a header with nothing but its title measured 8+18+8+1 = 35px, a
   visible 10px step between two collapsed boxes standing on each other.  45px on a border-box header
   leaves every header that already reaches it untouched and grows the header-only ones into the same
   line.  The collapse tool is NOT what sets the height — it is absolutely positioned, which is also
   why the header reserves 48px of right padding for it. */
.box > .box-header {
  border-radius: calc(var(--radius-box) - 1px) calc(var(--radius-box) - 1px) 0 0;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px 10px;
  padding: 8px;
  min-height: 45px;
}
/* The in-card action row: a right-aligned button row that opens a card's results block, sitting
   between the filter bar's hairline and the grid.  The gap is the button-row gap every other toolbar
   uses; the bottom margin is the 6px the filter bar already leaves above the grid, so the row lands on
   the card's existing rhythm rather than inventing one.  Nothing sizes the buttons — they keep the
   free-standing 36px deliberately, because they are in the scrolling content, not in the fixed
   header. */
.card-toolbar {
  display: flex;
  justify-content: flex-end;
  gap: var(--spacing-inline-gap);
  margin-bottom: 6px;
}
/* ---------- The floating .form-actions bar is promoted into the title row ----------
   `promoteFormActionsToTitleRow()` (web/js/scripts.js) adds this class ONLY after it has successfully
   built the title-row tiles, so the page can never end up with neither — the mechanism fails safe by
   leaving the bar exactly as it is.

   HIDDEN, NOT REMOVED, and this is the data-loss trap: the `input.action-save` inside the bar is what
   the title tile clicks, it is where every page-specific submit handler is bound, and its
   `name=action-save` still has to reach the POST.  `display: none` keeps it in the DOM and clickable
   via `.trigger('click')`.

   Scoped to the promoted class rather than written as a blanket rule, because `.form-actions` is also
   the DIALOG footer's source markup (showDialog() copies its HTML into `.bsdialog-footer`) and the ~13
   pages whose bar carries a labelled action of its own keep theirs. */
.form-actions.form-actions--promoted { display: none; }
/* ---------- The bar is hidden BEFORE the promotion decides, not after ----------
   `.form-actions` is `position: fixed`, so between first paint and DOM-ready it painted in the
   bottom-right of every create and update page and then vanished — a bar that twinkled on every load.
   Hidden by default instead, and handed BACK by `promoteFormActionsToTitleRow()` on every bail-out:
   `--kept` is that reveal, and it is why that function returns a boolean.

   `section.content` is load-bearing twice over. It keeps the rule off the DIALOG path: showDialog()
   transplants this bar's innerHTML into `.bsdialog-footer`, where the buttons are no longer inside a
   `.form-actions` at all, and the in-modal original is hidden by its own rule further down. And it
   keeps the rule off anything outside the content area entirely. */
section.content .form-actions { display: none; }
section.content .form-actions.form-actions--kept { display: block; }

/* ============================================================================================
   3e. COMPONENTS / grid and key-value list
   ============================================================================================ */
.pagination > li > a, .pagination > li > span { color: var(--ok-green); border-color: var(--border-default); }
.pagination > li:first-child > a, .pagination > li:first-child > span { border-radius: var(--radius-control) 0 0 var(--radius-control); }
.pagination > li:last-child > a, .pagination > li:last-child > span { border-radius: 0 var(--radius-control) var(--radius-control) 0; }
.pagination > .active > a, .pagination > .active > a:hover, .pagination > .active > a:focus,
.pagination > .active > span {
  background-color: var(--ok-green);  /* was stock #337ab7 blue */
  border-color: var(--ok-green);
  color: var(--text-inverse);
}
.kv-grid-table .skip-export.kv-align-center a { border-radius: var(--radius-control); }
.kv-grid-table thead tr th { background: transparent; font-weight: 600; color: var(--text-label); }
.kv-grid-table thead tr.filters td { background: transparent; }
.summary { color: var(--text-muted); font-size: 13px; }
/* Grid action buttons: square icon buttons (matches production) */
.kv-grid-table .btn-xs, .table .btn-xs {
  width: var(--size-grid-action-btn); height: var(--size-grid-action-btn); padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
  line-height: 1;
}
/* Grid checkboxes: same language as form checkboxes, compact */
.kv-grid-table input[type="checkbox"], .table input[type="checkbox"] {
  appearance: none; -webkit-appearance: none; position: static; margin: 0;
  width: var(--size-checkbox-grid); height: var(--size-checkbox-grid); cursor: pointer;
  background: var(--surface-input); border: 1px solid var(--border-default); border-radius: var(--radius-sm);
  display: inline-grid; place-content: center; vertical-align: middle;
  transition: border-color 120ms, background-color 120ms;
}
.kv-grid-table input[type="checkbox"]:hover, .table input[type="checkbox"]:hover { border-color: var(--border-strong); }
.kv-grid-table input[type="checkbox"]:checked, .table input[type="checkbox"]:checked { background: var(--ok-green); border-color: var(--ok-green); }
.kv-grid-table input[type="checkbox"]:checked::before, .table input[type="checkbox"]:checked::before {
  content: ""; width: 11px; height: 11px; background: var(--text-inverse);
  clip-path: polygon(14% 44%, 0 65%, 40% 100%, 100% 16%, 84% 0%, 38% 68%);
}
/* Grid softening: rounded tinted header band, no hard side borders */
.kv-grid-table { border-collapse: separate; border-spacing: 0; }
.kv-grid-table > thead > tr.filters > td { background: transparent; border: none; padding-top: 10px; padding-bottom: 4px; }
.kv-grid-table > tbody > tr > td { border-top: none; border-bottom: 1px solid var(--border-default); }
.kv-grid-table > tbody > tr:last-child > td { border-bottom: none; }
/* Datepicker calendar addon in filter rows: rounded + tinted like other addons */
.input-group.date .input-group-addon, .kv-grid-table .filters .input-group-addon,
.krajee-datepicker + .input-group-addon {
  border-radius: var(--radius-control) 0 0 var(--radius-control);
  background: var(--surface-tint); border-color: var(--border-default); color: var(--ok-green);
}
/* Row selection: calm sky tint, NOT danger red.  Needs
   `CheckboxColumn 'rowSelectedClass' => 'kv-row-select'` (krajee's default is 'danger'). */
/* ---------- The grid's pjax loader ----------
   kartik's .kv-loader is a 100px box whose gif renders at native size, anchored top/left 50% with
   NO re-centering translate — it hangs down-right of centre and clips through the bottom of a
   short (empty) grid. Re-centred by negative margins and shrunk. The `!important` on
   background-size is the vendor-contest exception: the vendor rule pins `background` as an
   important SHORTHAND, which resets background-size to auto with importance — only a later
   important longhand can take it back.
   `:root`-prefixed, not bare: a dialog opened from a page with no kartik grid registers
   kv-grid.css AFTER this sheet, where a (0,1,0) tie loses on order — one level deeper wins the
   contest in both load orders (the `.bsdialog .hint-block` precedent). */
:root .kv-loader {
  width: 48px;
  height: 48px;
  margin: -24px 0 0 -24px;
  border-radius: 50%;
  background-size: 32px 32px !important;
}
/* The loader centres on the LOADING CONTAINER, and an audit-trail box that has never loaded is a
   3px-tall pjax container (measured) — the gif hung almost entirely outside it. The class is
   kartik's and lives only for the duration of the request, so the floor never pads a loaded grid:
   76 = the 48px loader plus one 14px breath each side. */
.kv-grid-loading { min-height: 76px; }
.kv-grid-table > tbody > tr.kv-row-select > td { background: var(--pastel-sky-light) !important; color: var(--pastel-sky-text); }
.kv-grid-table > tbody > tr.kv-row-select:hover > td { background: var(--ok-sky) !important; }
.input-group .input-group-addon:last-child { border-radius: 0 var(--radius-control) var(--radius-control) 0; }
.input-group .input-group-addon:first-child { border-radius: var(--radius-control) 0 0 var(--radius-control); }
/* Grid action buttons: uniform square icon tiles (they were a mix of btn/btn-sm sizes).  This
   higher-specificity rule keeps the non-btn-xs strays at the same size. */
.grid-view .action-column .btn {
  width: var(--size-grid-action-btn); height: var(--size-grid-action-btn);
  padding: 0 !important;
  display: inline-flex; align-items: center; justify-content: center;
  line-height: 1;
}
/* Squaring caught the dropdown toggles of a grid's action column — only square real icon buttons.
   `:not(.btn-actions-toggle)` narrows that exception away from the bulk "Akce" HEADER toggle, which is
   now a 28px tile like every other control; this rule at (0,4,0) was the only thing un-squaring it.
   WHAT IS LEFT MATCHES NOTHING TODAY: the only other toggle the selector can reach is the one
   ActionColumn::renderFilterCellContent() draws beside the clear-filter funnel, and that branch is
   unreachable — `ActionColumn::$filter` is typed `bool`, so it can never hold the non-empty array the
   branch needs, and all 41 call sites pass `''`. Kept, not deleted, so reviving the branch would not
   silently square a bare caret; it is a deletion candidate the moment that branch goes. */
.grid-view .action-column .btn.dropdown-toggle:not(.btn-actions-toggle) {
  width: auto; height: auto;
  padding: 4px 10px !important;
  display: inline-block;
  line-height: 18px;
}
/* THE GREY BAND BEHIND EVERY FILTER ROW.  The 2026 layer softens the CELLS (`> thead > tr > th` and
   `> tr.filters > td` are both `background: transparent`), but kartik paints the band on the THEAD
   ELEMENT: kv-grid.css:23 `.kv-table-header { background: linear-gradient(to bottom, #fff 0%, #eee
   100%) }`.  With transparent cells that gradient shows straight through — white at the header row,
   #eee at the filter row.  Nothing in our layer had ever targeted the thead, so the selector list
   simply had a hole.  Same for the footer band (kv-grid.css:18). */
.kv-grid-table > thead.kv-table-header,
.kv-grid-table > tfoot.kv-table-footer {
  background: transparent;
}
/* UAT ruling: a header label never wraps mid-label — "Pojistné po slevě (včetně fee)" broken
   over three lines reads wrong, and the sort glyph must sit at its label's end. Stacked
   multi-label headers keep their lines: they are joined with explicit <br>. The cost: a dense
   grid takes horizontal scroll (kartik's responsive container provides it). */
.grid-view .kv-grid-table > thead > tr > th { white-space: nowrap; }
/* The bare rule as well as the two thead-scoped ones: a grid can hold a `th` neither covers — a
   detail table rendered inside an expand row, a nested table in a cell — and the ruling is
   app-wide, not thead-wide. */
.grid-view th { white-space: nowrap; }
/* A filter cell's text input must not collapse when the column is narrow. */
.grid-view .filters input[type=text] { min-width: 50px; }
/* The comment grid pins its first column (the comment text) to a quarter of the table. */
.box .comment-grid thead th[data-col-seq="1"] { width: 25%; }
/* Filter-reset eraser (ActionColumn TYPE_LINK): `btn-link` inherits Bootstrap's link blue, which is
   off-palette; quiet ghost instead — green icon, mint hover, like every other ghost button. */
.kv-grid-table .btn-clear-filter,
.kv-grid-table .btn-clear-filter:focus {
  color: var(--ok-green);
  text-decoration: none;
}
.kv-grid-table .btn-clear-filter:hover {
  color: var(--ok-green);
  background: var(--brand-mint-bg);
}
/* ---------- Summary + pager on one line ---------- */
.list-gridfoot {
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
}
.list-gridfoot .pagination { margin: 6px 0; }
/* ---------- Record overview boxes are key-value rows ----------
   The client and contract Přehled boxes are read-only summaries, so their disabled `div.form-control`
   pairs give way to a two-column list: label left in muted ink, value right in bold tabular figures, a
   hairline under every pair but the last.

   The values are still wrapped in a `div.form-control` inside a `field-<model>-<attribute>` row, which
   is what web/js/modules.js and the two _documents.php live-updaters write into and what
   ContractUpdateCept / ClientUpdateCept assert on — so the input chrome is stripped here rather than in
   the markup, and it is not a `<span>` because the tests match `div.form-control`.

   NOT changed: business-transaction's own _dashboard.php, which has the same shape.  Obchodní případ
   keeps the old look until it is asked for. */
.kv-grid {
  display: grid;
  grid-template-columns: 1fr auto;
  row-gap: 0;
}
/* The wrapper exists only to carry the `field-<model>-<attribute>` hook and the negative modifier;
   `display: contents` promotes its label and value to grid items so it costs no box and no margin
   (the same device the select2 rendered element uses). */
.kv-grid__row {
  display: contents;
}
.kv-grid__label,
.kv-grid__value {
  padding: var(--spacing-kv-row-pad-y) 0;
  border-bottom: 1px solid var(--border-default);
}
.kv-grid__label {
  color: var(--text-muted);
  padding-right: 12px;   /* a long label must not touch the value column */
}
/* `align-items: center` is what puts a one-line value in the MIDDLE of a row whose label wrapped.
   Both cells are grid items and both stretch (that is what keeps the hairline continuous), so the
   value cell is as tall as the two-line label while its content is one line; left at the flex
   default the line sat at the top and read as belonging to the label's first line only.  Centring
   the flex items centres the content inside the stretched cell without touching the cell itself.

   It replaces `baseline`, which the number/unit pair no longer needs: value and unit are the same
   13px/18.2px line, so the two alignments place them identically.  They part company only if a value
   ever wraps WHILE carrying a unit — then the unit centres against the whole value instead of riding
   its first line.  Every unit-bearing row in the app is a formatted amount, which does not wrap.
   A value that wraps on its own is unaffected either way: it is then the tallest thing in the row,
   so there is no slack left to centre it in. */
.kv-grid__value {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 4px;
  font-weight: 600;
  color: var(--text-body);
  font-variant-numeric: tabular-nums;
  text-align: right;
}
/* The last pair paints no rule, but KEEPS the 1px the border occupies — dropping the border outright
   made the last row a pixel shorter than every other row in the panel, which is what made the
   contract's pill row (its last) look like the pill was costing it height.  Keyed off `:last-child`
   rather than a class so the conditional rows cannot leave a dangling hairline whichever of them
   happens to be last. */
.kv-grid > .kv-grid__row:last-child > .kv-grid__label,
.kv-grid > .kv-grid__row:last-child > .kv-grid__value {
  border-bottom-color: transparent;
}
/* Strip the input chrome from the value carrier the live-updaters and the tests need. */
.kv-grid__value > .form-control {
  display: inline;
  width: auto;
  height: auto;
  min-height: 0;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: none;
  box-shadow: none;
  font: inherit;
  color: inherit;
  font-variant-numeric: inherit;
}
/* The unit sits outside .form-control (a `.text()` update would otherwise wipe it) and reads as
   part of the number, so it takes the value's weight and only loses a little contrast. */
.kv-grid__unit {
  color: var(--text-muted);
  font-weight: 600;
}
/* A negative Zaplacené pojistné is the one number in this box worth spotting at a glance.
   Audit ruling 2026-09-22: the orange-dark ink measured 3.57:1, the danger token passes at
   4.77:1. Central rule, so every kv list in the app that marks a negative amount follows. */
.kv-grid__row--negative > .kv-grid__value,
.kv-grid__row--negative > .kv-grid__value > .kv-grid__unit {
  color: var(--status-danger);
}
/* The two state tints of the pill.  Both sit AFTER the chrome-stripping rule above on purpose: that
   rule paints `background: none` at the same (0,2,0) specificity, so a tint declared earlier in the
   file loses the tie and the pill renders with no fill at all — which is what "nekompletní" did.
   The alarm tint is light orange and not red: red is reserved for errors, and a missing document is
   a state to notice, not a fault. */
.kv-grid__value > .kv-pill--ok { background: var(--panel-success-bg); color: var(--ok-green); }
.kv-grid__value > .kv-pill--missing { background: var(--panel-danger-row-bg); color: var(--status-danger-muted); }
/* Overview key-value rows keep their 9px (explicit designer note).  They are unaffected by the
   form-group rhythm — `.kv-grid__row` is `display: contents`, so that margin never applied — and are
   re-stated here only so a future rhythm change cannot silently take them with it. */
.kv-grid__label, .kv-grid__value { padding: var(--spacing-kv-row-pad-y) 0; }
/* ---------- Grid headers ----------
   Three states on one cell: the label, the funnel, and the sort caret that REPLACES the funnel on the
   sorted column.  The funnel means "this column can be sorted".

   It is added server-side at ONE shared place: app\components\web\DataColumn appends
   `<i class="fa fa-filter kv-th-filter">` to the header content, and config/container.php substitutes
   that class for kartik\grid\DataColumn (the `dataColumnClass` every kartik GridView instantiates), so
   all ~150 kv grids get it without a single view being edited.  Everything below is CSS. */
.kv-grid-table > thead > tr > th {
  background: transparent;
  border-bottom: 2px solid var(--border-default);
}
/* Audit remedy R1 (2026-09-22): the header band stops being the same 13/400 ink as the data —
   12/600 muted gives every grid a scan anchor. `:not(.filters)` keeps the filter row (inputs
   live there) out of the header type. */
.kv-grid-table > thead > tr:not(.filters) > th {
  font-size: 12px;
  font-weight: 600;
  color: var(--text-muted);
}
.kv-grid-table > thead > tr > th > a,
.kv-grid-table > thead > tr > th > a:hover,
.kv-grid-table > thead > tr > th > a:focus {
  color: var(--text-muted);
  text-decoration: none;
}
.kv-grid-table > thead > tr > th > a:hover { color: var(--ok-green); }
.kv-grid-table > thead > tr > th > .kv-th-filter {
  margin-left: 5px;
  font-size: 11px;
  color: var(--ok-mint);
}
/* The caret REPLACES the funnel on the sorted column. */
.kv-grid-table > thead > tr > th:has(> a.asc) > .kv-th-filter,
.kv-grid-table > thead > tr > th:has(> a.desc) > .kv-th-filter { display: none; }
/* The base sort caret, on EVERY sort link (yii\grid writes `asc`/`desc` on the header link and
   nothing else) — the kv and plain-grid rules below only re-dress its glyph and colour, so the
   Glyphicons pair is the fallback a sort link outside both grid shapes still renders. */
a.asc:after, a.desc:after {
    position: relative;
    top: 1px;
    display: inline-block;
    font-family: 'Glyphicons Halflings';
    font-style: normal;
    font-weight: normal;
    line-height: 1;
    padding-left: 5px;
}
a.asc:after { content: "\e151"; }
a.desc:after { content: "\e152"; }
/* kartik puts its own glyph in a `.kv-sort-icon` span INSIDE the link and the base rule above adds a
   second one via `a.asc:after` — two sort glyphs on every sorted column today. Keep the
   pseudo-element (it is the one that sits right after the label) and drop the span. */
.kv-grid-table > thead > tr > th > a > .kv-sort-icon { display: none; }
.kv-grid-table > thead > tr > th > a.asc:after,
.kv-grid-table > thead > tr > th > a.desc:after {
  font-family: FontAwesome;
  font-size: 12px;
  color: var(--ok-green);
  padding-left: 5px;
  top: 0;
}
.kv-grid-table > thead > tr > th > a.asc:after { content: "\f0d8"; }
.kv-grid-table > thead > tr > th > a.desc:after { content: "\f0d7"; }
/* ---------- Grid action tile glyphs ----------
   font-awesome.min.css declares `.fa { font-size: inherit }`, so every glyph takes its size from its
   button — and `.btn` is 13px since the type was reverted to the legacy metrics.  In a 28px tile
   FontAwesome's own 14px is the size the tile was drawn around, so the glyph is given it back
   explicitly.  The tile geometry is untouched.

   DIRECT `.fa` child only, for the same reason the icon-mode rules are written that way:
   `.btn-icon-glyph` sizes itself and its two parts, and a descendant selector here would outrank it. */
.grid-view .action-column .btn > .fa,
.kv-grid-table .btn-xs > .fa,
.table .btn-xs > .fa {
  font-size: 14px;
}
/* ---------- The sorted column's header ----------
   With every header link the same weight and colour, the 12px caret was the single pixel of difference
   between "this column is sorted" and "this column is merely sortable" — easy to miss on a wide grid.
   The label now carries the state with its caret: same brand green, one weight step up.  600 and not
   700, because the grid header is a 400-weight row by design and 700 would read as a different type
   style rather than as a state of the same one.  Unsorted headers are untouched, so the contrast
   between the two is what does the work.
   Specificity: the base rules are (0,1,4) and (0,2,4); adding `.asc`/`.desc` makes these (0,2,4) and
   (0,3,4) — one class above each, so the hover state keeps the sorted colour too. */
.kv-grid-table > thead > tr > th > a.asc,
.kv-grid-table > thead > tr > th > a.desc,
.kv-grid-table > thead > tr > th > a.asc:hover,
.kv-grid-table > thead > tr > th > a.desc:hover,
.kv-grid-table > thead > tr > th > a.asc:focus,
.kv-grid-table > thead > tr > th > a.desc:focus {
  font-weight: 600;
  color: var(--ok-green);
}
.grid-view > table:not(.kv-grid-table) > thead > tr > th > a,
.grid-view > table:not(.kv-grid-table) > thead > tr > th > a:focus {
  color: var(--text-label);
  text-decoration: none;
}
.grid-view > table:not(.kv-grid-table) > thead > tr > th > a:hover { color: var(--brand-green); }
/* The base rule above hangs the sort caret off `a.asc:after` / `a.desc:after`; it inherits the
   link's colour, which is now the label grey. The sorted column's caret is the one green mark,
   exactly as the kv grids do. */
.grid-view > table:not(.kv-grid-table) > thead > tr > th > a.asc:after,
.grid-view > table:not(.kv-grid-table) > thead > tr > th > a.desc:after { color: var(--brand-green); }
/* Why `.form-card` exists (the selector that reads it is the header-less-box rule further up).  That
   rule paints a header-less `.box` that CONTAINS a `.box` transparent, because on an UPDATE page such
   a box is legacy scaffolding wrapped around the real cards.  A CREATE page's box is the opposite: it
   IS the card, and the box inside it is one of the form's own sub-sections.  Two create pages trip
   that (client/create's "Výchozí kontakt" panel and subject/create's `.box.data-detail` register
   panel — `display:none`, but `:has()` matches it anyway, which is why that page rendered with no card
   at all).  No CSS can tell the two shapes apart, so the create views say so in markup. */

/* Plain (non-kartik) grids.  The seven `yii\grid\GridView` grids in the app have no `.kv-grid-table`
   class, and EVERY grid rule in this file is scoped to it — so they kept bootstrap's default header
   link colour, which is the CTA orange.  Mirroring the two header rules plus the header-wrapping fix
   is the smallest thing that covers all seven instead of leaving the same bug behind seven times.
   NOT mirrored: the funnel glyph (DataColumn only substitutes for kartik's DataColumn) and the thead
   band (a plain table has no `.kv-table-header` to repaint). */
.grid-view > table:not(.kv-grid-table) > thead > tr > th {
  /* the same non-wrapping, 12/600 muted header band the kv rules set */
  white-space: nowrap;
  font-size: 12px;
  font-weight: 600;
  color: var(--text-muted);
}
/* ---------- An inactive row is DISABLED, not an alarm ----------
   The entity grids mark a record that is no longer live (`is_finished`, or `deleted_at` for an account)
   with bootstrap's `danger` row class, which paints the alarm tint — so a whole page of ordinary rows read
   as a page of errors.  RED IS FOR ERRORS: inactive is the disabled surface with the ordinary body ink,
   which is how every other "present but not live" control in the app is drawn.  The grey is two steps
   darker than the disabled token, which is 3 points off white and could not be seen in a list; why it
   cannot collide with the hovered-row tint is derived in the zebra note at the end of section 3c.

   SCOPE — kartik puts the grid's id on every row it renders
   (GridViewTrait::renderTableRow -> `Html::addCssClass($options, $this->options['id'])`), so
   `tr.subject-grid` / `tr.contract-grid` / … is an exact, already-present hook and needs no markup.
   `tr.client-grid.danger` does not occur today (that grid sets no rowOptions), and is listed anyway so the
   pair the ruling names has ONE answer and adding the marker later is a view change with no CSS to chase.
   The two remaining `tr.danger` users in the app are deliberately NOT here and keep the alarm — see the
   note in the notifications section.

   Specificity: the base is `.table > tbody > tr.danger > td` (0,2,2) with `!important` on the fill, and its
   hover (0,3,2).  These are (0,4,2) / (0,4,3) and repeat the `!important`, so both win on count. */
.kv-grid-table > tbody > tr.subject-grid.danger > td,
.kv-grid-table > tbody > tr.client-grid.danger > td,
.kv-grid-table > tbody > tr.contract-grid.danger > td,
.kv-grid-table > tbody > tr.incident-grid.danger > td,
.kv-grid-table > tbody > tr.account-grid.danger > td {
  background-color: var(--surface-inactive-row) !important;
  color: var(--text-body);
}
.table-hover.kv-grid-table > tbody > tr.subject-grid.danger:hover > td,
.table-hover.kv-grid-table > tbody > tr.client-grid.danger:hover > td,
.table-hover.kv-grid-table > tbody > tr.contract-grid.danger:hover > td,
.table-hover.kv-grid-table > tbody > tr.incident-grid.danger:hover > td,
.table-hover.kv-grid-table > tbody > tr.account-grid.danger:hover > td {
  background-color: var(--surface-inactive-row-hover) !important;
}
/* ---------- A grid's favourite star, wherever the grid is drawn ----------
   KEYED TO `.grid-view`, NOT TO AN ANCESTOR, and that is the point.  The look used to be carried by
   `.content-wrapper .btn.btn-star` / `.btn-star-off`, so a grid drawn OUTSIDE `.content-wrapper` lost it —
   and one is drawn outside it every time a dialog opens (`showDialog()` moves content into a `.modal` that
   bootstrap appends to <body>).  Probed there, the star lost its gold.  Keying the pair to `.grid-view`
   makes the answer true by construction for every grid, and nothing else supplies that gold: outside the
   record title row, `btn-star` has no paint source that does not name an ancestor.

   ONE favourite button in the design system, the title-row trio look — "gold means starred, green means
   you may star this":
     ☆ not favourited   ghost tile, OUTLINE star (fa-star-o), BRAND-GREEN ink   `btn-star-off`
     ★ favourited       ghost tile, FILLED star  (fa-star),   GOLD ink          `btn-star`
   The glyph swap is FontAwesome's, done in markup by FavoriteButton; only the ink and the tile are CSS.

   TWO NON-BUTTONS ARE OUT OF SCOPE: the home page's `.fav-row__star` (a decorative glyph, no tile, no
   link, and `.fav-list` is not a `.grid-view`) and the title row's own trio.
   (0,3,0) ties the source rules and wins on order; the `!important` is only there because theirs carries
   one on the fill. */
.grid-view .btn.btn-star {
  background: var(--surface-card) !important;
  border-color: var(--border-strong);
  color: var(--brand-gold);
}
.grid-view .btn.btn-star:hover, .grid-view .btn.btn-star:focus {
  background: var(--surface-tint) !important;
  border-color: var(--brand-gold);
  color: var(--brand-gold-dark);
}
.grid-view .btn.btn-star-off {
  background: var(--surface-card) !important;
  border-color: var(--border-strong);
  color: var(--brand-green);
}
.grid-view .btn.btn-star-off:hover, .grid-view .btn.btn-star-off:focus {
  background: var(--brand-mint-bg) !important;
  border-color: var(--border-strong);
  color: var(--brand-green-dark);
}
/* ---------- The state pill ----------
   `.kv-pill` is the state chip in the Přehled key-value panel of client and contract update ("Stavy
   požadovaných dokumentů") — mint when every requested document is in, light orange while some are
   missing.  It sits on a ~34px key-value row, and 5px of vertical padding instead of 3 takes it
   22px -> 26px, the height of the Úkoly status chip.  Font-size, line-height, radius, fill and ink are
   untouched.

   `.cbl-state`, the Uzávěrka provizí grid's state chip, is the SAME chip and joins the selector rather
   than restating these six metrics next to its own tints in 4e — the two read side by side in the app
   and were 22 vs 26.  Only the fill and the ink differ between them, and those belong to each chip's
   own state modifiers.

   The chip also carries `.form-control` so the document dialogs' `.text()` still lands on it, hence the
   same-specificity selector to win back the padding and fill the chrome-stripping rule above removes.

   THE VERTICAL PADDING IS 4/6 ON PURPOSE — it is optical, not geometric.  A symmetric 5/5 centres the
   16px line box in the 26px box exactly, but Inter puts its baseline at 80% of its font box
   (ascent 12, descent 3 at 12px), so the visible INK of both status names — "v pořádku" and
   "nekompletní", each with a descender and a caron/acute above cap height — spans 8.32px to 19.95px
   from the top and its centre lands 1.13px BELOW the pill's.  One pixel of the bottom padding moved to
   the top brings that to 0.13px and leaves the height at 4+16+6 = 26.  `display: inline-flex` with
   `align-items: center` does NOT fix this (measured: still 1.13px) — it centres the line box, which was
   never the thing off centre, and the pill is a flex item anyway so `inline-flex` blockifies to `flex`.
   Same selector as the palette rule so the two cannot drift; later in the file, so it wins the tie. */
.kv-grid__value > .kv-pill,
.cbl-state {
  display: inline-block;
  border-radius: var(--radius-sm);
  font-size: 12px;
  font-weight: 600;
  line-height: 16px;
  padding: 4px 8px 6px;
}
/* ---------- The pill must not deepen the key-value row ----------
   The pill is 26px against an 18.2px label line, and both cells stretch to the taller of the two, so
   the pill alone would make its row 8px deeper than every other row in the panel.  Centring the cells
   (`align-items` on `.kv-grid`) is not available: the row hairline is a border on each cell and only
   spans the row while both stretch.

   Pulling 4px off each end leaves the pill's PAINTED box untouched and hands the row height back to
   the label's line: 26 - 8 = 18px of contributed height against that 18.2px line, so the row measures
   exactly what its neighbours do and the value cell's own centring places the pill.  It touches
   nothing but the pill, so the rest of the panel is unaffected. */
.kv-grid__value > .kv-pill {
  margin-top: -4px;
  margin-bottom: -4px;
}

/* ============================================================================================
   3f. COMPONENTS / panels, flashes, error summaries
   ============================================================================================ */
.alert { border-radius: var(--radius-box); }
/* ---------- Flashes, static alerts and validation summaries ----------
   Every flash in the app is rendered by dmstr\widgets\Alert, which emits
   `<div class="alert alert-<type>"><i class="icon fa fa-…"></i>text<button class="close">×</button></div>`;
   a handful of views write the same `.alert` block by hand.  Both shapes are covered here, so no view
   changes were needed.

   `!important` is not decoration: AdminLTE.css:4376+ paints these classes
   `background-color: <saturated> !important; color: #fff !important`, so a tinted panel can only be
   reached by matching it.

   ActiveForm error summaries render as `.callout.callout-danger.error-summary` (all ~90 call sites pass
   that class), NOT as `.alert` — they are in the same AdminLTE `!important` list, so they take the
   danger anatomy here too.  The BARE `.error-summary` (a summary from Html::errorSummary(), no
   callout classes) joins every list by user ruling (site retirement R1, 2026-09-21): one summary
   anatomy everywhere.  The `.error-summary.callout-danger` selectors CANNOT collapse onto the bare
   class: AdminLTE's callout `!important` pair is (0,2,0) and only an equal-specificity match beats
   it. */
.alert-success, .alert-warning, .alert-danger, .alert-error,
.error-summary.callout-danger, .error-summary {
  position: relative;
  padding: 11px 38px 11px 14px;
  border: 1px solid;
  border-left-width: 4px;
  border-radius: var(--radius-control);
  box-shadow: none;
}
.alert-success {
  background-color: var(--panel-success-bg) !important;
  border-color: var(--panel-success-border);
  border-left-color: var(--ok-green);
  color: var(--ok-green) !important;
}
.alert-warning {
  background-color: var(--panel-warning-bg) !important;
  border-color: var(--panel-warning-border);
  border-left-color: var(--ok-gold);
  color: var(--panel-warning-text) !important;
}
.alert-danger, .alert-error, .error-summary.callout-danger, .error-summary {
  background-color: var(--panel-danger-bg) !important;
  border-color: var(--panel-danger-border);
  border-left-color: var(--status-danger);
  color: var(--panel-danger-text) !important;
}
/* JUDGEMENT CALL, flag for the designer: the batch named success / warning / danger only, but
   `.alert-info` is used in three live views and left alone it stays a saturated AdminLTE cyan slab,
   jarring beside three tinted panels.  Given the same anatomy in the palette's Sky Blue.  Delete this
   one rule block if the designer wants info left as-is. */
.alert-info {
  position: relative;
  padding: 11px 38px 11px 14px;
  border: 1px solid var(--panel-info-border);
  border-left: 4px solid var(--ok-sky);
  border-radius: var(--radius-control);
  box-shadow: none;
  background-color: var(--panel-info-bg) !important;
  color: var(--panel-info-text) !important;
}
.alert-info > i.fa-info:before { content: "\f05a"; }
        /* dmstr emits fa-info; use the disc */
.alert-info:not(:has(> i)):before { font-family: FontAwesome; margin-right: 8px; content: "\f05a"; }
.alert-info a { color: inherit; text-decoration: underline; }
/* A link inside a tinted panel can no longer be white-on-colour (AdminLTE.css:2417). */
.alert-success a, .alert-warning a, .alert-danger a, .alert-error a,
.error-summary.callout-danger a, .error-summary a { color: inherit; text-decoration: underline; }
.error-summary.callout-danger > p, .error-summary > p { font-weight: 600; margin-bottom: 4px; }
.error-summary.callout-danger ul, .error-summary ul { margin-bottom: 0; padding-left: 18px; }
/* The summary is a <p> + <ul> stack, so its glyph has to ride on the heading rather than on the
   block (where it would sit on a line of its own above the text). */
.error-summary.callout-danger:before, .error-summary:before { content: none; }
.error-summary.callout-danger > p:first-child:before,
.error-summary > p:first-child:before {
  font-family: FontAwesome;
  content: "\f06a";
  margin-right: 8px;
}
/* The glyph. dmstr already emits one per type and two of the three are already the designer's
   (fa-check, fa-warning == fa-exclamation-triangle \f071); only danger/error differ, so the
   glyph is swapped rather than the markup (fa-ban \f05e -> fa-exclamation-circle \f06a). */
.alert-danger > i.fa-ban:before, .alert-error > i.fa-ban:before { content: "\f06a"; }
.alert > i:first-child { margin-right: 8px; }
/* WARNING: do NOT add `.error-summary.callout-danger:before` to the two rules below.  It used
   to be in both of them, twenty lines AFTER the `content: none` the paragraph-glyph block above sets on
   that very selector — same specificity (0,2,1), later in the file, so the later pair won and every
   error summary in the app drew TWO exclamation discs: one as an anonymous block line above the heading
   and one inline on the heading itself.

   The `.alert-*` members stay exactly as they are: an `.alert` is a single line of text with no heading
   to hang a glyph on, so the block-level `:before` IS its anatomy.  Only the summary — a `<p>` + `<ul>`
   stack — wants the glyph on the heading. */
.alert-success:not(:has(> i)):before,
.alert-warning:not(:has(> i)):before,
.alert-danger:not(:has(> i)):before,
.alert-error:not(:has(> i)):before {
  font-family: FontAwesome;
  margin-right: 8px;
}
.alert-success:not(:has(> i)):before { content: "\f00c"; }
.alert-warning:not(:has(> i)):before { content: "\f071"; }
.alert-danger:not(:has(> i)):before,
.alert-error:not(:has(> i)):before { content: "\f06a"; }
/* Close ✕ top right at 60%. Absolute rather than BS3's floated `right: -21px` hack, which needs
   the alert to be `.alert-dismissable` — the widget never adds that class, so the ✕ used to sit
   at the end of the last text line. */
.alert > .close {
  position: absolute;
  top: 8px;
  right: 10px;
  margin: 0;
  color: inherit;
  opacity: .6;
  text-shadow: none;
}
.alert > .close:hover, .alert > .close:focus { opacity: 1; color: inherit; }

/* ---------- The floated flash (toast) ----------
   The toast (`okis.toast()` in web/js/scripts.js) is deliberately NOT a new component.  Its markup is
   dmstr\widgets\Alert's, so every declaration above it — the tint, the 4px left accent, the glyph, the
   radius, the absolute ✕ — is the one the page's own flashes use, and a change to the flash family is a
   change to the toast.  What is added here is only what makes it float: the stack, the shadow that
   lifts it off the page, and the two 8px slides.

   ONE deliberate departure, and it is the designer's: the toast's ink is SEMIBOLD.  A flash sits in the
   page and is read at leisure; a toast is momentary and has to be read at a glance.

   `top: 62px` is the 50px fixed header plus a 12px gap; `pointer-events` is off on the stack and back
   on inside a toast, so an empty stack (created once, then kept) can never swallow a click on the page
   underneath; `z-index` clears the modal (1050) so an acknowledgment fired while a dialog is open is not
   hidden behind it. */
.okis-toasts {
  position: fixed;
  top: 62px;
  right: 24px;
  z-index: 1060;
  display: flex;
  flex-direction: column;
  gap: 10px;
  width: 360px;
  max-width: calc(100vw - 48px);
  pointer-events: none;
}
.okis-toast {
  margin: 0;
  font-weight: 600;
  pointer-events: auto;
  box-shadow: var(--shadow-dropdown);
  animation: okis-toast-in 160ms cubic-bezier(.2, .6, .3, 1);
}
.okis-toast--leaving { animation: okis-toast-out 200ms ease-in forwards; }
@keyframes okis-toast-in {
  from { opacity: 0; transform: translateY(-8px); }
  to { opacity: 1; transform: none; }
}
@keyframes okis-toast-out {
  from { opacity: 1; transform: none; }
  to { opacity: 0; transform: translateY(-8px); }
}

/* ============================================================================================
   3g. COMPONENTS / dialogs
   ============================================================================================ */
.modal [class*="select2-container--krajee"] { z-index: 1100; }
.modal-content { border-radius: var(--radius-modal); box-shadow: var(--shadow-dropdown); }
/* ---------- Confirmation dialogs ----------
   MECHANISM, which is the part that is easy to get wrong.  There is no BsDialog and no `data-confirm`
   override of our own in play: the dialog users actually see comes from KrajeeDialog
   (kartik\dialog\Dialog -> bootstrap3-dialog).  Every kartik GridView renders `Dialog::widget()`, which
   publishes a `krajeeDialog` instance in <head> and then, on document-ready, replaces `yii.confirm` with
   its own via `krajeeYiiConfirm()` — so it wins over the bootbox override web/js/scripts.js installs,
   and it is what both yii.js's `data-confirm` and the ~30 `data-confirm-message` handlers in modules.js
   end up calling.  Its stock look is a `type-warning` dialog with an orange header bar titled
   "Confirmation" in English (the shipped cs/sk `kvdialog` catalogs are empty).  scripts.js hooks
   `KrajeeDialog.prototype.confirm` and passes BootstrapDialog OPTIONS; the plugin itself is untouched.

   bootbox is still covered too — it remains `yii.confirm` on a page that renders no kartik widget, and
   modules.js calls `bootbox.alert/dialog/confirm` directly in a few places.  Its markup is different
   (no header, `.bootbox-body`), so the two anatomies key off `.bootstrap-dialog` and `.bootbox` and
   share only the card, the overlay and the buttons. */
.bootstrap-dialog .modal-content, .bootbox .modal-content {
  background: var(--surface-card);
  border: 0;
  border-radius: var(--radius-box);
  box-shadow: 0 12px 40px rgba(0, 42, 37, .28);
}
.bootstrap-dialog .modal-dialog, .bootbox .modal-dialog { width: 460px; }
/* Every dialog opens BELOW the title row, not over the navbar — bootstrap's 30px put it on top
   of the fixed chrome. One offset for all modal kinds (bsdialogs, plain bootstrap modals,
   bootbox alerts), so the family opens on one line. */
.modal > .modal-dialog { margin-top: var(--offset-modal-top); }
/* The offset costs viewport: a tall dialog body must scroll, or its footer buttons end up below
   the fold. 150px = header + footer + a bottom gap; a dialog with an inline height (the Subject
   viewer) keeps its own, tighter cap. */
.modal .modal-body {
  max-height: calc(100vh - var(--offset-modal-top) - 150px);
  overflow-y: auto;
}
/* Opt-in wide dialog (BsDialogForm SIZE_EXTRA_LARGE).
   No !important: it only ever had to beat bootstrap's .modal-dialog 600px, which file order
   already does — and with it, no dialog could opt out (the generate-precepts dialog must). */
@media (min-width: 768px) {
    .modal-xl {
        width: 90%;
        max-width: 1200px;
    }
}
/* The narrow dialog forms (contract-risc-option-value/_form.php, subject-risc/_form.php):
   zeroed row gutters, compact addons, labels that must not wrap in the tight columns.  The
   `.input-group.date .input-group-addon` part reaches every date addon OUTSIDE .content-wrapper
   (inside it, the 40px icon-addon rule in 2b out-ranks it). */
.container-narrow div.row {
    margin: 0;
}
.container-narrow .input-group-addon, .input-group.date .input-group-addon, .input-group-btn select {
    padding-left: 4px;
    padding-right: 4px;
}
.container-narrow div.row > div {
    padding-left: 0px;
    padding-right: 2px;
}
.container-narrow div.row div.form-group label {
    white-space: nowrap;
}
/* Both plugins use the shared bootstrap backdrop, which no selector can scope to them, so the
   2026 overlay applies to every modal — which is what the design asks for anyway. */
.modal-backdrop, .modal-backdrop.in { background-color: var(--overlay-backdrop); opacity: .45; }
/* --- KrajeeDialog / bootstrap3-dialog --- */
/* No coloured header bar: the header keeps only the title, and becomes the top row of the card
   (40px icon disc left, title right). `.btn-default` lands on the header element itself —
   bootstrap3-dialog names it after the dialog type, and `scripts.js` pins that type to
   `type-default` — so this part is a VENDOR surface, not a button, and stays whatever else happens
   to the legacy paint classes.  Its (0,3,0) is also what keeps the bare `.btn-default:hover` pair
   from tinting a hovered dialog header mint. */
.bootstrap-dialog .modal-header,
.bootstrap-dialog .modal-header.btn-default,
.bootstrap-dialog.type-default .modal-header {
  display: grid;
  grid-template-columns: 40px 1fr;
  gap: 14px;
  align-items: center;
  padding: 22px 22px 0;
  background: var(--surface-card);
  border: 0;
  border-radius: var(--radius-box) var(--radius-box) 0 0;
  box-shadow: none;
}
.bootstrap-dialog .modal-header:before {
  content: "\f05a";                      /* fa-info-circle — neutral */
  font-family: FontAwesome;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 18px;
  background: var(--pastel-sky-light);
  color: var(--pastel-sky-text);
}
.bootstrap-dialog.okis-confirm--danger .modal-header:before {
  content: "\f1f8";                      /* fa-trash — destructive */
  background: var(--panel-danger-bg);
  color: var(--status-danger);
}
/* The plugin nests title + close button in a `.bootstrap-dialog-header` div, which would take
   the whole second grid cell; `display: contents` promotes them so the title lands in it. */
.bootstrap-dialog .modal-header > .bootstrap-dialog-header { display: contents; }
.bootstrap-dialog .bootstrap-dialog-title,
.bootstrap-dialog.type-default .bootstrap-dialog-title {
  font-size: 16px;
  font-weight: 600;
  line-height: 1.35;
  color: var(--text-body);
}
/* Absorbed from widgets/bsdialogform's own stylesheet, which the AJAX dialog path appended AFTER
   this file — the only sheet that could land behind the canonical rules while touching shared
   selectors (.modal-footer .btn). Selectors kept verbatim; the .form-actions hide is load-bearing:
   showDialog() copies that bar into .bsdialog-footer and the in-body original must not show. */
.modal.large {
  width: 60%;
  margin-left: -30%;
}
.modal-body .form-actions { display: none; }
/* The corner ✕ is a dialog's ONLY way out now that the footer's Zavřít is hidden below, and bootstrap
   ships `.close` at `opacity: .2` — a 20%-grey cross on a white header, too faint for the only exit
   out of a dialog. Full opacity and the app's own muted ink, keeping
   bootstrap's "brightens on approach" direction by resolving to the destructive ink on hover. */
.bsdialog .modal-header > button.close {
  opacity: 1;
  color: var(--text-muted);
  text-shadow: none;
  font-size: 22px;
}
.bsdialog .modal-header > button.close:hover,
.bsdialog .modal-header > button.close:focus {
  opacity: 1;
  color: var(--status-danger-text);
}
/* ---------- A dialog has ONE way out, and it is the corner ✕ ----------
   The footer's Zavřít was a second one, and the footer is where showDialog() copies the source bar's
   buttons. Hidden rather than deleted from the ~287 partials that emit it, because the same markup is
   also a PAGE's bar — and a handful of those carry a labelled "Zpět na …" link, which is navigation,
   not a Zavřít. `display: none` takes it out of the tab order as well as out of the paint. */
.bsdialog-footer a.action-exit { display: none; }
.modal-footer .btn { margin-bottom: 0; }
.modal-body { overflow: auto; }
/* The message is the body, indented to the title's column (22 + 40 + 14). */
.bootstrap-dialog .modal-body { padding: 8px 22px 4px 76px; }
.bootstrap-dialog .bootstrap-dialog-message {
  font-size: 13px;
  line-height: 1.45;
  color: var(--text-muted);
}
.bootstrap-dialog .bootstrap-dialog-close-button .btn-modal-close {
  background: none;
  border: 0;
  color: var(--text-muted);
  opacity: .6;
}
.bootstrap-dialog .bootstrap-dialog-close-button .btn-modal-close:hover { opacity: 1; }
/* --- bootbox --- */
/* bootbox has no title parameter and the app always passes a single message, so there the
   message IS the title line and any nested <p>/<small> becomes the 13px body. */
.bootbox .modal-body {
  display: grid;
  grid-template-columns: 40px 1fr;
  gap: 14px;
  align-items: start;
  padding: 22px 22px 6px;
}
.bootbox .modal-body:before {
  content: "\f05a";
  font-family: FontAwesome;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 18px;
  background: var(--pastel-sky-light);
  color: var(--pastel-sky-text);
}
.bootbox.okis-confirm--danger .modal-body:before {
  content: "\f1f8";
  background: var(--panel-danger-bg);
  color: var(--status-danger);
}
/* The ALERT's disc says which kind of message this is.  `info` is the sky disc declared above and needs
   no rule of its own: it is what every `bootbox.alert()` has always looked like, and it stays the
   default for the call sites that cannot prove their outcome.  The three below are opted into from JS by
   `okis.alert(msg, sev)` and use the same tint/ink pairs as the flashes in 3f, so a "saved" disc and a
   "saved" flash are the same green on the same mint. */
.bootbox.okis-alert--success .modal-body:before {
  content: "\f00c";
  background: var(--panel-success-bg);
  color: var(--ok-green);
}
.bootbox.okis-alert--warning .modal-body:before {
  content: "\f071";
  background: var(--panel-warning-bg);
  color: var(--panel-warning-text);
}
.bootbox.okis-alert--error .modal-body:before {
  content: "\f06a";
  background: var(--panel-danger-bg);
  color: var(--status-danger);
}
/* The message line sits on the CENTRE of the icon disc.  The grid is `align-items: start`, which is the
   right choice — a long message must grow DOWNWARD from the disc, not push the disc to the middle of a
   paragraph — but "start" aligns the top of a 21.6px line box with the top of a 40px disc, which puts
   the text 7.2px high.  The 9px is derived, not tuned: (40 - 16 x 1.35) / 2 = 9.2, i.e. half the
   difference between the disc and one line of the message.  It follows the two values above it — change
   the disc size or the message's line-height and this has to move with them, which is why all three
   live in one block. */
.bootbox .bootbox-body {
  font-size: 16px;
  font-weight: 600;
  line-height: 1.35;
  color: var(--text-body);
  padding-top: 9px;
}
.bootbox .bootbox-body p,
.bootbox .bootbox-body small,
.bootbox .bootbox-body .text-muted {
  font-size: 13px;
  font-weight: 400;
  line-height: 1.45;
  color: var(--text-muted);
  margin: 6px 0 0;
}
/* bootbox prepends the ✕ into the body (and inline-styles `margin-top: -10px`) whenever the
   dialog has no title, which would otherwise claim the first grid cell. */
.bootbox .bootbox-close-button {
  position: absolute;
  top: 12px;
  right: 14px;
  margin: 0 !important;
  color: var(--text-muted);
  opacity: .6;
  text-shadow: none;
}
.bootbox .bootbox-close-button:hover { opacity: 1; }
/* bootbox's own title variant (bootbox.dialog(), used for a few custom dialogs in modules.js)
   keeps a plain white header rather than growing a second icon disc. */
.bootbox .modal-header {
  background: var(--surface-card);
  border: 0;
  padding: 20px 22px 0;
}
.bootbox .modal-header .modal-title { font-size: 16px; font-weight: 600; color: var(--text-body); }
.bootbox:has(> .modal-dialog .modal-header) .modal-body { display: block; padding: 10px 22px 4px; }
.bootbox:has(> .modal-dialog .modal-header) .modal-body:before { content: none; }
.bootbox:has(> .modal-dialog .modal-header) .bootbox-body { font-size: 14px; font-weight: 400; color: var(--text-muted); }
/* --- shared: footer --- */
.bootstrap-dialog .modal-footer, .bootbox .modal-footer {
  border-top: 0;
  padding: 12px 22px 22px;
  text-align: right;
}
.bootstrap-dialog .modal-footer .btn, .bootbox .modal-footer .btn { min-width: 92px; }
/* Ghost "Zrušit" + the filled verb.  The dialogs are appended to <body>, i.e. outside
   `.content-wrapper`, so none of the content-area button repaints reach them — these rules are the whole
   palette, and for the ALERT's OK they are the only paint it has (it carries no paint class at all).
   All they have to out-rank is the canonical class the confirm buttons also carry (`btn-ghost` on the
   cancel, `btn-brand` on the OK), and the (0,3,0) selector shape does that on its own: measured on a
   real dialog, dropping every `!important` in this block leaves all four buttons byte-identical.  The
   twelve are kept because this phase does not change the app's `!important` set, not because any of
   them is holding something off.

   An ALERT's single OK joins the ghost, it does not lead anything — there is no choice to make in a
   dialog with one button, so the filled verb would be shouting.  It shares the cancel's rules rather
   than repeating them.  (bootbox would otherwise have made it `btn-primary`, i.e. orange.) */
.modal-footer .btn.okis-confirm__cancel,
.modal-footer .btn.okis-alert__ok {
  background: var(--surface-card) !important;
  border-color: var(--border-strong) !important;
  color: var(--ok-green) !important;
}
/* The alert's OK takes the hover but NOT the `:focus` half. It opens focused (scripts.js restores
   the focus bootbox used to put on its `btn-primary`), so a focus repaint would mean the resting
   white ghost is the one state nobody ever sees. The 2px ring at the end of this section is the
   focus signal there, which is what a ring is for. */
.modal-footer .btn.okis-confirm__cancel:hover,
.modal-footer .btn.okis-confirm__cancel:focus,
.modal-footer .btn.okis-alert__ok:hover {
  background: var(--surface-tint) !important;
  border-color: var(--ok-green) !important;
  color: var(--ok-green-dark) !important;
}
.modal-footer .btn.okis-confirm__ok {
  background: var(--ok-green) !important;
  border-color: var(--ok-green) !important;
  color: var(--text-inverse) !important;
}
.modal-footer .btn.okis-confirm__ok:hover,
.modal-footer .btn.okis-confirm__ok:focus {
  background: var(--ok-green-dark) !important;
  border-color: var(--ok-green-dark) !important;
  color: var(--text-inverse) !important;
}
/* `.okis-confirm--danger .modal-footer .btn.okis-confirm__ok` (+ its `:hover`/`:focus`) deleted:
   the destructive OK is `.btn-danger-solid` and carries no `okis-confirm__ok`, so neither part had a
   member left.  It could not keep the hook — the green rule above is (0,3,0) `!important` and would
   have out-painted the class. */
/* ---------- The dialog footer's focus ring stops reading as a second border ----------
   `bootbox.alert()` focuses its OK button as it opens, so the button comes up wearing the app's global
   focus ring, `0 0 0 3px rgba(0,80,70,.14)`.  A 3px spread with no offset HUGS the border, and on a
   filled button the result is a pale second edge one shade off the first — and on an orange OK it is
   also the wrong hue.

   Replaced, in dialog footers only, by a ring that is unmistakably a ring: 2px brand green, held 2px off
   the button so there is white between the two edges.  Accessibility is preserved — it is still
   `:focus-visible`, so it still appears for keyboard users and on the auto-focused OK, which is correct:
   that button IS focused and Enter will activate it.  Every other button in the app keeps the global
   wash. */
.bootstrap-dialog .modal-footer .btn:focus-visible,
.bootbox .modal-footer .btn:focus-visible {
  box-shadow: none;
  outline: 2px solid var(--brand-green);
  outline-offset: 2px;
}
/* ---------- The envelope dialog's document and recipient rows ----------
   A ROW IS A FLEX ROW, and that is what lets its controls be full-size ones.  Both lists were built
   out of `.pull-right` floats, and a float sits at the TOP of the row: fine while every control was a
   24px `.btn-xs` tile, impossible once the signature-type select became a real `.form-control`, since
   a 34px float beside a 24px float cannot be centred on anything.  A float row also reads
   right-to-left in the markup, so the row's controls are now emitted in reading order and the group
   is pushed over by `margin-left: auto` on whichever control OPENS it — the reorder pair in a document
   row, the select in a recipient row.

   `min-width: 0` on the name is what keeps a long filename from pushing the controls off the row: a
   flex item's floor is its `min-content` width, not zero, and these names are one unbroken token.
   The select restates `width: auto` because `.form-control` is `width: 100%`, which in a flex row
   means "the whole row"; `flex: none` on the controls keeps the shrink on the name.

   ONLY the document name ellipsizes.  It is already truncated to 50 characters in the markup and
   carries the whole string in a tooltip, so a clipped tail costs nothing.  A recipient name has no
   tooltip behind it, so it wraps instead — a taller row rather than characters nobody can recover. */
.attach-documents-or-create-envelope-form .list-group-item {
  display: flex;
  align-items: center;
  gap: var(--spacing-inline-gap);
}
.attach-documents-or-create-envelope-form .list-group-item > .document-name,
.attach-documents-or-create-envelope-form .list-group-item > .recipient-name { min-width: 0; }
.attach-documents-or-create-envelope-form .list-group-item > .document-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.attach-documents-or-create-envelope-form .list-group-item > .btn,
.attach-documents-or-create-envelope-form .list-group-item > .label,
.attach-documents-or-create-envelope-form .envelope-signature-type { flex: none; }
.attach-documents-or-create-envelope-form .envelope-signature-type {
  width: auto;
  max-width: 100%;
}
.attach-documents-or-create-envelope-form .list-group-item > .envelope-move-up,
.attach-documents-or-create-envelope-form .list-group-item > .envelope-signature-type { margin-left: auto; }
/* Every action in this dialog sits beside a 34px control (the signature select, the pickers), so
   the tiles take the CONTROL height, not the free-standing 36. One rule, all seven. */
.attach-documents-or-create-envelope-form :is(.envelope-move-up, .envelope-move-down,
.remove-envelope-document, .remove-envelope-recipient,
#envelope-add-document-btn, #envelope-add-contact-btn, #envelope-add-account-btn) {
  width: var(--control-height);
  height: var(--control-height);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
}
/* The merge-documents dialog's rows: same anatomy as the envelope rows above. Only the name
   shrinks — it is 50-char truncated and carries the full value in a tooltip, so a clipped tail
   costs nothing. */
#documents-list .list-group-item {
  display: flex;
  align-items: center;
  gap: var(--spacing-inline-gap);
}
#documents-list .list-group-item > .document-name {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
#documents-list .list-group-item > .move-up { margin-left: auto; }
#documents-list .list-group-item > :is(.move-up, .move-down, .remove-document) {
  width: var(--control-height);
  height: var(--control-height);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  flex: none;
}
/* The two document dialogs' picker row: the select2 fills the row, the buttons keep their width. */
.merge-documents-form .document-selector-wrapper,
.attach-documents-or-create-envelope-form .document-selector-wrapper {
    display: flex;
    gap: 10px;
    align-items: flex-start;
}
.merge-documents-form .document-selector-container,
.attach-documents-or-create-envelope-form .document-selector-container {
    flex: 1;
    min-width: 0;
}
.merge-documents-form .move-up,
.merge-documents-form .move-down,
.attach-documents-or-create-envelope-form .envelope-move-up,
.attach-documents-or-create-envelope-form .envelope-move-down {
    margin-right: 5px;
}
.attach-documents-or-create-envelope-form #create-new-envelope,
.attach-documents-or-create-envelope-form #insert-into-envelope {
    margin-bottom: 15px;
}
/* The inline password error of the merge-documents dialog: the same ink as any other error message
   (`.has-error .help-block`), because that is what this help-block is. */
.merge-password-error {
    color: var(--status-danger);
}
/* A dialog form's rows keep the pre-compact 15px rhythm the dialogs were laid out around. */
.modal-content .modal-body .row .form-group {
    margin-bottom: 15px !IMPORTANT;
}

/* ============================================================================================
   3h. COMPONENTS / canonical classes — one block per Figma "Buttons" / "Chips" component.  Every value
   was MEASURED off the as-built app.  NOTHING in the app emits a legacy paint class beside a canonical
   one any more: the classes below are the only paint a first-party button has.

   THIS BLOCK HAS TO BE LAST, in the last stylesheet.  Every selector in it is a bare (0,1,0) class and
   so are its competitors: bootstrap's `.btn { border: 1px solid transparent }` and
   `a { background-color: transparent }`, skin.css's `a { color: <orange> }` and its `.btn-primary`
   fill, and this sheet's own `.btn-default` / `.btn-danger` at 1065-1094.  Every one of those ties is
   decided on LOAD ORDER, so the canonical paint wins only while `css/okis-2026-unified.css` is the
   last entry of AppAsset::$css and this is the last paint block in it.  Move either and a `.btn-ghost`
   anchor comes back transparent-bordered with orange ink.

   WHAT STILL OUT-RANKS IT, and must: everything at (0,2,0) or deeper.  That is the three surviving
   contextual legacy rules (`.content-wrapper .btn-primary`, `.content-wrapper .btn-danger`,
   `.content-wrapper .btn-success` and their state pairs), the whole `!important` set (navbar, dialog
   footers, `.btn.disabled`, `.btn-star`, `.btn-entity`), and AdminLTE's box-primary header ink at
   (0,5,0).  Everything those three still paint carries the legacy class ALONE and is either vendor
   (kartik's fileinput, the `admin` module, bootbox's own button-class fallback) or markup whose class
   IS its data (the colour-swatch pickers, the discussion status chips, the two `data-toggle="buttons"`
   radio groups) — which is why they outlive the emission that used to feed them.

   CONSEQUENCE to know before adding a widget button OUTSIDE `.content-wrapper`: the only legacy fill
   left out there is skin.css's bare `.btn-primary` (orange) at (0,1,0), so the canonical class wins the
   load-order tie and such a button comes out with its 2026 look.  That is now the intended answer, not
   an accident: the body-level dialogs bootbox and BootstrapDialog build already mix the two — the
   confirm's cancel and OK carry canonical classes, bootbox's own fallback buttons carry legacy ones.

   DELIBERATELY NOT DECLARED HERE: `display` (see the height ladder), padding / font-size on the paint
   classes (still owned by the legacy `.btn` box), and a glyph size on the tiles (the grid tiles render
   their `.fa` at 14px, the clear-filter funnel at 13px — two call sites, two answers, so the tile shape
   does not claim it).
   ============================================================================================ */

/* ============ THE HEIGHT LADDER ============
   36px free-standing  ·  34px on a control line  ·  28px compact  ·  20px the grid checkbox.

   36px is the BLESSED free-standing default: it is what a plain `.btn` already computes (`padding:
   7px 14px` + `line-height: 20px` + 2px border), so declaring it changes nothing today and makes the
   value survive the day that padding stops being reachable.  Figma's Buttons page says 34; the ruling
   keeps as-built 36 and gives 34 to the control line only.  The other three rungs have their own
   homes: 34 = --control-height (the filter-bar actions, the record trio, the navbar, every
   input-group); 28 = --size-grid-action-btn / --size-boxheader-icon-btn / --size-chip-add-btn;
   20 = --size-checkbox-grid / --size-radio-form.

   Why declaring it is inert: this selector is (0,1,0) and every 34 and every grid 28 is pinned by a
   `height` at (0,2,0) or deeper — `.list-filter-bar__actions .btn`, `.grid-view .action-column .btn`,
   `.content-header .record-actions .btn`, `.content-header .page-actions .btn`,
   `.navbar-custom-menu .btn`, `.input-group-btn .btn`, `body.toolbar-icons …` — so those rungs keep
   their own heights and this one is read only by the free-standing button, which already computes
   36px from padding.  The two cases sized by padding with NOTHING to lose to are handed back
   explicitly by the two guards below.

   `display: inline-flex` beats bootstrap's `.btn { display: inline-block }` on load order — both are
   (0,1,0) — so it changes the layout mode of every canonical button nothing pins deeper.  That is
   what makes `gap` the glyph-to-label spacing: a flex container turns each text run into an anonymous
   item and trims its edge white space, so the literal space `Icon::createIcon($i, true)` emits stops
   generating a box and only `gap` is left to hold the pair apart.  Measured over the 125-page corpus:
   875 buttons change layout mode, 3 257 take the gap, and a labelled icon button grows 2.34px
   (6 - 3.66).  `justify-content` replaces the `text-align: center` an inline-block button read from
   `.btn`; drop it and every fixed-width tile left-aligns its glyph.
   A BUTTON THAT MUST NOT BE A FLEX CONTAINER HAS TO SAY SO AT (0,2,0) OR DEEPER. */
.btn-cta, .btn-brand, .btn-ghost, .btn-ghost-danger {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--spacing-btn-icon-gap);
  height: var(--size-btn-height);
}

/* The strut the line above takes away, handed back to the glyph.
   font-awesome declares `.fa { line-height: 1 }`.  While the glyph was an inline box the button's own
   line box (the strut) set the content height; as a flex item the glyph sets its own, and a button
   whose height is `auto` — the two guards below, and `.btn-group > .btn-entity + .btn-cta` — collapses
   to the glyph.  Measured without this rule: the 22 entity-duo "+" segments 36px -> 29px and the small
   icon-only DetailButton 28px -> 23px, taking 85 rects and 5px of page height with them.
   Inert wherever the height is pinned, which is every other canonical button.

   ONE EXCEPTION, and it is a composite rather than a glyph.  `.btn-glyph-money-stack` is a WRAPPER
   around two `fa-money` notes, the back one absolutely positioned 4px up and 4px left of the other and
   clipped to exactly that 4px.  A 20px strut on the wrapper raises nothing inside it — it drops the
   IN-FLOW note to the strut's baseline, 7.5px below the absolute one instead of 4px, and the clip then
   keeps a strip that no longer meets the front note: measured 3.5px of blank between the two banknotes,
   which is the "too much whitespace" the stack was reported for.  Excluded, it keeps font-awesome's own
   `line-height: 1` — the geometry its three 4px values were cut for.  The exclusion cannot collapse
   anything: both call sites are `p.pull-right` toolbars, where the tile's 36px is pinned (verified). */
:is(.btn-cta, .btn-brand, .btn-ghost, .btn-ghost-danger) > .fa:not(.btn-glyph-money-stack) {
  line-height: inherit;
}

/* TRANSITIONAL — the one rung of the ladder that nothing else pins.
   The box-header / sub-section buttons are 28px because their `padding: 3px 6px` shrinks the box, NOT
   because anything declares a height.  So there is no competing `height` for the 36px above to lose to,
   and without this rule those buttons grow 28 -> 36 (measured: 22 of them on one update page, and
   +48px of page height).  `auto` hands them back to the padding that has always sized them.
   Remove it once `.btn-boxheader` is emitted on those buttons and carries the 28 itself. */
.box-header-buttons :is(.btn-cta, .btn-brand, .btn-ghost, .btn-ghost-danger),
.form-section-label--toggle :is(.btn-cta, .btn-brand, .btn-ghost, .btn-ghost-danger) {
  height: auto;
}

/* TRANSITIONAL — the same job for the bootstrap size classes, and for the same reason.
   A free-standing `.btn-sm` computes 28px and `.btn-xs` 24px from padding alone, with no `height`
   anywhere to pin them, so the 36px above wins the moment one of them gains a paint class.  `auto`
   hands them back to the padding.  Measured population: the 300 `action-client` / `action-contract`
   entity links of a populated precept grid, plus the `btn-xs` rows of the envelope, merge-document and
   discussion dialogs, which no static capture reaches.
   `.btn-lg` is NOT listed: its only free-standing use is the login page, which unpins itself at
   (0,2,0) in `2d. BASE / buttons` because it has to beat the padding there as well.

   THE SIZE HALF IS `:where()`, NOT `:is()`, AND THAT IS THE WHOLE SAFETY PROPERTY.  `:where()`
   contributes no specificity, so this rule is (0,1,0) — a tie with the ladder above, won on load
   order because it comes after it, and a tie with any element that declares its OWN height at
   (0,1,0), won by whichever is written later in this sheet.  As `:is(.btn-sm, .btn-xs)` it was
   (0,2,0) and out-ranked such an element unconditionally: `.risk-card__toggle` (24px, `padding: 0`)
   collapsed to a 15px line box the moment it gained `.btn-ghost`, because there was no padding left
   for `auto` to hand it back to.  Any element with its own height must be declared BELOW this rule.
   Remove it once the size classes have heights of their own. */
:is(.btn-cta, .btn-brand, .btn-ghost, .btn-ghost-danger):where(.btn-sm, .btn-xs) {
  height: auto;
}

/* ---------- .btn-cta — Figma Buttons / "CTA (orange)" ----------
   The one filled orange action: "Uložit", "Nový klient", "Provést uzávěrku", the toolbar "+".
   Emitted by widgets/Button TYPE_SUCCESS and written at ~110 page-action call sites; nothing pairs it
   with `btn-success` any more.  The `btn-success` still in the tree is a colour CHIP, not a CTA —
   renaming those to this class is a design decision about a chip palette, not a rename. */
.btn-cta {
  background-color: var(--brand-orange);
  border-color: var(--brand-orange);
  color: var(--text-inverse);
  border-radius: var(--radius-control);
}
.btn-cta:hover, .btn-cta:focus, .btn-cta:active, .btn-cta:active:focus {
  background-color: var(--brand-orange-dark);
  border-color: var(--brand-orange-dark);
  color: var(--text-inverse);
}

/* ---------- .btn-brand — Figma Buttons / "Brand (filled green)" ----------
   The filled brand-green action: the filter bar's "Vyhledat" — running a search commits nothing, so it
   takes brand green rather than the orange CTA.  It is also the neutral confirm dialog's OK, wired in
   scripts.js so the class matches what the footer rule paints.
   EMISSION: deliberately NOT a TYPE_* mapping.  TYPE_PRIMARY maps to `.btn-ghost` (the majority look:
   ~137 of its ~139 call sites render as the white ghost), and `.btn-brand` is written explicitly at
   the three places that really are brand green — the "Vyhledat" submit of the client, notification and
   commission-balance-log filter bars.  Those three are raw `Html::submitButton` and never passed
   through widgets/Button anyway, so the class is stated at the call site, which is where a one-off
   look belongs. */
.btn-brand {
  background-color: var(--brand-green);
  border-color: var(--brand-green);
  color: var(--text-inverse);
  border-radius: var(--radius-control);
}
.btn-brand:hover, .btn-brand:focus, .btn-brand:active, .btn-brand:active:focus {
  background-color: var(--brand-green-dark);
  border-color: var(--brand-green-dark);
  color: var(--text-inverse);
}

/* ---------- .btn-ghost — Figma Buttons / "Ghost (white)" ----------
   The default quiet button, and the most common one in the app: white fill, strong-border outline,
   brand-green ink, mint on hover.
   Emitted by widgets/Button for BOTH TYPE_DEFAULT and TYPE_PRIMARY — the content-area `btn-primary`
   WAS this look, which is why the two types share one canonical class.
   Two containers still tint the hover with `--surface-tint` instead, both from deeper than (0,2,0):
   the legacy-keyed `.content-wrapper .btn-primary` pair and the open `.dropdown-toggle.btn-ghost`.
   `--brand-mint-bg` is the base answer, and it is now the box-solid header's too. */
.btn-ghost {
  background-color: var(--surface-card);
  border-color: var(--border-strong);
  color: var(--brand-green);
  border-radius: var(--radius-control);
}
/* Both ghost classes fold `:active` into this block, the way `.btn-cta` and `.btn-brand` above do —
   one fill for every interaction state: a bare `:active` falls to the base link rule in 2a
   (`a, a:focus, a:hover, a:active { color: var(--link-ink) }`) at (0,1,1), which out-ranks a
   (0,1,0) canonical class and turns a pressed ghost's ink link-red. */
.btn-ghost:hover, .btn-ghost:focus, .btn-ghost:active, .btn-ghost:active:focus {
  background-color: var(--brand-mint-bg);
  border-color: var(--border-strong);
  color: var(--brand-green);
}

/* ============ THE DANGER FAMILY ============
   One `btn-danger` used to wear three different looks.  The ruling: the WHITE GHOST is the canonical
   one everywhere in the page — the quiet destructive button is the default — and the full-strength
   red is earned only by a confirm dialog's verb.
     .btn-ghost-danger — every destructive action in the page (canonical, TYPE_DANGER)
     .btn-danger-solid — the verb in a destructive confirm dialog, and nowhere else
   A third variant, `.btn-tile--danger` (the blush tile), was defined here and deleted: the ruling gave
   the record title row the white ghost too, so the blush had no call site to earn.  The only blush
   left in the app is the legacy `.content-wrapper .btn-danger` pair in 4a, which paints raw markup. */

/* ---------- .btn-ghost-danger — Figma Buttons / "Ghost danger" ----------
   The destructive action drawn quietly: white fill, red ink, tinted red border.  CANONICAL.
   Emitted by widgets/Button TYPE_DANGER; `scripts.js` also routes on it to pick the red confirm
   dialog, so a destructive button that does not carry it gets the neutral green anatomy. */
.btn-ghost-danger {
  background-color: var(--surface-card);
  border-color: var(--status-danger-border);
  color: var(--status-danger-text);
  border-radius: var(--radius-control);
}
.btn-ghost-danger:hover, .btn-ghost-danger:focus,
.btn-ghost-danger:active, .btn-ghost-danger:active:focus {
  background-color: var(--status-danger-wash);
  border-color: var(--status-danger-text);
  color: var(--status-danger-text);
}

/* ---------- .btn-danger-solid — the dialog verb ----------
   EXCLUSIVE to the OK button of a destructive confirm dialog.  The full-strength red is the one place
   the app still spends it: the dialog has already interrupted the user, the verb is the point of the
   interruption, and there is no surrounding content for it to shout over.  Do NOT use it anywhere
   else — an in-page destructive action is .btn-ghost-danger.
   Emitted by `okisConfirmOkClass()` in scripts.js, for both dialog engines.  That button must NOT also
   carry `okis-confirm__ok`: the footer rule keyed to that hook is (0,3,0) `!important` brand green and
   would out-paint this (0,1,0) class. */
.btn-danger-solid {
  background-color: var(--status-danger);
  border-color: var(--status-danger);
  color: var(--text-inverse);
  border-radius: var(--radius-control);
}
.btn-danger-solid:hover, .btn-danger-solid:focus {
  background-color: var(--status-danger-text);
  border-color: var(--status-danger-text);
  color: var(--text-inverse);
}

/* ---------- .btn-tile — Figma Buttons / "Icon tile 36" ----------
   The square icon-only tile of the record toolbar in body.toolbar-icons.
   Coexists with: the toolbar rules on `p.pull-left/.pull-right/.communication .btn`.
   `font-size: 0` is NOT restated: hiding a label the markup still carries is an icon-MODE concern that
   belongs to body.toolbar-icons, not to the tile shape — a `.btn-tile` is icon-only by construction.
   Emitted by app\widgets\buttons\PageActionCreateButton, where the page-actions block re-squares it to
   the fixed header's 34px. */
.btn-tile {
  width: var(--size-toolbar-icon-tile);
  height: var(--size-toolbar-icon-tile);
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  line-height: 1;
  flex: none;
  border-radius: var(--radius-control);
}

/* ---------- .btn-tile-sm — Figma Buttons / "Icon tile 28" ----------
   The grid-row action tile (view / update / delete) and the filter row's clear-filter funnel — both
   measure the same shape, which is why ActionColumn can emit it on both.
   Coexists with: btn-ghost, which carries the paint, + `.grid-view .action-column .btn`, which wins
   the box back at (0,3,0) whenever the tile is inside a grid.
   Emitted by components/web/ActionColumn.php. */
.btn-tile-sm {
  width: var(--size-grid-action-btn);
  height: var(--size-grid-action-btn);
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  line-height: 1;
  border-radius: var(--radius-control);
}

/* ---------- .btn-boxheader — Figma Buttons / "Box-header 28" ----------
   The small LABELLED button of a box header or a form sub-section heading.  A short button, not a
   tile: 3 + 20 line-height + 3 + 2 border = the 28px that lines it up with the tile ladder.
   The 28px it would carry is today handed out by the `height: auto` guard above plus adminlte.css's
   `.box-header-buttons a { padding: 3px 6px }`; wiring this class is what lets that guard go.  In
   body.toolbar-icons the same button squares to --size-boxheader-icon-btn.  No emission point yet. */
.btn-boxheader {
  padding: 3px 6px;
  line-height: 20px;
  border-radius: var(--radius-control);
}

/* ---------- .chip — Figma Chips / "Chip (pill)" ----------
   The bordered white pill: group-membership chips in the record title row, the saved-search chips on
   the home page, the entity chip in the Avíza/Úkoly lists.
   Coexists with: `.search-chip` (from the frozen home sheet, which is why the shape is restated here
   under the canonical name rather than aliased) and its two size modifiers `.entity-chip` /
   `.header-groups > .search-chip`, which carry the same 4/6 optical split at their own sizes.
   The 4/6 vertical padding is optical, not geometric — see `.kv-grid__value > .kv-pill`.
   Emitted by widgets/GroupChips.php (its _header-chips partial).
   THE WEIGHT IS DECLARED, and it has to be: a chip that states no `font-weight` inherits its
   container, and the record title row puts its chips inside an `<h1>`, where bootstrap.css:1187 gives
   `h1…h6 { font-weight: 500 }` — measured live, the three group chips on
   `/contract/contract/update?id=973310` computed 500 while the byte-identical `.search-chip` on
   `/site/index` computed 400.  One class, two weights, decided by an ancestor that has nothing to do
   with chips.  400 is the app's label weight (`label.control-label`, `.box-title` and
   `label.form-label` are all 400). */
.chip {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 4px 8px 6px 14px;
  background-color: var(--surface-card);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-pill);
  font-size: 13px;
  font-weight: 400;
  color: var(--brand-green);
  transition: border-color 120ms, box-shadow 120ms;
}
.chip:hover {
  border-color: var(--brand-green);
  box-shadow: var(--shadow-box);
}

/* ---------- .status-chip — Figma Chips / "Status chip" ----------
   The small square-cornered state label.  There IS a shared shape: `.cbl-state` and `.kv-pill` are one
   rule and agree on all six metrics below; only the tint differs, and the tint belongs to the state
   modifier, so no fill or ink is declared here.  The 4/6 vertical padding is optical, not geometric —
   see that rule for the measurement.
   Coexists with: .cbl-state, .kv-pill.  Emitted by widgets/devel/DevelInfo.php, which composes it
   with `.devel-info-chip` for the navbar tint and placement (2g). */
.status-chip {
  display: inline-block;
  padding: 4px 8px 6px;
  border-radius: var(--radius-sm);
  font-size: 12px;
  font-weight: 600;
  line-height: 16px;
}

/* ============================================================================================
   4a. AREAS / record title header and group chips
   ============================================================================================ */
/* The title row's one pastel tile: the sky merge.  (There is no cream star tile — a favourited star
   is a ghost tile with a GOLD GLYPH everywhere; see the grid-star block in 3e.)
   A blush `.content-header .btn-danger` pair was deleted here.  `.content-header` exists in exactly
   one place — `layouts/content.php`, inside `.content-wrapper` — so `.content-wrapper .btn-danger`
   further down matched every element this pair could, at the same (0,2,0) / (0,3,0), with the same
   `!important` on `background`, with the same values, later in this sheet.  The title-row trash is
   painted by the `.record-actions` block in 4a at (0,4,0) either way. */
.content-header > h1 > .title-text:after {
  content: "";
  position: absolute;
  left: 0;
  bottom: 0;
  width: 44px;
  height: 3px;
  border-radius: 2px;
  background: var(--ok-gold);
  display: none;
}
.content-header .btn.btn-merge { background: var(--ok-sky) !important; border-color: var(--ok-sky); color: var(--pastel-sky-text); }
.content-header .btn.btn-merge:hover { background: #B9CBF0 !important; border-color: #B9CBF0; color: var(--pastel-sky-text); }
/* Content-area blush, and by now it paints no destructive BUTTON at all: the widget emits
   `btn-ghost-danger` alone and every hand-written and JS-built delete followed it, so those are the
   white ghost the danger family in 3h describes.  THE PAIR STAYS for what is left, which is markup
   whose paint class is its DATA — the red chip of the colour-swatch pickers, `_discussions.php`'s
   closed-status chip — plus kartik fileinput's upload-error button and the `admin` module's views.
   None of them carries a canonical class, so this is their only fill, and without it they fall to
   bootstrap's `.btn-danger:active { color: #fff }` at (0,2,0) on press: white ink on the white that
   this sheet's bare `.btn-danger { background: #fff !important }` pins at (0,1,0). */
.content-wrapper .btn-danger { background: var(--ok-blush) !important; border-color: var(--ok-blush); color: var(--status-danger-text); }
.content-wrapper .btn-danger:hover { background: var(--pastel-blush-hover) !important; border-color: var(--pastel-blush-hover); color: var(--status-danger-text); }
/* Disabled buttons: ghost stays visible but clearly inert */
.btn.disabled, .btn[disabled], fieldset[disabled] .btn {
  background: var(--surface-disabled) !important; border-color: var(--border-default) !important;
  color: var(--text-disabled) !important; opacity: 1; cursor: not-allowed; box-shadow: none;
}
/* ---------- [header] record actions in the fixed content header ---------- */
.content-header > h1 {
  display: flex;               /* the title, the group chips and the action tiles are one flex row */
  align-items: center;
  gap: 14px;                   /* title-to-chips and chips-to-actions */
  float: none;                 /* adminlte.css:459 floats the h1 at >=992px, which shrink-wraps it;
                                  the mock is float:none so the h1 spans the header and the actions'
                                  margin-left:auto can push them flush right. Safe: .content-header
                                  has no other child (no breadcrumb is rendered into it). */
  white-space: nowrap;         /* keep: single line, actions shrink-to-fit */
  overflow: visible;           /* adminlte.css:60 h1{overflow:hidden} would clip the buttons */
  /* One title-row height whether or not the row carries buttons.  Without this the row is only as
     tall as the 26.4px title line — a 7.6px shorter header on every page with no action — because
     the 34px buttons are what stretches it everywhere else.  34px is that button-carrying case
     (`--control-height`, the height both `.page-actions .btn` and the record trio are pinned to),
     and it is the CEILING the fixed header can take: 8 + 34 + 8 + 1 = 51px, one pixel inside the
     52px `.content` clears for it. */
  min-height: var(--size-control-height);
}
/* A long title must ellipsize here rather than overflow the header, now that the h1 no longer clips.
   `overflow != visible` also zeroes this flex item's automatic minimum size, so it can actually
   shrink. */
.content-header > h1 > .title-text { display: inline-block; position: relative; padding-bottom: 0; overflow: hidden; text-overflow: ellipsis; }
.content-header .record-actions {
  display: inline-flex;
  gap: var(--spacing-inline-gap);
  margin-left: auto;           /* actions sit flush right, NOT beside the title */
  padding-right: 8px;
  font-size: 14px;             /* don't inherit the h1's 24px */
  flex: none;                  /* a long title must ellipsize, never squash the buttons */
}
/* The header is position:fixed and `.content` only clears it by margin-top:52px (>=992px), so the
   title row's buttons must fit inside that budget: 3 + 18 line-height + 3 + 2 border = 26px, which fits
   the h1's 26.4px line box and leaves the header at its pre-change 50.4px.  Cheaper than touching
   adminlte.css's offset system. */
.content-header .btn { line-height: 18px; padding: 3px 10px; }
.content-header .record-actions .btn {
  width: var(--size-title-trio-btn); height: var(--size-title-trio-btn);
  padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 15px;
}
/* Disabled buttons must not react to hover.  The disabled palette is pinned at (0,2,0) with
   `!important`, but the per-variant HOVER rules are more specific and also `!important`, so they won in
   the disabled state — a disabled "Uložit" flashed dark orange and a disabled `btn-entity` flashed mint.
   The same mismatch also broke the RESTING disabled state of the entity/star variants
   (`.content-wrapper .btn.btn-entity` (0,3,0) already outranked `.btn.disabled` (0,2,0), so they stayed
   white instead of going ghost), so both states are re-asserted here.
   `.btn.btn` is a deliberate duplicate class: it lifts these selectors to (0,4,0) — enough to outrank
   the most specific competitor, `.content-wrapper .btn.btn-entity:hover` (0,4,0, which then loses the tie
   on load order) — without enumerating every variant by hand. */
.content-wrapper .btn.btn.disabled, .content-wrapper .btn.btn[disabled],
.content-wrapper .btn.btn.disabled:hover, .content-wrapper .btn.btn[disabled]:hover,
.content-wrapper .btn.btn.disabled:focus, .content-wrapper .btn.btn[disabled]:focus,
.content-wrapper .btn.btn.disabled:active, .content-wrapper .btn.btn[disabled]:active,
.content-wrapper fieldset[disabled] .btn, .content-wrapper fieldset[disabled] .btn:hover {
  background: var(--surface-disabled) !important;
  border-color: var(--border-default) !important;
  color: var(--text-disabled) !important;
  box-shadow: none !important;
  transform: none !important;
}
/* ---------- Group chips under the record title ----------
   The chip row's `margin-left: auto` claims the free space, so the chips sit flush right with the
   record-action trio 20px after them.  When the widget renders nothing (no groups, no permission) the
   trio's own `margin-left: auto` still applies, so pages without chips are untouched.
   The `.search-chip` / `.search-chip__remove` base rules are not repeated here — they are in
   `4g. AREAS / home page`. */
.content-header > h1 > .header-groups {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--spacing-inline-gap);
  margin-left: auto;
  font-size: 13px;      /* don't inherit the h1's 24px */
  line-height: 18px;
  white-space: normal;  /* the h1 is nowrap; the chip row is the one place that may wrap */
  min-width: 0;
}
.content-header > h1 > .header-groups + .record-actions {
  margin-left: 20px;    /* the trio follows the chips, not the far edge */
}
/* The round 28x28 ⊕ — the small-tile rung of the size ladder.  Beats
   `.content-header .btn { padding: 3px 10px }` on specificity, and is deliberately OUTSIDE
   `.record-actions` so the trio's 34px squares do not apply. */
.content-header > h1 > .header-groups > .header-groups__add {
  width: var(--size-chip-add-btn);
  height: var(--size-chip-add-btn);
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-pill);
  font-size: 11px;
  line-height: 1;
  flex: none;
}
/* ---------- Page actions in the content header ----------
   Not `.record-actions`: that class squares its buttons into icon tiles and these carry labels.  The
   placement is the record-actions placement without the squaring. */
.content-header .page-actions {
  display: inline-flex;
  gap: 8px;
  margin-left: auto;
  padding-right: 8px;
  font-size: 14px;
  flex: none;                  /* a long title must ellipsize, never squash the buttons */
}
/* ---------- The record title toolbar ----------
   The trio gained Zavřít (✕ ghost) and Uložit (a disc) as one row, and the floating `.form-actions` bar
   is gone from the pages that have the toolbar.  The two tiles inherit the trio's squaring and gap from
   `.content-header .record-actions`, so the only work here is colour.
   Not touched: the `.form-actions` fixed-positioning rules in adminlte.css — every other form in the app
   (dialogs, lookup tables, create pages, user profile) still uses that bar.

   Uložit is the ORANGE CTA: orange is the CTA FAMILY, and the save disc is the save action wherever it
   is drawn.  Keyed to the canonical class and NOT to `btn-success`: everything this rule reaches is
   built by a widget or by scripts.js, and both write `btn-cta` (the `.record-actions` spans in the
   views contain widgets only, and promoteFormActionsToTitleRow writes the pair).  It stays rather than
   being deleted because (0,4,0) with `!important` pins the disc above anything the title row declares
   on a `.btn`, so the fill cannot drift if the canonical base changes.  `:active` is listed with
   hover/focus because the base states the same three. */
.content-header .record-actions .btn.btn-cta {
  background: var(--ok-orange) !important;
  border-color: var(--ok-orange);
  color: var(--text-inverse);
}
.content-header .record-actions .btn.btn-cta:hover,
.content-header .record-actions .btn.btn-cta:focus,
.content-header .record-actions .btn.btn-cta:active {
  background: var(--ok-orange-dark) !important;
  border-color: var(--ok-orange-dark);
  color: var(--text-inverse);
}
/* Sloučit retires its Sky Blue fill for a white ghost with a dark-green glyph, so the toolbar reads as
   one row of quiet tiles with a single filled action (Uložit).  Same specificity trick: the fill rule is
   `.content-header .btn.btn-merge` at (0,3,0). */
.content-header .record-actions .btn.btn-merge {
  background: var(--surface-card) !important;
  border-color: var(--border-strong);
  color: var(--ok-green);
}
.content-header .record-actions .btn.btn-merge:hover,
.content-header .record-actions .btn.btn-merge:focus,
.content-header .record-actions .btn.btn-merge:active {
  background: var(--surface-tint) !important;
  border-color: var(--ok-green);
  color: var(--ok-green-dark);
}
/* ---------- Navbar ----------
   Every navbar button is white with dark-green ink.  Matching on `.navbar-nav > li > .btn` rather than on
   the variant classes is what makes the bell consistent: `views/layouts/header.php` swaps
   `btn-notification--unread` on and off with the count, so keying off a variant would have made the
   button change shape with the number.  The same selector leaves the search pair alone — it sits one
   level deeper, inside `.input-group-btn`, and is already this exact ghost. */
.main-header .navbar .navbar-nav > li > .btn,
.main-header .btn-user {
  background: var(--surface-card) !important;
  border-color: var(--border-strong) !important;
  color: var(--ok-green) !important;
  font-weight: 400;
}
/* Logout is a bare white glyph on the green header — no fill, no border; hover is a 15% white
   wash. It is the one navbar button that does NOT go white, so it has to outrank the white block
   above ((0,4,1) with the `> li >` selector) — hence the same shape plus `.btn-logout`. */
.main-header .navbar .navbar-nav > li > .btn.btn-logout,
.main-header .navbar .btn-logout, .main-header .btn-logout {
  background: transparent !important;
  border-color: transparent !important;
  color: var(--text-inverse) !important;
  box-shadow: none;
}
.main-header .navbar .navbar-nav > li > .btn.btn-logout:hover,
.main-header .navbar .navbar-nav > li > .btn.btn-logout:focus,
.main-header .navbar .btn-logout:hover, .main-header .btn-logout:hover {
  background: rgba(255, 255, 255, .15) !important;
  border-color: transparent !important;
  color: var(--text-inverse) !important;
}
.list-filter-bar__actions .btn.btn-brand:hover,
.list-filter-bar__actions .btn.btn-brand:focus,
.list-filter-bar__actions .btn.btn-brand:active,
.list-filter-bar__actions .btn.btn-brand:active:focus {
  background: var(--ok-green-dark) !important;
  border-color: var(--ok-green-dark);
  color: var(--text-inverse);
}
/* ---------- The title row's own edges ----------
   The record-action trio is 34px squares, joining the control height.  The header's top padding is
   trimmed to 8px so the fixed header still stays within the 52px content offset (8 + 34 + 8 + 1 = 51px).

   THE RIGHT EDGE.  The fixed header's padding-left is 24px to match `.content`, but its padding-right was
   left at AdminLTE's 15px — so the h1 ended 9px past where the content ends, and the record-actions' own
   `padding-right: 8px` then put the last button past that.  Both are restated: 24px matches the content
   column on both sides, and with that the trio's extra 8px would push it 8px INSIDE the cards' right
   edge, so it goes.  `.page-actions` — the index pages' other occupant of this header — carries the same
   8px for the same reason and is zeroed with it. */
.content-header { padding-left: 24px; padding-top: 8px; padding-right: 24px; }
.content-header .record-actions,
.content-header .page-actions { padding-right: 0; }
/* ---------- A LABELLED title-row action is 34px ----------
   `.content-header .btn` is deliberately squeezed to `padding: 3px 10px` so the fixed header
   stays inside the 52px `.content` clears for it, which leaves the header at 44.4px.  A LABELLED page
   action inherited that squeeze and came out 26px tall while every other control on the page is 34px.
   Restoring the app's standard 14px side padding and taking the vertical to 7px gives
   7 + 18 (the header's own line-height) + 7 + 2 = 34px — the `--control-height` value, reached through
   the padding because the header's line-height is 18px and not the content area's 20px.

   34px and NOT the 36px a record toolbar uses: that toolbar sits in the scrolling content and can be any
   height, these sit in the FIXED header, and 9+36+8+1 = 54px would push it past the 52px offset and over
   the top of the page.  34px lands the header at exactly 52px — flush, no overlap, and the largest value
   that fits.  If 36px is ever wanted here, the header's padding and `.content`'s offset have to move
   together.

   Nothing is needed for the two-line labels three of these pages carry: the bar is
   `align-items: flex-end`, so a taller label grows its own field upwards and every CONTROL on the row
   still shares one baseline. */
.content-header .page-actions .btn { padding: 7px 14px; height: var(--size-control-height); }
/* ---------- …and an ICON-ONLY one is a square ----------
   The page CTA is the ⊕ tile now, so the padding above — which exists to give a WORD its room — has to
   come back off, and the width has to match the height the rule above hands out. 34px and not
   `.btn-tile`'s own 36: same reason as the labelled case, the header is fixed and 36 would push it past
   the 52px `.content` offset.  (0,3,0) like the rule it corrects, so source order decides and this must
   stay below it.  Keyed to `.btn-tile`, NOT to `.page-actions .btn-cta`: the same container still holds
   labelled orange CTAs — the two uzávěrka actions on /finance/commission-balance-log/index and the ones
   promoteFormActions() writes at runtime — and blanking those is exactly what this must not do. */
.content-header .page-actions .btn-tile { width: var(--size-control-height); padding: 0; }
/* The `.page-actions` Pjax containers.  `yii\widgets\Pjax` renders a `<div>`, and modules.js reloads
   both by id after a phase change, so neither may be unwrapped in the markup.  As flex ITEMS they would
   swallow the group's gap between their own children and, when a state renders them empty, still claim a
   gap of their own on both sides.  `display: contents` takes the boxes out of the layout while leaving
   the elements — and therefore the pjax targets — in the DOM, so the buttons inside become direct flex
   children of `.page-actions` and the group spaces itself as if it were flat.
   `.btn-group` is exempt: it is a dropdown's positioned anchor, and unwrapping its box sends the
   absolute `.dropdown-menu` to the page corner (the Export ABO menu on /finance/collection/index). */
.content-header .page-actions > div:not(.btn-group) { display: contents; }
/* The bar hugs the viewport's right edge, so a left-aligned menu runs off screen. */
.content-header .page-actions .btn-group > .dropdown-menu { left: auto; right: 0; }
/* ---------- The title-row trio: ghost tiles with coloured ink ----------
   User ruling.  The trio was the last place in the app where a button's MEANING was carried by a pastel
   FILL (a Buttercream star, a Powder Blush delete) while every other button in the row says it with ink on
   white — two fills against three ghosts and one orange disc read as four button systems in 200px.  So the
   fills go and the ink stays: one ghost shell, four glyphs.

     ☆ / ★  outline star in brand green / filled star in gold   (`btn-star-off` / `btn-star`)
     ⤢ 🗑 ✕  merge and close in brand green, delete in RED ink
     💾      Uložit stays the ORANGE DISC

   BORDER: `--border-strong` on all four, INCLUDING delete.  The canonical `.btn-ghost-danger` uses the
   tinted `--status-danger-border`, which is right for a free-standing destructive button; here the four
   tiles are one row and a different border on one of them re-introduces exactly the "several systems"
   problem this removes.  The red ink plus the trash glyph is signal enough, and the HOVER does take the
   danger border.

   SPECIFICITY: `.record-actions` is what confines all of this to the title row, and it is also what
   keeps it ahead of its competitors — `.content-wrapper .btn.btn-star` (0,3,0) `!important` for the
   stars, the canonical `.btn-ghost-danger` (0,1,0) / (0,2,0) for the delete.  At (0,4,0) / (0,5,0),
   repeating the `!important`, these win outright, and the same classes on a grid row or in a dialog
   are untouched.  The delete keys on the CANONICAL class: `widgets/Button` is the only thing that
   renders a destructive button in here (the ten `<span class="record-actions">` in the views hold
   nothing hand-written, and the title-row promoter in scripts.js builds only an exit and a save), so
   there is no raw member for a legacy key to protect. */
.content-header .record-actions .btn.btn-star,
.content-header .record-actions .btn.btn-star-off,
.content-header .record-actions .btn.btn-ghost-danger {
  background: var(--surface-card) !important;
  border-color: var(--border-strong);
}
.content-header .record-actions .btn.btn-star { color: var(--brand-gold); }
.content-header .record-actions .btn.btn-star-off { color: var(--brand-green); }
.content-header .record-actions .btn.btn-ghost-danger { color: var(--status-danger); }
.content-header .record-actions .btn.btn-star:hover,
.content-header .record-actions .btn.btn-star:focus,
.content-header .record-actions .btn.btn-star:active {
  background: var(--surface-tint) !important;
  border-color: var(--brand-gold);
  color: var(--brand-gold-dark);
}
.content-header .record-actions .btn.btn-star-off:hover,
.content-header .record-actions .btn.btn-star-off:focus,
.content-header .record-actions .btn.btn-star-off:active {
  background: var(--brand-mint-bg) !important;
  border-color: var(--border-strong);
  color: var(--brand-green-dark);
}
.content-header .record-actions .btn.btn-ghost-danger:hover,
.content-header .record-actions .btn.btn-ghost-danger:focus,
.content-header .record-actions .btn.btn-ghost-danger:active {
  background: var(--status-danger-wash) !important;
  border-color: var(--status-danger);
  color: var(--status-danger-text);
}
/* ---------- The groups ⊕ carries a label when there are no groups ----------
   With chips beside it the bare ⊕ is unambiguous: it is the "one more of these" control at the end of a
   row of groups.  Alone on the title line it names nothing — there is no "Skupiny" anywhere else on the
   page for it to attach to.  The markup adds the label and this modifier only in that case, so a record
   WITH groups is byte-identical to before.

   Icon-only mode keeps the bare tile.  `font-size: 0` blanks the text node without a wrapper to hang
   `display: none` on and restores the glyph on the direct `.fa` child — the same technique the
   box-header buttons use, for the same reason (the label is a bare text node next to the `<i>`).
   `body.toolbar-icons` in front takes that selector to (0,4,1) against this modifier's (0,3,1), so it
   wins on count and the order of the two blocks is irrelevant. */
.content-header > h1 > .header-groups > .header-groups__add--labelled {
  width: auto;
  height: var(--size-chip-add-btn);
  padding: 0 12px;
  gap: 6px;
  font-size: 13px;
}
/* A bar whose submit is a NAMED action ("Odeslat", "Provést", "Použít") keeps its word and lands
   in `.page-actions`, the title row's labelled-CTA slot, rather than becoming a floppy disc.
   `.page-actions` and `.record-actions` each carry `margin-left: auto`, so on the one page that
   ends up with BOTH (Adresář: Použít + Zavřít) the free space was shared between them and the CTA
   stopped 500px short of its tile. Only the first of the pair should push. */
.content-header > h1 > .page-actions + .record-actions { margin-left: 0; }

/* ============================================================================================
   4b. AREAS / icon-only toolbar mode — the opt-in user preference `body.toolbar-icons`.
   ============================================================================================ */
/* The tile geometry is the designer-approved mock's:
     .content .btn[title] { width: 36px; padding-left: 0; padding-right: 0; text-align: center }
     .content .btn[title]:has(.caret) { width: 48px }
     .content .btn[title] .caret { margin-left: 2px }
     .btn-group .btn[title] { width: 36px }
   The mock's own markup had already dropped the button text, so it needed no text-hiding rule; our
   buttons keep their full markup, so the text is hidden here (`font-size: 0`) and restored on the icon
   only — the hidden label lives on in the `title` tooltip that LinkButton sets automatically.
   Scoped to the actual toolbar containers (the pull-left/pull-right action rows, the communication
   multi-button and the external-apps dropdown) rather than the mock's broad `.content`, so grid
   buttons, form buttons and the record-actions trio are never touched.
   `.dropdown-pull-left` MUST be its own container here: its markup sits inside `<p class="pull-left">`
   in the view, but a `<div>`/`<ul>` start tag closes an open `<p>`, so the parsed DOM has the dropdown
   as the paragraph's SIBLING and no `p.pull-left …` selector can reach it — the "Externí aplikace"
   toggle rendered with its label among icon tiles. */
body.toolbar-icons p.pull-left .btn[title],
body.toolbar-icons p.pull-right .btn[title],
body.toolbar-icons .communication .btn[title],
body.toolbar-icons .dropdown-pull-left .btn[title] {
  width: var(--size-toolbar-icon-tile);
  padding-left: 0;
  padding-right: 0;
  text-align: center;
  font-size: 0;
  /* `font-size: 0` EMPTIES the label, it does not remove it: the text run is still a flex item of the
     canonical button, so the ladder's `gap` reserves 6px for a box of zero width and the glyph loses
     half of it — measured -3px on every 36px tile, and -11px on the 48px caret tiles, where the gap
     is claimed on both sides of the invisible label.  No tile in this mode has two VISIBLE items:
     the caret keeps its own `margin-left: 2px` below. */
  gap: 0;
}
body.toolbar-icons p.pull-left .btn[title] > .fa,
body.toolbar-icons p.pull-right .btn[title] > .fa,
body.toolbar-icons .communication .btn[title] > .fa,
body.toolbar-icons .dropdown-pull-left .btn[title] > .fa {
  font-size: 13px; /* matches the current .btn text size */
}
body.toolbar-icons p.pull-left .btn[title]:has(.caret),
body.toolbar-icons p.pull-right .btn[title]:has(.caret),
body.toolbar-icons .communication .btn[title]:has(.caret),
body.toolbar-icons .dropdown-pull-left .btn[title]:has(.caret) {
  width: 48px;
}
body.toolbar-icons p.pull-left .btn[title] .caret,
body.toolbar-icons p.pull-right .btn[title] .caret,
body.toolbar-icons .communication .btn[title] .caret,
body.toolbar-icons .dropdown-pull-left .btn[title] .caret {
  margin-left: 2px;
}
/* Split "+" groups (e.g. Smlouvy/Obchodní případ/Pojistné události): the titled
   btn-entity segment gets the same fixed 36px.  The attached "+" segment is NOT in this selector — it
   is pinned to the same tile width by its own `.btn-group > .btn-entity + .btn-cta` block, in both
   toolbar modes, so it must not be sized from here as well. */
body.toolbar-icons p.pull-right .btn-group .btn[title] {
  width: var(--size-toolbar-icon-tile);
}
/* A toolbar button with NO icon must opt OUT of the icon treatment entirely.  The rules above key the
   text-hiding off `.btn[title]` alone and restore the glyph via `.btn[title] > .fa`, so a button whose
   markup carries no `<i>` lost its label and gained nothing back — a blank 36x36 tile.  The label is the
   only affordance those buttons have.  `:not(:has(> .fa))` lifts these selectors to (0,5,2) vs the
   block's (0,4,2), so they win on specificity, not order. */
body.toolbar-icons p.pull-left .btn[title]:not(:has(> .fa)),
body.toolbar-icons p.pull-right .btn[title]:not(:has(> .fa)),
body.toolbar-icons .communication .btn[title]:not(:has(> .fa)),
body.toolbar-icons .dropdown-pull-left .btn[title]:not(:has(> .fa)) {
  width: auto;
  padding-left: 10px;
  padding-right: 10px;
  font-size: 13px;
}
/* Icon-row rhythm.  The rows are inline flow, so the gaps were whatever whitespace text nodes and the
   widgets' inline `margin: 0 3px` happened to add up to — measured 3, 3.3, 3.9, 5, 6.1 and 8px between
   neighbours on one page — and the help "?" icons (a bare `<i>` sibling of the buttons,
   `vertical-align: baseline`) sat 1.5px above the buttons' optical centre.  Making each row the flex row
   the mock already is fixes both: whitespace-only text nodes generate no flex items, so the spacing
   becomes exactly one uniform gap, and `align-items: center` centres the "?" against the tiles.
   `flex: none` on the icons so a full row can never squeeze them.
   Scoped to `body.toolbar-icons` — the default text toolbar keeps its accepted look. */
body.toolbar-icons p.pull-left,
body.toolbar-icons p.pull-right,
body.toolbar-icons .communication > .pull-left {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--spacing-inline-gap);
}
/* CommunicationButton writes `margin-left/right: 3px` as inline styles on the wrapper and on
   every button inside it, which would stack with the gap — hence !important. The wrapper keeps
   a 6px right margin because it is a separate float, not a flex sibling of `p.pull-left`. */
body.toolbar-icons .communication .btn { margin-left: 0 !important; margin-right: 0 !important; }
body.toolbar-icons .communication > .pull-left { margin-left: 0 !important; margin-right: var(--spacing-inline-gap) !important; }
/* The dropdown container is a float sibling too (see the parser note above), with the legacy 3px
   from the `.dropdown-pull-left` base rule in 2h — the row's rhythm is 6. */
body.toolbar-icons .dropdown-pull-left { margin-right: var(--spacing-inline-gap); }
/* The gap BETWEEN float groups belongs to whichever group ends: the communication wrapper carries
   its own right margin above, `.export-actions` carries its own left margin — but a `p.pull-left`
   carries nothing, so a KOOP/external-app tile sat flush against the e-mail tile.  Conditional on
   `:has(.btn)` because the same paragraph exists EMPTY (just the hidden separator) on every contract
   without an external app, where a margin would shift the whole toolbar 6px off the row edge; the
   sibling condition keeps it off the paragraph before `.export-actions`, whose own margin already
   pays that gap. */
body.toolbar-icons p.pull-left:has(.btn):has(+ .communication, + p.pull-left) {
  margin-right: var(--spacing-inline-gap);
}
body.toolbar-icons p.pull-left > .help-item-icon,
body.toolbar-icons p.pull-right > .help-item-icon,
body.toolbar-icons .communication .help-item-icon {
  flex: none;
  margin-left: 0;
  line-height: 1;
}
/* Icon-only mode reaches the box headers: the box-header buttons and the sub-section create button
   become square icon tiles too.  This is strictly a `body.toolbar-icons` treatment — in text mode they
   stay icon + text — and production markup keeps the full label while the CSS hides it, exactly as the
   toolbar block above does.
   The tile takes the 28px rung, which is the exact height the same buttons measure WITH text, so the
   tile cannot grow the header by 2px when icon mode is on.
   The font-size restore deliberately matches the DIRECT `.fa` child only: the composite glyph below
   sizes itself and its parts, and a descendant selector here would outrank it.

   THE THIRD SELECTOR is the same component in a second place.  A collapsible
   sub-section puts its OWN `.box-header-buttons` inside the `<details>` body rather than in a
   `.box-header` — two of them exist, both holding the "Odstranit označené" bulk delete of a
   Požadované dokumenty grid (subject/form/_documents.php:107, incident/form/_documents.php:98) — and
   the first selector's `.box > .box-header` chain cannot reach them, while the second matches only
   the `<summary>` (their ⊕ IS in the summary, which is why the ⊕ collapsed and the trash beside it did
   not).  So one box header on an all-icon page rendered a full-width "🗑 Odstranit označené" text
   button among 28px tiles.  Keyed on `.box-header-buttons` under `details.form-section-collapse`
   rather than on the button, so any further control that lands in a sub-section header collapses too. */
body.toolbar-icons .box > .box-header .box-header-buttons .btn[title],
body.toolbar-icons details.form-section-collapse > .box-header-buttons .btn[title],
body.toolbar-icons .form-section-label--toggle .btn[title] {
  width: var(--size-boxheader-icon-btn);
  height: var(--size-boxheader-icon-btn);
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 0;
  /* Same reason as the toolbar block: the ladder's gap pushes every 28px entity ⊕ 3px left of its
     tile. */
  gap: 0;
}
body.toolbar-icons .box > .box-header .box-header-buttons .btn[title] > .fa,
body.toolbar-icons details.form-section-collapse > .box-header-buttons .btn[title] > .fa,
body.toolbar-icons .form-section-label--toggle .btn[title] > .fa {
  font-size: 13px;
}
/* Same opt-out as the toolbar rows: a button with no glyph at all would become an empty tile, so it
   keeps its label.  `:not(:has(.fa))` adds one class level, so it wins on specificity. */
body.toolbar-icons .box > .box-header .box-header-buttons .btn[title]:not(:has(.fa)),
body.toolbar-icons details.form-section-collapse > .box-header-buttons .btn[title]:not(:has(.fa)),
body.toolbar-icons .form-section-label--toggle .btn[title]:not(:has(.fa)) {
  width: auto;
  padding: 3px 6px;
  font-size: 13px;
}
/* The icon-mode-only glyph carried by buttons that the text mode renders as a label alone ("Vložit do
   obálky", "Spojit dokumenty").  Hidden by default so text mode is unaffected.  The envelope + arrow
   composite is knocked out of the button fill so the two glyphs stay legible where they overlap. */
.btn-icon-glyph { display: none; }
body.toolbar-icons .btn-icon-glyph {
  position: relative;
  display: inline-flex;
  font-size: 12px;
  line-height: 1;
}
body.toolbar-icons .btn-icon-glyph > .fa-arrow-down {
  position: absolute;
  top: -7px;
  left: 50%;
  transform: translateX(-50%);
  font-size: 8px;
  text-shadow: -1.5px 0 var(--surface-card), 1.5px 0 var(--surface-card), 0 1.5px var(--surface-card), 0 -1.5px var(--surface-card);
}
/* ---------- One row, one height: the icon-mode tile is a SQUARE ----------
   Two things had to be pinned here.  (1) With `font-size: 0` the button's line box comes from whatever
   is inside it: an `<i>` at 13px produced a 21px content box (37px total) while an empty one fell back to
   the 20px line-height (36px), so icon buttons sat 1px taller than the icon-less and the untitled "+"
   segments — hence the explicit height and the geometric centring instead of inline layout.  (2) That
   height used to be expressed as `var(--control-height)`, and when the control token moved 36 -> 34 every
   icon-mode toolbar tile quietly became a 36x34 RECTANGLE.

   So the height is `--size-toolbar-icon-tile` (the designer's own toolbar spec) and NOT the control
   token, which the form controls need at 34.  `p.` in the selectors is deliberate but has a known gap: a
   toolbar group that sits in a `div.pull-left` (Pjax cannot wrap a `<p>`) is not reached, which is why
   the commission balance-log toolbar has a rule of its own. */
body.toolbar-icons p.pull-left .btn,
body.toolbar-icons p.pull-right .btn,
body.toolbar-icons .communication .btn {
  padding-top: 0;
  padding-bottom: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  height: var(--size-toolbar-icon-tile);
}
/* ---------- The Export group joins the icon-only toolbar ----------
   The one toolbar control the rules above can NEVER reach, because it can never live where they look.
   They are scoped to the toolbar PARAGRAPHS, and ExportButton emits a `.btn-group` holding a
   `<ul class="dropdown-menu">` — and a `<ul>` start tag CLOSES an open `<p>`, no matter how deeply it is
   nested inside it, so the group is always a SIBLING of the paragraph in the parsed DOM.  (The same rule
   is why "PDF formuláře" used to escape the paragraph on contract update: a `<div class="communication">`
   sat between them.  That one is fixed in the markup, because the div genuinely belonged outside.)

   So the group is matched by its own class instead.  The declarations are the toolbar block's own,
   restated for this selector rather than added to its list, so the paragraph rules keep saying exactly
   what they say today.  The 6px margin is the row gap — the group is not a flex item of the row, so it
   has to state it itself, and the widget writes 3px inline on both sides, hence the `!important` pair. */
body.toolbar-icons .export-actions > .btn[title] {
  width: 48px;
  height: var(--size-toolbar-icon-tile);
  padding-left: 0;
  padding-right: 0;
  text-align: center;
  font-size: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* This tile carries a caret as well as the blanked label, so the ladder's gap is claimed twice —
     the same -11px the toolbar block above measures. */
  gap: 0;
}
body.toolbar-icons .export-actions > .btn[title] > .fa { font-size: 13px; }
body.toolbar-icons .export-actions > .btn[title] .caret { margin-left: 2px; }
body.toolbar-icons .export-actions { margin-left: var(--spacing-inline-gap) !important; margin-right: 0 !important; }
body.toolbar-icons .content-header > h1 > .header-groups > .header-groups__add--labelled {
  width: var(--size-chip-add-btn);
  padding: 0;
  gap: 0;
  font-size: 0;
}
body.toolbar-icons .content-header > h1 > .header-groups > .header-groups__add--labelled > .fa {
  font-size: 11px;
}
/* ---------- The box-header ⊕▾ needs room for its caret ----------
   `body.toolbar-icons` only.  The rule above squares every titled box-header button to 28px with
   `padding: 0` and `font-size: 0`, which is right for a single-glyph ⊕.  One box header's button is a
   ButtonDropdown, so it renders TWO children — `.fa-plus-circle` and `.caret` — into the same 28px, and
   the two touch.  The toolbar block twenty lines up already ruled on exactly this shape: a titled icon
   button carrying a caret goes 36 -> 48px, i.e. +12px for the caret and its 2px gap.  The same +12 on
   the 28px header rung is 40px.

   40 is the same NUMBER as --size-input-addon and deliberately NOT the same TOKEN: one is the icon-mode
   header tile with a caret, the other the addon column, and they answer to different rungs (28 and 34).
   `:has(.caret)` adds one level over the base selector, so it wins without an `!important`.

   `:not(.btn-actions-toggle)` on the first selector is what keeps the create-menu OUT of the +12px.
   Both box-header menus draw a ⊕ over a caret, so the mark is not what separates them: the
   create-menu's caret is DISPLAY:NONE (the block below), and a tile widened to make room for a caret
   that is not painted would be a 40x28 rectangle holding a single glyph.  The other two selectors
   need no exclusion — no `.btn-actions-toggle` has ever lived in a collapsible sub-section's
   header. */
body.toolbar-icons .box > .box-header .box-header-buttons .btn[title]:has(.caret):not(.btn-actions-toggle),
body.toolbar-icons details.form-section-collapse > .box-header-buttons .btn[title]:has(.caret),
body.toolbar-icons .form-section-label--toggle .btn[title]:has(.caret) {
  width: 40px;
}
body.toolbar-icons .box > .box-header .box-header-buttons .btn[title] .caret,
body.toolbar-icons details.form-section-collapse > .box-header-buttons .btn[title] .caret,
body.toolbar-icons .form-section-label--toggle .btn[title] .caret {
  margin-left: 2px;
}
/* ---------- The dropdown tiles that collapse to one glyph ----------
   Three controls emit ActionColumn::ACTIONS_TOGGLE_CLASS, so this is the one place their shape is
   stated: the grid header's Akce (app\components\web\ActionColumn, the shared header of 123 grids),
   the report grid's hand-duplicated copy (ReportGridView) and the create-menu
   (BsDialogCreateListLinks).  THE CLASS IS
   THE SHAPE, NOT THE GLYPH — the first two open a bulk-action list and carry
   `fa-angle-double-down`; the third creates a record and carries `fa-plus-circle`, and this block
   must keep saying nothing about which.

   THE CARET HAS TO BE HIDDEN EXPLICITLY.  `font-size: 0` empties a text label, but a bootstrap caret is
   a BORDER TRIANGLE — its size comes from `border-width`, not from the font, so `font-size: 0`
   cannot reach it.  What the hidden caret
   costs differs per glyph and is affordable either way: on the Akce toggles the double angle already
   says "this opens down", and on the create-menu the collapsed tile becomes a bare ⊕ — which is what
   every other create affordance in that box header already is, so it matches its row rather than
   announcing a menu.

   28px, not the toolbar's 36: each of the three sits among 28px controls — the grid-row action tiles
   directly under the header cell, and the box-header buttons the create-menu shares a row with.  Inside
   a grid `.grid-view .action-column .btn` (0,3,0) states the same box from the same token and wins on
   specificity, so the two cannot drift apart. */
body.toolbar-icons .btn-actions-toggle {
  width: var(--size-grid-action-btn);
  height: var(--size-grid-action-btn);
  padding: 0;
  text-align: center;
  font-size: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* Same reason as the toolbar block: the ladder's gap reserves space for a box of zero width and the
     glyph loses half of it. */
  gap: 0;
}
/* 13px is the fallback rung, and it is NOT what the grid header measures: `.grid-view .action-column
   .btn > .fa` (0,3,1) says 14px there, which is the size of the row-action glyphs directly below the
   toggle — so the header glyph matches its column, and only the report grid's copy, which sits in no
   such column, takes the 13px. */
body.toolbar-icons .btn-actions-toggle > .fa { font-size: 13px; }
body.toolbar-icons .btn-actions-toggle .caret { display: none; }
/* ---------- The filter bar's action row joins icon-only mode ----------
   The toolbar block above cannot reach this row: its selectors are scoped to `p.pull-left`,
   `p.pull-right` and `.communication`, and the filter card's actions live in
   `.list-filter-bar__actions`, a flex row of its own.

   THE TILE IS THE CONTROL RUNG (34px), NOT THE TOOLBAR'S 36px. This row sits on the input line and
   the clear-filter tile beside it is `--control-height` square, so a 36px
   sibling would be a 36x34 rectangle against it — the same mismatch the toolbar block documents two
   screens up. `height` is left to `.list-filter-bar__actions .btn`, which already says 34.

   `.btn-clear-filter` is EXCLUDED rather than restated: it is already icon-only and already square,
   and its funnel+cross is a composite inside a `<span>` wrapper, so it has no direct `> .fa` child
   and would have taken the icon-less opt-out below and lost its width.

   The `[title]` gate is the opt-in, and it is doing real work here: the two report filter bars
   submit "Odeslat", a NAMED action, and the app's rule is that a named action keeps its word. They
   are the only two of the 21 bars with no `title`, so they alone stay labelled. */
body.toolbar-icons .list-filter-bar__actions .btn[title]:not(.btn-clear-filter) {
  width: var(--control-height);
  padding-left: 0;
  padding-right: 0;
  text-align: center;
  font-size: 0;
  /* Same reason as the toolbar block: the ladder's gap reserves space beside a box of zero width and
     the glyph loses half of it. */
  gap: 0;
}
body.toolbar-icons .list-filter-bar__actions .btn[title]:not(.btn-clear-filter) > .fa {
  font-size: 13px;
}
/* A titled action with no glyph at all must keep its label — a blank 34px tile has no affordance
   left. `:not(:has(> .fa))` adds a level over the base selector, so it wins on specificity. */
body.toolbar-icons .list-filter-bar__actions .btn[title]:not(.btn-clear-filter):not(:has(> .fa)) {
  width: auto;
  padding-left: 10px;
  padding-right: 10px;
  font-size: 13px;
}

/* ============================================================================================
   4c. AREAS / entity forms
   ============================================================================================ */
/* ---------- Email compose form (message/email/_form.php) ---------- */
div.email-form > div.box {
    margin-bottom: 0;
}
/* The show-more / show-less rows hang their link on the form's right edge; modules.js swaps the
   two blocks. */
.email-form .show-more, .email-form .show-less {
    display: block;
    position: relative;
    padding-bottom: 15px;
    margin-top: -5px;
}
.email-form .show-more a, .email-form .show-less a {
    right: 0px;
    position: absolute;
}
/* The client form's two mutually exclusive branches (name vs. personal number,
   client/form/_primary.php and client/create.php): the inactive branch stays in the DOM for the
   blanket `$('#has-name input')` toggles but contributes nothing. */
.client-create #has-name.disabled, .client-create #has-id-number.disabled, .client-update #has-name.disabled, .client-update #has-id-number.disabled {
    display: none;
}
/* The select2 "create new option value" row of the contract risc box
   (ContractRiscOptionValueCreateButton). */
a.contract-risc-option-value-create-record {
    background-color: var(--surface-card);
    color: var(--ok-green) !important;
}
/* The precept amount fields are computed by scripts.js; native spinners would invite edits the
   recalculation overwrites. */
#insurance-company-amounts-table input[type=number]::-webkit-outer-spin-button,
#insurance-company-amounts-table input[type=number]::-webkit-inner-spin-button,
#precept-fee::-webkit-outer-spin-button,
#precept-fee::-webkit-inner-spin-button,
#precept-fee_percentage::-webkit-outer-spin-button,
#precept-fee_percentage::-webkit-inner-spin-button {
    -webkit-appearance: none;
    margin: 0;
}
#insurance-company-amounts-table input[type=number],
#precept-fee,
#precept-fee_percentage {
    -moz-appearance: textfield;
}
/* The precept form's fee row (finance/precept/_form.php): the grouped columns share one visual
   cell, so only the row's outer edges keep the bootstrap gutter. */
.inline-grouped .col-md-2 {
    padding: 0 !IMPORTANT;
}
.inline-grouped .col-md-2:first-child {
    padding-left: 15px !IMPORTANT;
}
.inline-grouped .col-md-2:last-child {
    padding-right: 15px !IMPORTANT;
}
/* The JSON-forms box-header fullscreen toggle (contract/subject/incident update pages). */
#fullscreen-container {
    cursor: pointer;
    display: inline-block;
}
/* The JSON form widget's native submit row is driven from web/js/modules.js ('.json-form-from
   .buttons input[name="action-save"]'), so the markup must stay in the DOM while contributing no
   box. */
.json-form-from .buttons {
    visibility: hidden;
    height: 0;
    width: 0;
    padding: 0;
    margin: 0;
    border: none;
}
.json-form-from .buttons input,  .json-form-from .buttons a{
    visibility: hidden;
    height: 0;
    width: 0;
    padding: 0;
    margin: 0;
    border: none;
}
/* The JSON form dialog (dialogId 'json-form', opened via ajaxEditForm in the entity update views)
   joins its header and body without the modal seam. */
#json-form-bsdialog .modal-header {
    padding-bottom: 0 !important;
    border: 0 !important;
}
#json-form-bsdialog .box-body {
    padding-top: 0 !important;
}
/* The JSON form generator widget's own box (json-form-generator.php) sits inside the form card. */
.form-box {
    border-top: 0 !important;
}
.form-box-body {
    padding-top: 0 !important;
}
.dropdown-light .dropdown-btn-title-light{
    background-color: white !important;
    border-color: #ddd;
    border-radius: 3px;
    color: var(--ok-green) !important;
}
/* The qualifier is the paint class the toggle carries, and it moves with it: the only element that
   ever gets `.dropdown-btn-title-light` is BsDialogCreateListLinks' toggle, which writes one literal
   class string. The three states exist to beat the button vocabulary with `!important` — the base
   rule above states the same border and radius WITHOUT it, so keyed to a class the button no longer
   has, `.btn-ghost:hover` (0,2,0) takes the border and
   `.box.box-solid > .box-header .btn.btn-ghost` (0,5,0) takes the radius.
   KEPT WITH NO CALL SITE TODAY: the widget renders only when a `json_form_generator` row is scoped to
   the record's entity and account, and no such row exists in the development database, so nothing here
   can be measured. The trio's specificity is what it must stay at; it does not out-rank the box-header
   block, so the toggle opens with `--border-strong` and a 6px radius rather than the 3px this block
   asks for — that conflict is between two designed rules and belongs to whoever gives the widget a
   call site. */
.dropdown-light .dropdown-btn-title-light.btn-ghost:focus,
.dropdown-light .dropdown-btn-title-light.btn-ghost:active,
.dropdown-light .dropdown-btn-title-light.btn-ghost:hover {
    background-color: white !important;
    border-color: #ddd !important;
    border-radius: 3px !important;
}
.dropdown-light .dropdown-btn-item-light {
    color: var(--ok-green) !important;
    margin-left: 0;
}
/* The three colour pickers' sample buttons are fixed 50px tiles — wide enough to read as a colour,
   never wide enough to read as a labelled button. */
.field-comment-color button, .field-risc-color button, .field-alert-type button {
    width: 50px;
}
/* ---------- Colour-swatch pickers ----------
   Three forms let the user pick a colour BY NAME — Poznámka's Barva, the risc form's Barva and the
   console alert's Typ — and all three build the swatch's class out of the stored value
   (`btn btn-<value>`).  So the sample took its paint from whatever the app happened to say about that
   bootstrap contextual class, which is a dependency on a decision that has nothing to do with it, and
   it broke twice: `success` turned ORANGE once `.content-wrapper .btn-success` became the CTA fill,
   and `danger` turned pale pink once `.content-wrapper .btn-danger` became the quiet destructive wash.
   A swatch is a colour SAMPLE, not a button role, so the palette is stated here and the sample is out
   of that cascade for good.

   `.color-swatches` is written by the three views on the picker's `.btn-group`.  The doubled `.btn`
   lifts each selector to (0,3,0) / (0,4,0), which clears `.content-wrapper .btn-<name>` (0,2,0) and
   its `:hover` (0,3,0) on specificity rather than on where in this file they sit — the point of the
   whole block is that a later edit to the button families cannot reach these four again.  The pointer
   states repeat the resting fill because a sample must not change colour when you point at it.

   ALL FOUR ARE THE ROW TINT, and that is the whole rule: a picker must show the colour the thing it
   paints will BE.  The names read as bootstrap's semantic set, but the surface they actually paint is
   a GRID ROW — `Comment::$color` and `Alert::$type` go straight onto the <tr> as its class
   (comment/_grid.php rowOptions, alert/_grid.php rowOptions) — and a row tint is pastel: a saturated
   red chip in the dialog beside a pale salmon row in the grid means only one of the two is the colour
   the user chose.  The four `--row-tint-*` tokens are shared with the row rules in 2f, so the two
   surfaces cannot drift apart.

   Ink follows the row too, so the `fa-circle` selection dot is the same ink the row's text is:
   every row state reads in --text-body, and the four swatches say so.
   BORDERS are the one thing the swatch has that the row does not, and it needs them: four pale fills
   side by side on a white form read as one washed-out block without an edge, and the picker is 50px
   wide per sample with no other separator.  Each takes its own family's border token, so the edge is
   always the same hue as the fill and never reads as a second colour. */
.color-swatches .btn.btn-success,
.color-swatches .btn.btn-success:is(:hover, :focus, :active) {
  background: var(--row-tint-success) !important; border-color: var(--panel-success-border); color: var(--text-body);
}
.color-swatches .btn.btn-warning,
.color-swatches .btn.btn-warning:is(:hover, :focus, :active) {
  background: var(--row-tint-warning) !important; border-color: var(--panel-warning-border); color: var(--text-body);
}
.color-swatches .btn.btn-danger,
.color-swatches .btn.btn-danger:is(:hover, :focus, :active) {
  background: var(--row-tint-danger) !important; border-color: var(--status-danger-border); color: var(--text-body);
}
.color-swatches .btn.btn-info,
.color-swatches .btn.btn-info:is(:hover, :focus, :active) {
  background: var(--row-tint-info) !important; border-color: var(--panel-info-border); color: var(--text-body);
}
/* The selection marker is the `fa-circle` the view swaps in — and the DOT ALONE is the marker: a
   border-and-ring treatment on top of it reads as overkill next to the samples.
   Bootstrap's `.btn.active` inset shadow is still zeroed (on a colour picker it reads as a fifth
   colour, not a state), and the dot keeps the 15px size so it carries the state by itself on the
   pastel fills. */
.color-swatches .btn.active,
.color-swatches .btn.active:is(:hover, :focus, :active) {
  box-shadow: none;
}
.color-swatches .btn.active > .fa-circle { font-size: 15px; }
/* ---------- `.color-swatches--data` — the picker whose palette IS its data ----------
   One picker in the app stores a colour that no CSS class can describe: the risc form's Barva writes
   one of THIRTEEN names from `Risc::getColors()`, a name => hex map, and the value's only rendered
   surface is the subject export's spreadsheet header, which is filled from `Risc::getColorHex()`.
   Four of the thirteen names collide with bootstrap's contextual set and so accidentally had paint;
   the other nine had none and rendered as identical blank buttonface-grey boxes, nine choices a user
   could not tell apart.

   The fix is one rule and no palette: the view hands each button its own hex as `--swatch-fill` and
   this reads it, so the model stays the single source of truth and CSS never holds a copy that can
   drift.  The modifier class is on the group, so the four semantic rules above — which paint the
   comment and alert pickers in the grid-row tints those two values actually produce — do not reach
   this picker, and nothing here reaches those.  (0,2,0) beats nothing in particular: with the paint
   class dropped from the markup, the competition for these buttons' fill is the UA's own buttonface,
   which is what made the nine grey in the first place.

   The selection dot is white with a dark halo rather than an ink chosen per fill: the thirteen hexes
   run from `#00FF00` to `#005046`-dark, no single ink reads on all of them, and computing one per
   swatch would put a luminance calculation in a view.  The halo reads on both ends.  The `.active`
   ring above still applies and still works here — its inner stop is `--surface-card`, so the dark
   outer ring is separated from the fill whatever the fill is. */
.color-swatches--data .btn,
.color-swatches--data .btn:is(:hover, :focus, :active) {
  background: var(--swatch-fill);
  border-color: var(--border-strong);
  color: var(--text-body);
}
.color-swatches--data .btn > .fa-circle {
  color: var(--surface-card);
  text-shadow: 0 0 2px rgba(31, 42, 40, 0.9);
}
/* Field-label variant (e.g. Cizinec): label on top, box below — opt in with
   'checkbox-stack' on the field's form-group.  The `.checkbox`-less selector covers plain
   yii\widgets\ActiveForm fields, which render no `.checkbox` div. */
.checkbox-stack label:has(> input[type="checkbox"]),
.checkbox-stack .checkbox label:has(> input[type="checkbox"]) {
  flex-direction: column-reverse;
  align-items: flex-start;
  gap: 6px;
}
/* ---------- [subjekt-grid] ----------
   The Subjekt section of the client update form.  The company and person branches place the same fields
   in different columns and a different order, and the three always-visible fields (RČ/IČO, Typ klienta,
   Cizinec) are single SHARED instances — so one float-based source order cannot render both branches
   without leaving a hole in row 1.  The section is a 12-column grid instead and each field container is
   positioned per branch.
   Geometry is Bootstrap's, deliberately: the container carries `.row`'s -15px side margins and every item
   carries `.col-md-*`'s 15px side padding, so a span of N columns is pixel-identical to `.col-md-N` and
   the field rhythm matches the neighbouring sections. */
.subjekt-grid {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  margin-left: -15px;
  margin-right: -15px;
}
.subjekt-grid .sj-col {
  padding-left: 15px;
  padding-right: 15px;
  min-width: 0;
}
/* The #has-name / #has-id-number toggle wrappers must not form a box of their own, otherwise
   their children cannot be placed in the parent grid. Kept deliberately below the specificity of
   4c's `.client-update #has-name.disabled { display: none }` (id + 2 classes) so hiding a
   branch still wins over this rule. */
.subjekt-grid .sj-branch {
  display: contents;
}
/* Placement uses a definite row plus a span and an explicit order — never a definite column — so
   auto-placement packs each row left to right in `order` sequence. A branch whose optional field
   is absent (no Právní forma when the client type does not load ARES) then closes up instead of
   leaving a hole. */
.subjekt-grid.is-person .sj-id-number    { grid-row: 1; grid-column: span 5; order: 1; }
.subjekt-grid.is-person .sj-client-type  { grid-row: 1; grid-column: span 5; order: 2; }
.subjekt-grid.is-person .sj-foreign      { grid-row: 1; grid-column: span 2; order: 3; }
.subjekt-grid.is-person .sj-pre-nominal  { grid-row: 2; grid-column: span 2; order: 4; }
.subjekt-grid.is-person .sj-lastname     { grid-row: 2; grid-column: span 4; order: 5; }
.subjekt-grid.is-person .sj-firstname    { grid-row: 2; grid-column: span 4; order: 6; }
.subjekt-grid.is-person .sj-post-nominal { grid-row: 2; grid-column: span 2; order: 7; }
.subjekt-grid.is-company .sj-id-number   { grid-row: 1; grid-column: span 3; order: 1; }
.subjekt-grid.is-company .sj-name        { grid-row: 1; grid-column: span 6; order: 2; }
.subjekt-grid.is-company .sj-client-type { grid-row: 1; grid-column: span 3; order: 3; }
.subjekt-grid.is-company .sj-legal-form  { grid-row: 2; grid-column: span 5; order: 4; }
.subjekt-grid.is-company .sj-file-sign   { grid-row: 2; grid-column: span 5; order: 5; }
/* Cizinec is the row's right-edge checkbox in both branches, so pin it there: if Právní forma is
   absent the two text fields close up on the left and the checkbox stays put. */
.subjekt-grid.is-company .sj-foreign     { grid-row: 2; grid-column: 11 / span 2; order: 6; }
/* ---------- The unified "add to group" popup ----------
   NO min-height, and that is the whole rule: the dialog is as tall as the field, the two checkboxes and
   the footer, and nothing else.  Reserving room for the open select2 dropdown is not needed: the widget
   parents its dropdown to `#<dialogId>-bsdialog`, the dialog ROOT (pluginOptions.dropdownParent in
   _assign-form.php), not to `.modal-body` whose `overflow: auto` would have clipped it, so the list is
   free to hang past the dialog's own bottom edge over the backdrop like every other dropdown in the
   app.  The height is content-driven: `BsDialogForm` is given `height => 'auto'` for this dialog, so no
   max-height caps it either. */
/* The one select2 that both picks an existing group and creates a new one.  Existing groups carry a
   mint fa-tag; the "create new" option is the highlighted last row.  The markup comes from the widget's
   templateResult, so these are plain descendant rules — no specificity fight with the select2
   stylesheets, which style the <li> and not its contents.  Both options share the same glyph gutter so
   the labels line up whichever kind of row the eye lands on. */
.group-option > .fa,
.group-option-new > .fa {
  width: 16px;
  margin-right: 6px;
  text-align: center;
}
.group-option > .fa-tag { color: var(--ok-mint); }
/* The create row reads as an action, not as one more group in the list: green label, mint
   tint, and a rule separating it from the groups above it. `display: block` with negative
   margins that cancel the option's own padding (measured 7px 12px live, krajee-bs3 theme)
   lets the tint and the divider bleed to the full row — an inline span would leave a gap
   on every side. The divider is --border-strong rather than --border-default so it stays
   visible against the mint that select2 paints the highlighted row with. */
.group-option-new {
  display: block;
  margin: -7px -12px;
  padding: 7px 12px;
  border-top: 1px solid var(--border-strong);
  background-color: var(--surface-tint);
  color: var(--ok-green);
  font-weight: 500;
}
.group-option-new > .fa-plus-circle { color: var(--ok-green); }
/* Highlighted, select2 already paints the row mint with green text — which is the mock's
   treatment for this option anyway — so step out of the way and only keep the divider. */
.select2-results__option--highlighted .group-option-new {
  background-color: transparent;
  color: inherit;
}
.select2-results__option--highlighted .group-option-new > .fa-plus-circle,
.select2-results__option--highlighted .group-option > .fa-tag { color: inherit; }
/* The hint under the field collided with the select: kartik's activeform.css pulls
   `.kv-form-bs3 .hint-block` up by 5px, which suits a plain input's slack but lands the text on top
   of select2's block.  That stylesheet is injected into the dialog body at render time, i.e. AFTER
   this file, so an equal-specificity (0,2,0) rule would lose — hence the duplicate-class trick the
   disabled-button block also uses.  Scoped to this popup so hints elsewhere keep the vendor
   spacing. */
.group-assign-form.group-assign-form .hint-block { margin-top: 4px; }
/* Checkbox rows: box on the left, 10px gap, label weight normal.  The app's canonical BS3/Yii markup
   nests the input inside the label, so the LABEL is the flex container.  The `:has()` is not decoration —
   it has to be carried over from the base `.checkbox label:has(> input[type="checkbox"])` rule whose gap
   this overrides: `:has()` lends its argument's weight, putting that rule at (0,2,2), which a plain
   `.group-assign-form .checkbox label` (0,2,1) would lose to. */
.group-assign-form .checkbox label:has(> input[type="checkbox"]) {
  display: flex;
  align-items: center;
  gap: 10px;
  font-weight: 400;
}
.group-assign-form .checkbox label input[type="checkbox"] { margin: 0; flex: none; }
/* A DISABLED row has to look disabled, and here it is load-bearing: the notification box renders from
   the moment the popup opens, so before a group is picked the box is inert and unchecked, and an inert
   unchecked box that reads as a live one says "this group will not notify"
   about a group nobody has chosen yet.  The box itself already differs (a 7-unit darker fill and
   `cursor: not-allowed`, both from the base control rules) — too little to carry the meaning on its
   own, so the label's ink goes muted with it.  `:has()` keys it off the input's own state, so it
   follows the JS that flips `disabled` with no second class to keep in sync. */
.group-assign-form .checkbox label:has(> input[type="checkbox"]:disabled) { color: var(--text-muted); }
.group-assign-notify > .form-group:last-child { margin-bottom: 0; }
/* ---------- The Rizika fold-all tile ----------
   The tile reports the cards' current aggregate, so the button ships ALL THREE drawn glyphs and
   `data-fold` reveals one.  Visibility, not markup: an innerHTML swap would rebuild the tile's only
   child on every fold, and the three glyphs are cut on one canvas precisely so the mark cannot move
   inside the 28px tile (see Icon::createFoldStateExpandedIcon()).
   The reveal rules add the attribute to the hide rule's specificity, so they win on weight rather
   than on file order — the one thing an AJAX-registered sheet could otherwise take from them. */
.risk-cards-toggle-all > .risk-fold-glyph { display: none; }
.risk-cards-toggle-all[data-fold="expanded"] > .risk-fold-glyph--expanded,
.risk-cards-toggle-all[data-fold="partial"] > .risk-fold-glyph--partial,
.risk-cards-toggle-all[data-fold="folded"] > .risk-fold-glyph--folded {
  display: block;
}
/* Rizika: one card per risc instead of a stack of nested boxes. */
.risk-card {
  border: 1px solid var(--border-default);
  border-left: 4px solid var(--status-risk-edge);
  border-radius: var(--radius-control);
  margin-bottom: 10px;
  background: var(--surface-card);
}
.risk-card__head {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 14px;
  background: var(--brand-mint-bg);
  border-radius: calc(var(--radius-control) - 1px) calc(var(--radius-control) - 1px) 0 0;
  cursor: pointer;
}
.risk-card__name {
  color: var(--ok-green);
  font-size: 14px;
}
/* The fold affordance is the box family's: a chevron before the name that rotates with the state
   (the old +/− tile read as a different component). Same anatomy as the .box-title caret in 2e. */
.risk-card__name::before {
  content: "\f054";
  font: normal normal normal 11px/1 FontAwesome;
  display: inline-block;
  margin-right: 10px;
  color: var(--text-muted);
  transform: rotate(90deg);
  transition: transform .15s ease;
}
.risk-card--collapsed .risk-card__name::before { transform: none; }
/* Unpainted but in the DOM, like the box family's .btn-box-tool: it is the element the fold JS
   listens on and carries the state's title/aria — head clicks forward to it. */
.risk-card__toggle { display: none; }
/* Nine columns: Specifikace / Pojistná částka / Limit plnění / Čerpání z limitu / Spoluúčast / Počátek /
   Konec / Sazba / Sleva.  `align-items: end` keeps the inputs on one baseline when a label wraps to two
   lines.  Roční / Lhůtní pojistné are two extra cells that only exist in "sum of riscs" mode; while
   modules.js keeps them `.hidden` they are display:none and take no grid cell, so the common case is
   exactly nine. */
.risk-grid {
  display: grid;
  grid-template-columns: 1.2fr 1fr 1fr 1fr 0.8fr 1fr 1fr 0.7fr 0.8fr;
  gap: 10px;
  padding: 12px 14px;
  align-items: end;
}
.risk-grid > .form-group {
  margin-bottom: 0;
}
/* Hidden value carriers (lock_version, risc_ids_hidden, the premium *_hidden pair) sit outside
   the grid — zero height, but their .form-group margin would still open a gap above it. */
.subject-risc-form > .form-group {
  margin-bottom: 0;
}
/* Inactive riscs ("Neaktivní rizika"): slim row, GOLD edge, field grid collapsed.  `--off` is the
   server's verdict (SubjectRisc::$is_active) and never changes client-side; `--collapsed` is the +/−
   toggle, and on first render only the EMPTY inactive riscs carry it — one that holds data or has an
   explicit `risc`.`order` opens expanded.
   User ruling: a not-yet-insured risk is a STATE, not an error, so the whole `--off` treatment sits in
   the warning family rather than the danger one. */
.risk-off-heading {
  border-bottom: 0;
  margin: 18px 0 10px;
}
.risk-card--off {
  border-color: var(--panel-warning-border);
  border-left-color: var(--ok-gold);
  margin-bottom: 8px;
  background: var(--panel-warning-bg);
}
.risk-card--off .risk-card__head {
  background: var(--panel-warning-bg);
  border-radius: calc(var(--radius-control) - 1px);
}
.risk-card--off .risk-card__name {
  color: var(--panel-warning-text);
  font-size: 13px;
  font-weight: 600;
}
.risk-card--collapsed > .risk-card__body {
  display: none;
}
/* …unless the collapsed risc failed validation: its messages must stay reachable. A risc with
   data is normally active (= expanded) after a failed save, but rate/discount can be rejected
   while the risc has no start date, which would otherwise hide the error. */
.risk-card--collapsed:has(.has-error) > .risk-card__body {
  display: block;
}
/* ---------- Subject "Neaktivní rizika": compact pills ----------
   The unarranged riscs were slim full-width bars; compacted into pills that wrap across the row, a
   vehicle with a dozen of them costs three lines instead of twelve.  Nothing inside the cards moved, so
   every field name and the POST are unchanged.

   The pill/card distinction is carried entirely by `.risk-card--collapsed`, which is exactly the class the
   +/− toggle adds and removes.  That is what keeps the expand behaviour: the click handler is untouched,
   and dropping the class turns the pill back into the full card on a row of its own, wide enough for the
   nine-column field grid. */
.risk-off-list {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: flex-start;
}
/* Default for anything in the list: a full row, i.e. the card.  `flex-basis: 100%` is also what forces an
   expanded risc onto its own flex line.  The margin goes because the row's `gap` now does the spacing. */
.risk-off-list > .risk-card {
  flex: 1 0 100%;
  margin-bottom: 0;
}
/* …unless it is collapsed, in which case it shrinks to its content and shares the row.
   `:not(:has(.has-error))` mirrors the escape hatch below: a collapsed risc whose fields failed
   validation keeps its body visible, and a visible field grid needs the full width. */
.risk-off-list > .risk-card--collapsed:not(:has(.has-error)) {
  flex: 0 0 auto;
/* the 4px gold edge is a card affordance; on a pill the tint carries the meaning */
  border: 1px solid var(--panel-warning-border);
  border-radius: var(--radius-pill);
}
/* Symmetric padding: the fold affordance sits before the name, so the name is the pill's
   last content. */
.risk-off-list > .risk-card--collapsed:not(:has(.has-error)) > .risk-card__head {
  padding: 4px 12px;
  gap: 8px;
  border-radius: var(--radius-pill);
}
/* "Zasílat upozornění" is a checkbox, not a notice, and it carries no styling of its own: whether it
   reads as the chosen setting of a group being CREATED (enabled) or as the existing group's own
   setting (disabled) is the browser's default rendering of the disabled attribute, not a class. No
   tinted panel frames it — a panel around a single checkbox reads as a callout demanding a decision.
   This is only the JS toggle's target, so all it owes the form is the row's own rhythm. */
.group-assign-notify {
  margin-bottom: 10px;
}
/* ---------- A stacked checkbox sits on its row's control line ----------
   `.checkbox-stack` stacks label-over-box, and the box then inherited the block-checkbox centring margin,
   so it hung ~3px below the line it shares with the date inputs beside it (bottom-aligned to the 34px
   input rather than centred on it).  Zeroing just the box's own VERTICAL margin leaves the stack's 6px gap
   to do the spacing and lands the box's centre on the input's centre.  The horizontal margin is
   deliberately untouched — the inline variant needs it.

   Scoped to `.checkbox-stack`, which is the shared opt-in for this exact geometry (every emitter
   sits next to a 34px control), and one class more specific than the rule it corrects.
   (The row's HORIZONTAL misalignment was a markup fix — a grid column's width is what the `col-md-*`
   class means, and overriding it in CSS would leave the view claiming a width it no longer has.) */
.checkbox-stack label:has(> input[type="checkbox"]) > input[type="checkbox"],
.checkbox-stack .checkbox label:has(> input[type="checkbox"]) > input[type="checkbox"] {
  margin-top: 0;
  margin-bottom: 0;
}
/* Field widths.  The three row-1 selects are capped so the free space goes to the search term (the widest
   field by far — it takes six kinds of identifier), and the row-2 select2s share the line evenly with the
   action group.  `min-width` is what decides wrapping for a flex item, so the caps are stated as a
   max/min pair rather than a fixed width. */
.client-filter__field { flex: 1 1 180px; min-width: 170px; max-width: 210px; }
.client-filter__field--grow { flex: 3 1 320px; min-width: 300px; }
.client-filter__field--sel { flex: 1 1 220px; min-width: 200px; }
/* Úkoly only: the six-field row needs a smaller HYPOTHETICAL SIZE for its `--grow` field.
   Flexbox decides where a line breaks from each item's hypothetical main size — the flex-basis CLAMPED BY
   min/max-width — and only THEN shrinks what it has placed.  So a row whose bases overflow wraps even
   though its min-widths would have fitted: at basis 320 / min 300 this row's hypothetical sizes came to
   1160 plus 5x12 gap = 1220 against a 1186px card, and the last field dropped to a line of its own.
   Lowering the basis alone is not enough, because the 300px min then becomes the clamp — hence both.
   220 for both fits and leaves room for the grow weights to share; the 3:1 weight is untouched, so that
   field still ends up the widest on the line.
   Scoped to this form: no other bar puts a `--grow` field on a six-field line. */
.comment-search .list-filter-bar__row > .list-filter-bar__field--grow {
  flex-basis: 220px;
  min-width: 220px;
}
/* ---------- Obchodní případ: the two moved boxes ----------
   Pojišťovny and Druhy pojištění moved from the bottom of the left column into the right one.  Nothing to
   declare for the move itself — it is markup — but the right column is 1/3 of the page, so the two grids
   that used to have 2/3 need the scroll container bootstrap 3 only hands out below 767px.  Without it the
   last columns are simply clipped by the card. */
.business-transaction-form .col-md-4 .kv-grid-container { overflow-x: auto; }
/* ---------- "Rozšířené vyhledávání" when it is OPEN ----------
   The toggle carries `.active` while the panel is open, and nothing in the 2026 layer claimed that state,
   so bootstrap's own `.btn-default.active` did — grey on a grey border, plus `.btn.active`'s inset
   box-shadow, which is what read as a second border inside the first.  On top of that `.btn.active:focus`
   RESTORES the UA focus ring that `.btn:focus { outline: none }` had removed, so the button also gained a
   blue halo the moment it was clicked.

   Open is a SELECTED state, so it takes the app's selected look: the mint fill every hovered or selected
   ghost button uses, the brand-green border that says "this is on", and the same green ink the button has
   when it is off.  No shadow.

   `#advanced-search-toggle` is the id modules.js drives on all five index filters, and the (1,1,0) it
   gives beats every one of bootstrap's `.btn-default.active…` rules ((0,2,0)-(0,3,0)) without an
   `!important`.  `:focus-visible` still draws a ring — keyboard users must keep it — but it is the app's
   green one, not the UA blue, and it is not drawn for a mouse click. */
#advanced-search-toggle.active,
#advanced-search-toggle.active:hover,
#advanced-search-toggle.active:focus {
  background-color: var(--brand-mint-bg);
  border-color: var(--brand-green);
  color: var(--brand-green);
  box-shadow: none;
  outline: none;
}
#advanced-search-toggle.active:focus-visible {
  outline: 2px solid var(--brand-green);
  outline-offset: 1px;
}

/* ---------- "Generovat předpisy": one card per generation mode ----------
   The requirement that shaped this block: THE SAME FIELD ALIGNS ACROSS CARDS — Období od beside
   Období od, row for row (per the team's ruling). A per-card flex
   stack cannot promise that: one wrapped title pushes that card's whole stack down. So the cards
   are SUBGRID columns of one shared grid — title, notes, each date and the action occupy the same
   shared row in every card, and a two-line title raises row 1 for all of them at once. Rows with
   no content anywhere (a missing variants row) collapse to zero for free. */
.generate-precepts-options {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: var(--spacing-box-gap);
}
.generate-precepts-card {
  grid-row: span 7;
  display: grid;
  grid-template-rows: subgrid;
  padding: var(--spacing-box-gap);
  border: 1px solid var(--border-default);
  border-radius: var(--radius-box);
  background: var(--surface-card);
}
/* The card rows: 1 title, 2 notes, 3-5 the dates, 6 variants, 7 action. Explicit placement,
   because subgrid auto-placement would fill sequentially and a card without dates would let its
   action drift up into a date row. */
.generate-precepts-card-title {
  grid-row: 1;
  margin-bottom: var(--spacing-form-group-gap);
  font-weight: 600;
  color: var(--ok-green);
}
.generate-precepts-card-notes { grid-row: 2; }
.generate-precepts-card-notes:empty { display: none; }
/* The blocked card has no dates, so its prose SPANS the date rows — otherwise the shared notes
   row would inflate to the prose height and hold an empty band under every other card's title. */
.generate-precepts-card--blocked .generate-precepts-card-notes { grid-row: 2 / 7; }
.generate-precepts-card-note { color: var(--text-muted); }
/* `display: contents` dissolves the wrapper so the three date form-groups become the card's own
   grid items and can take shared rows 3-5. */
.generate-precepts-card-fields { display: contents; }
.generate-precepts-card-fields > .form-group { margin-bottom: var(--spacing-form-group-gap); }
.generate-precepts-card-fields > .form-group:nth-child(1) { grid-row: 3; }
.generate-precepts-card-fields > .form-group:nth-child(2) { grid-row: 4; }
.generate-precepts-card-fields > .form-group:nth-child(3) { grid-row: 5; }
.generate-precepts-card-action { grid-row: 7; align-self: end; padding-top: var(--spacing-form-group-gap); }
/* A blocked mode keeps its card and its reason — the card recedes, it does not disappear. */
.generate-precepts-card--blocked { background: var(--surface-tint); }
.generate-precepts-card--blocked .generate-precepts-card-title { color: var(--text-disabled); }
.generate-precepts-variants {
  grid-row: 6;
  display: flex;
  flex-wrap: wrap;
  gap: var(--spacing-box-gap);
  margin-bottom: 0;
  padding-top: var(--spacing-form-group-gap);
}
/* The variant radios are inside a `.form-group`, one of the canonical 20px-radio selectors in 2a,
   so they inherit the app's radio and all of its states rather than restating them here. */
.generate-precepts-variant {
  display: flex;
  align-items: center;
  gap: var(--spacing-inline-gap);
  margin-bottom: 0;
  font-weight: 400;
  cursor: pointer;
}

/* ============================================================================================
   4d. AREAS / notifications
   ============================================================================================ */
/* Notifications-specific field widths.  `max-width` is what keeps the line unwrapped: a flex item sized
   `auto` bases its width on its content, and the Typ select's widest option measured 376px, which pushed
   Obsah onto a second row. */
.notif-filter__field {
  flex: 0 1 auto;
  min-width: 150px;
  max-width: 180px;
}
/* The type select carries the longest prompt ("- Typ notifikace -"); Obsah takes the slack. */
.notif-filter__field:first-child { min-width: 180px; max-width: 220px; }
/* A 160px basis, not the mock's 200px: flex decides wrapping on BASE sizes, and a 200px basis left the
   button pair without room on the line (it wrapped under the fields). */
.notif-filter__field--grow { min-width: 160px; max-width: none; flex: 1 1 160px; }
/* ---------- Status segmented toggle + "mark all as read" ---------- */
.notif-toolbar {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 10px 0;
}
.notif-status { display: inline-flex; }
/* Mint, not solid green — the same "selected but quiet" treatment the active pager segment uses.  Weight
   600 is what carries the state without colour alone. */
.notif-status__segment--active {
  background-color: var(--brand-mint-bg);
  font-weight: 600;
}
/* On the control line with the segments beside it.  `.btn-sm`'s own 4px vertical padding leaves 28px, so
   the height alone would not centre the label: at border-box 34 the two 1px borders and a 18px line box
   leave exactly 7px above and below.  The horizontal padding is left to `.btn-sm`. */
.notif-toolbar__markall {
  margin-left: auto;
  height: var(--size-control-height);
  padding-top: 7px;
  padding-bottom: 7px;
}
/* ---------- List-style table ---------- */
.notif-table {
  table-layout: fixed;
  width: 100%;
}
/* 90px, not the mock's 70px: 70 fits the two 28px buttons but not the "Akce ▾" export dropdown the column
   header keeps (72px + the cell's padding = 88px), which spilled into Datum. */
.notif-col-actions { width: 90px; }
.notif-col-date { width: 110px; white-space: nowrap; color: var(--text-muted); }
.notif-col-entity { width: 320px; }
/* (The orange `.notif-dot` and its `.notif-col-dot` column are gone — the row tint and the bold subject
   are the unread cues.) */
.notif-date__time {
  display: block;
  font-size: 12px;
}
/* One line per field: with table-layout:fixed the cell width is known, so long subjects and
   previews end in an ellipsis instead of growing the row to five lines (the old grid's look).
   Both carry a title attribute, and the row detail has the full text. */
.notif-subject,
.notif-preview {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.notif-subject {
  color: var(--text-body);
  margin-bottom: 2px;
}
.notif-preview {
  color: var(--text-muted);
  font-size: 13px;
}
/* Weight is the second unread cue next to the dot and the tint. */
.notification-unread .notif-subject { font-weight: 600; }
.notification-read .notif-subject { font-weight: 400; }
/* Row tints.  Important, so the striping cannot lose an order tie to a sheet registered after this
   file (the AJAX-dialog path appends vendor bundles there).  The classes are written only by
   notification/_grid.php's rowOptions, always inside `.notif-table > tbody`.  Hover keeps a visible
   response, which a plain `!important` tint would have swallowed. */
.notif-table > tbody > tr.notification-unread { background-color: var(--pastel-sky-light) !important; }
.notif-table > tbody > tr.notification-read { background-color: var(--surface-card) !important; }
.notif-table > tbody > tr.notification-unread:hover { background-color: var(--pastel-sky-light-hover) !important; }
/* Read rows hover the same --surface-tint as every other grid; only UNREAD rows hover inside
   their own sky family above, so the unread striping survives the hover. */
.notif-table > tbody > tr.notification-read:hover { background-color: var(--surface-tint) !important; }
/* Only the "no entity" placeholder is notifications-only — the chip itself is the page-independent
   `.entity-chip` in `3b. COMPONENTS / chips`, shared with the Úkoly list. */
.notif-entity--empty { color: var(--text-placeholder); }
/* The notification reader: one card, page and dialog alike (notification/_view.php). */
.notification-reader { display: flex; flex-direction: column; gap: 20px; max-width: 760px; }
.notification-reader__meta { display: flex; align-items: center; gap: 10px; color: var(--text-muted); font-size: 12px; }
.notification-reader__dot { width: 8px; height: 8px; border-radius: 50%; background: var(--brand-orange); }
.notification-reader__state.is-unread { color: var(--brand-orange); font-weight: 500; }
.notification-reader__toggle {
  width: var(--size-grid-action-btn); height: var(--size-grid-action-btn); padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
  margin-left: 6px;
}
/* The reader has room the grid cell does not: the chip hugs its label and wraps instead of
   ellipsising (`.entity-chip > .btn` nowrap in 3b). */
.notification-reader .search-chip.entity-chip { width: auto; max-width: 100%; }
.notification-reader .entity-chip > .btn { white-space: normal; }
.notification-reader__subject { margin: 0; font-size: 17px; font-weight: 600; color: var(--ok-green); }
.notification-reader__body { padding-top: 20px; border-top: 1px solid var(--border-default); line-height: 1.65; }
.notification-reader__actions { display: flex; gap: 8px; padding-top: 8px; }
.notification-reader__nav { display: flex; justify-content: space-between; margin-top: 4px; padding-top: 16px; border-top: 1px solid var(--border-default); }
.notification-reader__nav > .btn.disabled { color: var(--text-disabled); pointer-events: none; }
.notification-reader-modal .modal-dialog { width: 680px; max-width: calc(100vw - 32px); }
.notification-reader-modal .modal-header { padding: 20px 24px 16px; }
.notification-reader-modal .modal-body { padding: 24px; }
/* DELIBERATELY STILL RED — the other `tr.danger` rows in the app, checked one by one so the inactive-row
   list in 3e is a decision and not a sample:
     modules/document/views/required-document/_grid.php   a required document that is MISSING
     modules/finance/views/commission/_grid.php           a commission rate flagged COLOR_DANGER
   Both are "something is wrong here", which is exactly what the ruling reserves red for.  Two more mark
   an inactive record and would take the grey by the same argument, but neither is an entity grid:
   subject/anniversary-hash and console/maintenance. */

/* ---------- The Avíza status segments: 34px is a MINIMUM, not a width ----------
   All three segments were pinned to `width: 34px; padding: 4px 0`, which is right for the two envelope
   glyphs and wrong for "Vše": the word is ~27px at 13px, so it sat in a 32px content box and read as
   clipped.  `min-width` keeps the icon tiles exactly as they were — a 13px glyph plus 2x9px padding plus
   the 2px border is 33, which the 34px floor rounds back up — and lets the text one take the width its
   label needs.
   The `height` puts the group on the control line with the "Označit vše" button; 9px HORIZONTAL is the
   width ruling above and must not be folded into it, and the 7px vertical is what centres a 18px line box
   inside border-box 34 (see `.notif-toolbar__markall`), so the widened "Vše" keeps its proportions. */
.notif-status__segment {
  text-align: center;
  width: auto;
  min-width: 34px;
  height: var(--size-control-height);
  padding: 7px 9px;
}
/* ---------- Subscription form (notification-subscription/_subscription.php) ---------- */
.notification-subscription .not-subscribed {
    color: red;
    margin-bottom: 20px;
}
/* The attribute checkboxes flow as floated columns; the container self-clears. */
.notification-subscription .checkbox-attributes {
    content: "";
    display: table;
    clear: both;
}
.notification-subscription .checkbox-attributes > div {
    float: left;
    padding-right: 20px;
}

/* ============================================================================================
   4e. AREAS / commission balance log
   ============================================================================================ */
/* ---------- Grid ----------
   230px is the mock's Akce width; it fits the seven 28px tiles plus the gaps the inline
   whitespace between them already produces, and nowrap keeps them on one line. */
.cbl-col-actions { width: 230px; white-space: nowrap; }
/* "1.10.2025 – 30.10.2025" must not break across two lines. */
.cbl-col-period { white-space: nowrap; }
/* The header period is the secondary of the two, hence the muted ink (mock: #5E6E6A). */
.cbl-col-period--header { color: var(--text-muted); }
/* State chip.  bootstrap's `.label` is 75% font-size with white ink on a saturated fill; this chip is a
   tinted pill with a dark ink of the same hue, so ink and fill are restated here.  Its SIZE is the
   shared `.kv-pill` anatomy in `3f. COMPONENTS` — the same chip serves both pages.
   The base is the NEUTRAL chip, which is what any state outside the three below gets.
   Full column width is the same ruling the entity chips carry. */
.cbl-state {
  width: 100%;
  background: var(--surface-disabled);
  color: var(--text-muted);
}
.cbl-state--validate { background: var(--ok-cream); color: var(--panel-warning-text); }
.cbl-state--send { background: var(--pastel-sky-light); color: var(--pastel-sky-text); }
.cbl-state--closed { background: var(--panel-success-bg); color: var(--ok-green); }
/* ---------- Uzávěrka provizí: one toolbar button height ----------
   The record toolbar mixed 36px and 28px, because some of its buttons pass `SIZE_SMALL` and one emits
   `.btn-sm` — a per-call-site choice in the view, not a design decision.  They also sat 4px apart
   vertically, because the two sizes centre on different line boxes.

   Normalised in CSS rather than by editing the widgets: `CommissionBalanceLogRegenerateButton` and
   `LinkButton` are shared, so changing their defaults would move buttons on pages nobody asked about.
   Scoped to this page's toolbar through the wrapper the view already renders.

   36px, NOT the 34px `--control-height`: 36 is what every other record toolbar in the app measures and
   what a plain `.btn` is deliberately kept at.  One value here if all toolbars should move onto the
   token instead. */
.commission-balance-log-view .card-toolbar .btn {
  padding: 7px 14px;
  line-height: 20px;
}

/* ============================================================================================
   4f. AREAS / dashboard
   ============================================================================================ */
/* ---------- Dashboard chart toggles: compact checkboxes ----------
   The Statistiky tab drives its four charts with 19 series toggles.  They were 28x28 — the size restored
   for FORM checkboxes at the client's request — which is oversized next to their 13px year/company
   labels; and because `.checkbox-inline` is missing from the canonical custom-appearance rule (which
   lists only `.checkbox` and `.form-group`) the unchecked ones were plain NATIVE checkboxes stretched to
   28px: square corners, no tint, no border.

   Both are fixed here with the compact grid treatment: 20px, `--radius-sm`, an 11px check.  Scoped to
   `body.body-dashboard` (the controller-id body class, so it holds for every dashboard action) AND to
   `.checkbox-inline`, which on this page means exactly those 19 toggles.

   NOT applied to the "Uživatel - Profil" tab (10 checkboxes in `.checkbox` / `.form-group`): that tab
   renders the shared user-profile form, so shrinking it here would contradict the form-checkbox ruling and
   would make the same form look different from /access/user/profile. */
body.body-dashboard .checkbox-inline input[type="checkbox"] {
  appearance: none;
  -webkit-appearance: none;
  width: var(--size-checkbox-grid);
  height: var(--size-checkbox-grid);
  margin: 0;
  cursor: pointer;
  background-color: var(--surface-input);
  border: 1px solid var(--border-default);
  border-radius: var(--radius-sm);
  display: inline-grid;
  place-content: center;
  vertical-align: middle;
}
body.body-dashboard .checkbox-inline input[type="checkbox"]:hover { border-color: var(--border-strong); }
/* The white check itself is the shared SVG background; only its size and the box colours are restated, so
   the checked box does not inherit the tint set above. */
body.body-dashboard .checkbox-inline input[type="checkbox"]:checked {
  background-color: var(--ok-green);
  background-size: 11px;
  border-color: var(--ok-green);
}
/* ---------- The dashboard's tab strip, switcher and cards on one pair of edges ----------
   The dashboard's three siblings (the account-switcher `<ul>`, the `.nav-tabs` and krajee's
   `.tab-content`) share ONE inset, because the 10px that only `.tab-content` had was lifted onto their
   common parent `.dashboard-form`; that inset is now deleted rather than moved again, so the block lands
   on the same pair of edges as the record pages while keeping the internal alignment.  The `.tab-content`
   zeroing that used to be paired with it is redundant: `.tabs-x > .tab-content { padding-left: 0;
   padding-right: 0 }` further down states it for EVERY krajee TabsX in the app, and the dashboard's
   `.tab-content` is a direct child of `div.tabs-x`.  The vertical 10px (the gap between the tab strip and
   the first card) is untouched by either. */

/* ---------- The account switcher is white, not field-tinted ----------
   Every form control in the app is filled `--surface-input`.  That works on a white card; this select2 is
   the one control in the app standing directly on the page's tinted background, and #FAFDFB against
   #F4FFFB is a 2-3 point difference per channel — the control effectively disappears.  White is the fix,
   scoped to this one switcher: the field tint stays the rule everywhere else.  (0,4,0) with the
   `.field-dashboard-account_id` wrapper against the base rule's (0,3,0), so it needs no `!important`. */
.dashboard-form .field-dashboard-account_id [class*="select2-container--krajee"] .select2-selection {
  background: var(--surface-card);
}
:is(.checkbox, .form-group, .checkbox-inline) input[type="checkbox"]:checked:disabled,
.checkbox label input[type="checkbox"]:checked:disabled,
label > input[type="checkbox"]:checked:disabled {
  background: var(--brand-mint-bg) url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath fill='none' stroke='%235E6E6A' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round' d='M3.5 8.5l3 3 6-7'/%3E%3C/svg%3E") center/70% no-repeat;
  border-color: var(--brand-mint);
}

/* ============================================================================================
   4g. AREAS / home page
   ============================================================================================ */
/* The fulltext-search category dialog (site/_search_category_form.php): responsive card columns. */
.search-category-checkbox-columns {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
    gap: 20px;
}
.search-category-column {
    border: 1px solid #ddd;
    padding: 15px;
    border-radius: 5px;
    background-color: #f9f9f9;
}
 /* fa-circle: filled */

/* Search-history chips row (replaces the Historie vyhledávání box) */
.search-history {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin-bottom: 36px; padding: 12px 8px 0;
}
.search-history__label { font-size: 13px; color: var(--text-muted); font-weight: 600; }
/* 4/6 vertical, not 5/5: the optical compensation of `.kv-grid__value > .kv-pill`, measured with
   that rule's own reference strings — ink centre 1.284px below the chip's at 5/5, 0.284px at 4/6.
   The height is unchanged.  `.entity-chip` and `.header-groups > .search-chip` restate all four
   insets, so each carries its own split at its own size and neither inherits this one. */
/* `font-weight: 400` for the same reason `.chip` states it — see that rule.  Both classes are on
   the same element in the record title row, so both have to say it or the h1's 500 comes back through
   whichever one stayed silent. */
.search-chip {
  display: inline-flex; align-items: center; gap: 8px;
  background: var(--surface-card); border: 1px solid var(--border-strong); border-radius: var(--radius-pill);
  padding: 4px 8px 6px 14px; font-size: 13px; font-weight: 400; color: var(--ok-green);
  transition: border-color 120ms, box-shadow 120ms;
}
.search-chip:hover { border-color: var(--ok-green); box-shadow: var(--shadow-box); }
.search-chip > a { color: var(--ok-green); text-decoration: none; }
.search-chip__remove {
  display: inline-flex; align-items: center; justify-content: center;
  width: 18px; height: 18px; border-radius: 50%;
  background: var(--surface-tint); color: var(--ok-green);
  font-size: 11px; line-height: 1; text-decoration: none;
}
.search-chip__remove:hover { background: var(--ok-blush); color: var(--status-danger-text); }
/* The eraser is the whole control — the word "Vymazat vše" lives in its `title` — so it needs
   a glyph size rather than the 13px text size it inherits, and it needs a hit area: an 18px chip
   row is the only thing around it and a bare 14px `<i>` is a 14px target. Same 18px square as
   `.search-chip__remove` above, which is the row's other icon-only affordance. */
.search-history__clear {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  font-size: 14px;
  line-height: 1;
  color: var(--status-danger);
  margin-left: 4px;
  text-decoration: none;
}
.search-history__clear:hover { color: #8C1D16; }
/* Favorites grid: text stacks instead of nested bordered boxes */
.fav-stack { display: flex; flex-direction: column; gap: 1px; }
.fav-stack__title { font-weight: 600; }
.fav-stack__meta { font-size: 12px; color: var(--text-muted); }
/* Result-box count badge (post-search state) */
.box-title .badge-count { background: var(--surface-tint); color: var(--ok-green); }
/* 4/4, and the criterion is the LINE BOX, not the ink extent.
   The 3/5 split this rule used to carry came from the kv-pill's optical compensation, measured with
   that rule's two reference strings — and both of them carry a descender.  A group name usually does
   not, and for a descender-less name the ink extent IS the cap band, so the 1px it borrowed from the
   top is spent on a shape that is not there: measured on the live five-chip row, `Skupina 9c` sat
   0.00px off centre while `KDP` sat 1.50px HIGH, `Fanout Test` and `Klient Fanout` 1.25px.  Ink-extent
   centring cannot be right for arbitrary user text — it moves with the letters.  Symmetric insets put
   the label's 18px line box in the middle of the 18px content box instead, which lands the cap band
   0.5px high for EVERY name, descender or not.  28px height unchanged. */
.content-header > h1 > .header-groups > .search-chip {
  padding: 4px 8px 4px 12px;
  font-size: 13px;
  gap: 4px;
}
.content-header > h1 > .header-groups > .search-chip > .fa-tag {
  color: var(--ok-mint);
  margin-right: 2px;
}
/* ---------- The home page's SEARCH-RESULTS state ----------
   Client ruling: the DEFAULT state is right as it stands and does not change — only the answer state does.
   The markup half is in views/site/_search.php: the Oblíbené card is not rendered while the search has
   rows, and the results column widens to the full row.  Deliberately left alone there: one white `.box`
   per result TYPE, the green `.box-title`, the count badge, the row action tiles and every link's target.

   Everything below is scoped to `.site-search__results`, the results column's own class, so the Oblíbené
   card in the default state is untouched.

   Result-row links go brand green: the app's grid link colour is the CTA orange, which on this page
   competes with the row's own action tiles for the eye — the results are a list to READ, not a set of
   calls to action.  Buttons keep their own paint. */
.site-search__results .box-body table a:not(.btn) { color: var(--brand-green); }
.site-search__results .box-body table a:not(.btn):hover,
.site-search__results .box-body table a:not(.btn):focus { color: var(--brand-green-dark); }
/* The header rule drops from 2px to the same hairline the rows use.  It is the last piece of table chrome
   heavy enough to read as a data grid rather than a compact result list — the striping and the cell
   borders were already gone, so this one value is the whole difference. */
.site-search__results .kv-grid-table > thead > tr > th { border-bottom-width: 1px; }
/* Two columns, boxes FLOWING into them (approved look: .superpowers/sdd/figma-live/figma-uvod-2col.png).
   The mock's arrangement is a flow, not a pair-per-row: its two short left boxes and one taller right box
   overlap vertically, which `col-md-6` pairs cannot produce — a float row is as tall as its tallest member
   and the next box starts below both.  `column-count` gives exactly that behaviour and needs no markup
   change, so the `li.site-search[data-save-url]` wrapper web/js/modules.js walks up to when it records a
   search-history entry stays where it is.
   The `li`s are bootstrap grid cells; inside a multicol container their float and gutter would break the
   flow, hence the reset.  `break-inside: avoid` keeps a box whole — without it a long grid would be sliced
   across the column boundary.  The vertical rhythm is the box's own margin, so only the horizontal gap is
   declared. */
.site-search__results > ul.row {
  column-count: 2;
  column-gap: var(--spacing-box-gap);
  margin: 0;
}
.site-search__results > ul.row > li {
  float: none;
  width: 100%;
  padding: 0;
  break-inside: avoid;
}
/* Half the width means three of the six result grids no longer fit.  kartik already wraps every grid in
   `.kv-grid-container.table-responsive`, but bootstrap 3 only gives `.table-responsive` its overflow
   inside a `max-width: 767px` media query — on the desktop the overflow is CLIPPED by the card and the
   last columns are simply lost.  Turning the wrapper into a real scroll container keeps the data
   reachable; the scroll is inside the card and the page itself still does not scroll horizontally. */
.site-search__results .kv-grid-container { overflow-x: auto; }
/* ---------- The home page's default state ----------
   Approved look: .superpowers/sdd/figma-live/figma-uvod-final-default.png.  Two changes, both a re-layout
   of markup that already existed: the search history moves from a bare row into the same white card as
   everything else on the page (label as a muted uppercase caption on its own line), and the single wide
   "Oblíbené" grid becomes THREE small per-entity cards side by side.

   The chip, its ✕ and "Vymazat vše" keep every rule they had — `.search-chip*` and
   `.search-history__clear` are declared just above and are not restated, which is why the modifier is on
   `.search-history` and not a new class.  Same for the star: it is still `FavoriteButton`, still
   `.favorite`, still the same AJAX round trip; only the tile it used to be drawn as is gone.

   The history card: `.search-history` carries a bottom margin and an asymmetric inset that were the
   spacing of a bare row standing on the page background.  Inside a `.box-body` the card already provides
   the inset and the `.box` its rhythm, so the modifier zeroes them rather than fighting them. */
.search-history--card { display: block; margin-bottom: 0; padding: 0; }
.search-history--card .search-history__label {
  display: block;
  margin-bottom: 10px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--text-muted);
}
.search-history__chips { display: flex; align-items: center; flex-wrap: wrap; gap: 10px; }
/* The three favourite boxes.  `.fav-box` is a plain card with a header that carries NO hairline — at this
   size the title and the first row are close enough that a divider reads as a table header, which is
   exactly the "grid" impression this undoes.  The rows are quiet: no borders, no zebra, one hover tint.
   Equal heights: the mock's three cards share a bottom edge, which a bootstrap float row cannot do (each
   column is as tall as its own content).  Flexing the row — and the columns, so the card inside can
   stretch — is the whole fix; the columns keep their `col-md-4` width and their gutters. */
.fav-boxes { display: flex; flex-wrap: wrap; }
.fav-boxes > [class*="col-"] { display: flex; }
.fav-boxes > [class*="col-"] > .fav-box { flex: 1 1 auto; }
.fav-box > .box-header { padding-bottom: 4px; }
.fav-box > .box-body { padding-top: 4px; }
.fav-list { list-style: none; margin: 0; padding: 0; }
.fav-row {
  display: flex;
  align-items: baseline;
  gap: 8px;
  padding: 6px 4px;
  border-radius: var(--radius-sm);
  font-size: 13px;
}
.fav-row:hover { background: var(--surface-tint); }
/* The star.  FavoriteButton appends `btn-star` here (this box only ever renders the favourited
   state), but the call site passes no `btn`, so no tile rule applies — `.grid-view .btn.btn-star`
   and the record-actions block both need a `.btn` this element does not have, and this pair is the
   star's ONLY paint.  `.fav-list` in front of the class is what keeps the gold ahead of skin.css's
   `a:focus, a:hover, a:active { color }` at (0,1,1), which a bare `.fav-row__star` at (0,1,0) would
   lose to on element count. */
.fav-list .fav-row__star {
  flex: none;
  background: none;
  border: 0;
  padding: 0;
  color: var(--brand-gold);
  font-size: 13px;
  line-height: 1;
  text-decoration: none;
}
.fav-list .fav-row__star:hover, .fav-list .fav-row__star:focus {
  color: var(--brand-gold-dark);
  text-decoration: none;
}
/* Both halves of a row are one line and ellipsised, never wrapped: the card is a third of a row wide
   and the meta now carries the client name plus the whole contract identity, which does not fit.  A
   wrap would make the three cards different heights and turn the list back into a block of text.
   `min-width: 0` is what lets a flex item shrink below its content at all — without it the ellipsis
   never engages and the row overflows the card instead.  The full string is in `title` on both. */
.fav-row__link, .fav-row__meta {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.fav-row__link { color: var(--brand-green); font-weight: 600; }
.fav-row__link:hover, .fav-row__link:focus { color: var(--brand-green-dark); text-decoration: underline; }
/* The separator is drawn, not stored: the view hands over the record's remaining description
   lines and the middot belongs to the layout.

   `flex: 1 1 0` is what keeps the TITLE intact: a zero flex basis carries no shrink weight, so the
   meta absorbs the whole overflow — it takes the row's leftover width by growing into it and gives it
   back by ellipsising.  With the default `auto` basis both halves shrink in proportion and a 9-digit
   contract number loses its last digits while the meta beside it still has 200px of text. */
.fav-row__meta { flex: 1 1 0; color: var(--text-muted); font-size: 12px; }
.fav-row__meta::before { content: "·"; margin-right: 6px; }
/* The row's cross-navigation glyphs — papír / plamínek / kufr, the client grid's own vocabulary.
   Borderless for the same reason the star at the other end of the row is: a pair of bordered tiles per
   row would rebuild the action column this card exists to avoid.  Smaller than the grid's 28px tile
   because the row is 13px ink in 32px of height — the padding here is the tap target, not spacing, and
   it is what takes the hit area to 21x19 without moving the row.  `flex: none` keeps them out of the
   meta's `flex: 1 1 0` shrink, and the baseline alignment is INHERITED on purpose: the star already
   proves a glyph sits on this row's baseline.  Two classes deep for the star's reason as well —
   skin.css's `a:focus, a:hover, a:active { color }` is (0,1,1) and would beat a bare class. */
.fav-list .fav-row__nav {
  flex: none;
  padding: 3px 4px;
  color: var(--brand-green);
  font-size: 13px;
  line-height: 1;
  text-decoration: none;
}
.fav-list .fav-row__nav:hover, .fav-list .fav-row__nav:focus {
  color: var(--brand-green-dark);
  text-decoration: none;
}
.fav-list__empty { margin: 0; padding: 6px 4px; color: var(--text-muted); font-size: 13px; }
/* ---------- The central search can show a selection again ----------
   One selection is declared for the whole app: `::selection { background: var(--ok-mint); color:
   var(--ok-green) }`.  The navbar search input's own FILL is `--ok-mint`, so selecting text there paints
   mint on mint and the only thing that moves is the ink — effectively invisible.

   SCOPED, NOT GLOBAL — the global was considered and measured, and it is not safe.  A brand-green fill
   with white ink is the obvious high-contrast answer on a light page, but the app has real DARK surfaces:
   `.main-header` and its `.navbar` are `--ok-green` flat, and the sidebar's `treeview-menu` is a 7% green
   over white.  Selecting the wordmark or a navbar label would then paint green on green — the same bug
   moved somewhere else.  The mint/green pair is right for the ~99% of the app that is white or near-white;
   only the one mint control is wrong, so only the one mint control is restated.
   `.main-header` keeps the rule off the home page's own search box (same `.search-history-form` class,
   white fill, where the global mint reads fine).  (0,2,0) + a pseudo-element against the global's (0,0,0),
   so no `!important`. */
.main-header .search-history-form .form-control::selection {
  background: var(--brand-green);
  color: var(--text-inverse);
}

/* ============================================================================================
   4h. AREAS / profile and access
   ============================================================================================ */
.user-update-profile .box-body > div > .row:last-child > div > .form-group:last-child { margin-bottom: 0; }
/* Profile (/access/user/profile): the vertical rhythm was gone — every label sat flush against the field
   above it.  ROOT CAUSE: adminlte.css:139 ends a long legacy selector list with
   `.box-body > div > .row:last-child > div > .form-group { margin-bottom: 0 }`, whose intent was "don't
   leave a trailing gap under the last row of a box body".  The profile form is a SINGLE row, so that row
   is also the last one and the rule zeroed the margin of EVERY form-group in it.  (The right-hand checkbox
   column is nested one `.row` deeper, which is why only two of the three columns looked broken.)
   Restored scoped to the profile page, keeping the original intent by zeroing only the genuinely last
   group in each column.  The legacy selector is (0,4,2); prefixing it with `.user-update-profile` makes
   ours (0,5,2), so it wins on specificity, not load order.  It restates the base step, so it has to track
   the base step. */
.user-update-profile .box-body > div > .row:last-child > div > .form-group { margin-bottom: 10px; }
/* …and the checkbox column reads as one list rather than five separate fields: a tighter, even step. */
.user-update-profile .form-group:has(> .checkbox) { margin-bottom: 6px; }
/* ---------- Account logo (account/form/_document_settings.php) ----------
   The action buttons anchor to the 80px thumbnail's bottom edge, beside it. */
.logo-display {
    position: relative;
}
.logo-display img {
    width: 80px;
}
.logo-buttons {
    position: absolute;
    bottom: 0;
    margin-left: 85px;
}
/* ============================================================================================
   4h. AREAS / profile, access and the login page
   ============================================================================================ */

/* ---------- The login page ---------- */
.body-guest .content-wrapper {
  background-color: var(--ok-green);
  background-image: url('../images/linky-light.svg');
  background-repeat: no-repeat;
  background-position: right top;
  background-size: cover;
  display: flex;
  flex-direction: row;
  justify-content: space-evenly;
  align-items: center;
  min-height: 90vh;
}
.body-guest .content { margin-left: 0; margin-right: 0; }
.body-guest .main-footer { background: var(--ok-orange); color: var(--surface-card); }
.body-guest .main-footer a { color: var(--main-bg-color); }
.body-guest .login-logo { margin-bottom: 35px; }
.body-guest .login-logo img { max-width: 25vw; max-height: 100vh; margin-bottom: 25px; }
.login-box { text-align: center; width: 100%; color: var(--ok-green); font-size: 18px; font-weight: 300; }
/* User ruling (skin retirement R4): the login submits stay the big 18px/10px pill on purpose —
   the app-wide 13px/6px `.btn` does not apply here. `.login-box .btn-lg` in 2a leans on this 18. */
.login-box .btn { font-size: 18px; border-radius: 10px; }
.login-box-body { display: grid; border-radius: var(--radius-control); }
.login-box-form { display: inline-block; }
.login-box-title { color: rgb(0, 0, 0); margin-bottom: 25px; font-size: 35px; text-align: center; font-weight: bold; }
.card { width: 25vw; min-width: 300px; border: none; border-radius: 20px; }
/* ---------- The login page's "O" ----------
   `views/layouts/content-login.php` renders `<section class="login-logo"><img
   src="/images/znacka_bila.svg"></section>` and the `.login-logo img` rule above caps it at
   `max-width: 25vw`.  That used to produce the giant white O; after the logo became an SVG it
   rendered 0x0.

   ROOT CAUSE: `web/images/znacka_bila.svg` carries a `viewBox` and NO `width`/`height` attributes, i.e. it
   has an intrinsic RATIO but no intrinsic SIZE.  `.body-guest .content-wrapper` is `display: flex`
   (above), so `.login-logo` is a flex item whose `flex-basis: auto` resolves against its content — and
   a replaced child with no intrinsic size contributes 0.  The section collapses to 0 and takes the image
   with it; neither cap is involved, and stating any size at all brings it back.

   So the fix is to state the size the SVG no longer carries.  `25vw` is not a new number: it is exactly
   what the cap already resolved to before, which is why the O comes back at the reference size
   rather than at a size of my choosing.  The cap stays where it is and is now a no-op; `height: auto`
   keeps the viewBox ratio.  `flex-shrink: 0` protects the decoration from a narrow viewport; below 768px
   `.hidden-xs` on the section removes it, as it always has. */
.body-guest .login-logo { flex-shrink: 0; }
.body-guest .login-logo img { width: 25vw; height: auto; }
/* ---------- The profile's switch column joins the form's rhythm ----------
   /access/user/profile lays out three `col-md-4` columns: two of ordinary fields and one of five
   checkboxes.  A FIELD row is 55px — an 18px label, a 3px gap and the 34px control — plus a 10px margin,
   so the left two columns step by 65.  A CHECKBOX row is only its label box, 34px for one line of text and
   36px for two, so the third column stepped by 45-47 and drifted further from the fields with every row.

   Two changes, one in the markup and one here.  MARKUP: the column used to wrap its five fields in a
   nested `.row > .col-md-12`, which contributed nothing — one full-width child — while its 15px gutter
   pulled the checkboxes left of the column edge the browser had already given them.  Gone, which is also
   what makes the selector below a plain child combinator.

   CSS: 55px is the field row's box, so the two `:nth-child`s of any row now agree and the 10px margin
   already on the group supplies the same 65px step.  `padding-top: 18px` is the label line the switches do
   not have — it puts the box on the CONTROL line of the row beside it instead of on its label line.  18 and
   not the full 21 (label + gap) on purpose: two of the five labels wrap to two lines (36.4px), and
   18 + 36.4 = 54.4 still fits inside the 55px box, so a wrapping label cannot push its row out of the
   rhythm.  The 3px the one-line rows need is supplied by the box's own centring inside the label.

   Scoped to the class the view carries: this geometry is "a column of switches beside a column of fields",
   and the profile is the only page in the app that has one. */
.user-profile-switches > .form-group {
  min-height: 55px;
  padding-top: 18px;
}
/* "Úprava profilu" leads the read-only profile: the button moved from after the last field to before the
   first one (the ruling: it was easy to miss at the bottom) and is now the orange CTA.  Standing directly
   on the field grid it needs the gap a form row would have given it, so it takes the `.form-group` bottom
   margin every row above it uses instead of introducing a second rhythm. */
.user-profile-edit-cta { margin-bottom: 10px; }

/* ============================================================================================
   4i. AREAS / vendor surfaces
   ============================================================================================ */
/* The mdmsoft admin module's vendor views print an <h1> page title the app's own pages carry in
   .content-header instead. */
.body-admin-assignment-index section.content h1, .body-admin-role-index section.content h1, .body-admin-permission-index section.content h1, .body-admin-route-index section.content h1, .body-admin-rule-index section.content h1 {
    display: none;
}
/* U7) Kartik tabs-x (dashboard overview) — align with the 4h quiet-tab design;
   tabs-x ships its own boxed styling at higher specificity. */
.tabs-krajee .nav-tabs { background: transparent; border-bottom: 1px solid var(--border-default); }
.tabs-krajee .nav-tabs > li > a,
.tabs-krajee.tabs-above .nav-tabs > li > a {
  border: none !important;
  border-bottom: 3px solid transparent !important;
  border-radius: 0 !important;
  background: transparent !important;
  box-shadow: none !important;
  color: var(--text-muted);
  padding: 10px 2px;
}
.tabs-krajee .nav-tabs > li.active > a,
.tabs-krajee .nav-tabs > li.active > a:hover,
.tabs-krajee .nav-tabs > li.active > a:focus,
.tabs-krajee.tabs-above .nav-tabs > li.active > a {
  border-bottom-color: var(--ok-gold) !important;
  color: var(--ok-green);
  font-weight: 600;
}
.tabs-krajee > .tab-content { background: transparent; box-shadow: none; border: none; }
/* ---------- CKEditor 4 chrome ----------
   The editor was still stock moono-lisa (#d1d1d1 / #bcbcbc borders, #f8f8f8 toolbar and footer bands,
   #e5e5e5 button states, square corners) next to our flat mint fields.  The widget is used in ~12 places,
   so this is a global restyle of the CHROME only.

   LOAD ORDER: CKEditor injects its own skin stylesheets from JS, so `assets/<hash>/skins/moono-lisa/
   editor.css` always lands at the END of <head> — after this file.  Any override of ours that merely tied
   on specificity would therefore lose, exactly like the select2-in-a-dialog case, so the same `:root `
   prefix is used here: it adds one class level and matches precisely what the unprefixed selector matched.

   The editable text area itself is an <iframe>; its background and typography come from CKEditor's
   contents.css INSIDE that document and are unreachable from here — which is also why `.cke_inner` is
   white rather than the fields' tint.
   Focus: CKEditor adds `cke_focus` to the chrome, so the focus ring needs no JS; `:focus-within` is kept
   as a fallback for non-iframe (inline/divarea) instances. */
:root .cke_chrome { border: 1px solid var(--border-default); border-radius: var(--radius-control); box-shadow: none; }
:root .cke_inner { background: var(--surface-card); border-radius: var(--radius-control); }
:root .cke_top {
  background: var(--surface-card);
  border-bottom: 1px solid var(--border-default);
  border-radius: var(--radius-control) var(--radius-control) 0 0;
  padding: 5px 8px 3px;
}
:root .cke_bottom {
  background: var(--surface-card);
  border-top: 1px solid var(--border-default);
  border-radius: 0 0 var(--radius-control) var(--radius-control);
}
:root a.cke_button { border-radius: var(--radius-sm); }
/* Stock swaps border+padding on hover/active, which makes the toolbar jitter — keep the
   geometry fixed and change only the fill. */
:root a.cke_button_off:hover, :root a.cke_button_off:focus { background: var(--surface-tint); border: 0; padding: 4px 6px; }
:root a.cke_button_off:active, :root a.cke_button_on { background: var(--brand-mint-bg); border: 0; padding: 4px 6px; }
:root .cke_button_label { color: var(--text-muted); }
:root .cke_button_arrow { border-top-color: var(--ok-green); }
:root .cke_toolgroup a.cke_button:last-child:after,
:root .cke_toolgroup a.cke_button.cke_button_disabled:hover:last-child:after,
:root .cke_combo:after { border-right-color: var(--border-default); }
:root .cke_toolbar_separator { background: var(--border-default); }
:root a.cke_combo_button { border-radius: var(--radius-sm); border-color: var(--border-default); }
:root .cke_combo_on a.cke_combo_button, :root .cke_combo_off a.cke_combo_button:hover,
:root .cke_combo_off a.cke_combo_button:focus, :root .cke_combo_off a.cke_combo_button:active {
  background: var(--surface-tint);
  border: 1px solid var(--border-strong);
}
:root .cke_combo_text { color: var(--ok-green); }
:root .cke_combo_arrow { border-top-color: var(--ok-green); }
/* Toolbar dropdown panels are appended to <body>, same as the select2 dropdown. */
:root .cke_panel { border: 1px solid var(--border-default); border-radius: var(--radius-control); box-shadow: var(--shadow-dropdown); }
:root .cke_panel_grouptitle { background: var(--surface-tint); border-bottom: 1px solid var(--border-default); color: var(--ok-green); }
:root .cke_panel_listItem.cke_selected a, :root .cke_panel_listItem a:hover,
:root .cke_panel_listItem a:focus, :root .cke_panel_listItem a:active { background-color: var(--brand-mint-bg); }
:root .cke_path_item, :root .cke_path_empty { color: var(--text-muted); }
:root .cke_resizer { border-color: transparent var(--border-strong) transparent transparent; }
:root .cke_chrome.cke_focus, :root .cke_chrome:focus-within {
  border-color: var(--ok-green);
  box-shadow: var(--ring-focus);
}
/* ---------- The record page's header rows sit on the cards' edges ----------
   The contract page had four different left edges and three different right ones: `.content`'s own 24px
   inset (where the gold title bar, the toolbar, the tab strip and the tab wrapper `.box` all start), 10px
   further in where the CARDS started, and further still where the first toolbar BUTTON started.  Client
   update (no tabs) was already on `.content`'s edges for its cards, which is what every non-tabbed page in
   the app uses — so the CARDS move OUT to the app's edge rather than the header rows moving in.

   THE CARDS: krajee's TabsX gives `.tab-content` a flat `padding: 10px`, so everything inside the active
   tab pane is inset 10px from the tab strip above it and from the wrapper box's edges.  Only the two
   HORIZONTAL sides are zeroed: the TOP 10px is the gap between the tab strip and the first card and has to
   stay.  `.tabs-x` is used in exactly two places (the contract form and the dashboard form), and the
   dashboard's own copy of this declaration is in its section above, so this changes the contract page. */
.tabs-x > .tab-content {
  padding-left: 0;
  padding-right: 0;
}
/* ---------- /admin/role/view — the two things the user asked for, and nothing else ----------
   This is a VENDOR page (mdmsoft/yii2-admin).  The user's own scope: "you can fix just the blue colour and
   height of the box underneath it".  Everything else on it — the vendor toolbar, the table skin, the
   >> / << buttons — is deliberately left as found.

   a) THE ASSIGNED-USER BADGES.  The vendor's `_script.js` builds each one as
      `<span class="label label-info"><a>`, i.e. bootstrap's cyan #00C0EF with a white label colour that the
      app's orange link colour then overrides INSIDE it — a cyan pill with orange text, the only two colours
      on the page that are in no palette.  Retinted to the app's quiet chip, with the anchor made to inherit
      so the badge has ONE colour.  Scoped to `.auth-item-view`, the vendor view's own wrapper, so the app's
      other `.label-info` is untouched.

   b) THE DUAL-LIST BOXES.  Both are `select.form-control.list[multiple][size=20]` and both rendered 34px
      tall — one row of a 306-option list, with the second row clipped mid-glyph.  Cause: bootstrap's
      `select[multiple], select[size] { height: auto }` (0,1,1) is overridden by
      `.form-control, select.form-control { height: var(--control-height) }`, which is later and equally
      specific.  `height: auto` hands the box back to its `size` attribute; `max-height` caps the 20-row
      result at a sane picker height and lets the list scroll.  Originally scoped to this page's wrapper
      alone; the identical defect was then reported on /admin/assignment/view, so the rule is app-wide. */
.auth-item-view .label.label-info {
  /* `!important` twice, and only because AdminLTE.min.css spends one first:
     `.bg-aqua, …, .label-info { background-color: #00C0EF !important }`. Nothing else on this page
     needs one. */
  background-color: var(--brand-mint-bg) !important;
  color: var(--brand-green) !important;
}
.auth-item-view .label.label-info > a,
.auth-item-view .label.label-info > a:hover,
.auth-item-view .label.label-info > a:focus {
  color: inherit !important;
  text-decoration: none;
}
select.form-control[multiple], select.form-control[size]:not([size="1"]) {
  height: auto;
  max-height: 320px;
}
/* ---------- /admin/assignment/view — the other mdmsoft dual-list page ----------
   The vendor view prints its own `<h1>` while the app layout already titles the page — the name
   showed twice, the inner copy overflowing.  The search inputs sit flush on their listboxes. */
.assignment-index > h1 { display: none; }
.assignment-index .search { margin-bottom: 8px; }

/* ============================================================================================
   4j. AREAS / scheduler console
   ============================================================================================ */
/* The log detail's message dump (scheduler-log/_view.php) is a read-only textarea sized to read
   stack traces without scrolling per line. */
.scheduler-log-message {
    width: 100%;
    height: 400px;
}
/* The command help tab's detail card (scheduler-command/_help_tab_command_details.php). */
.scheduler-command-info {
    border: 1px solid silver;
    background-color: white;
    padding: 20px;
}
