/* ===================================================
   SEO Platform — Component Overrides
   Adapter layer for third-party stacks (DaisyUI, HTMX, Alpine)
   and project shell utilities (TailAdmin sidebar, theme bootstrap).

   Contract:
   - design-system.css  → proprietary tokens + .ds-* classes
   - component-overrides.css (this file) → everything else that has to
     live globally: DaisyUI shape overrides, HTMX runtime classes,
     Alpine [x-cloak], pre-Alpine theme icon visibility, sidebar layout,
     #_tip global tooltip element.

   Migrated from inline <style> in base.html (see seo-3mq).
   Loaded in <head> AFTER design-system.css so .ds-* tokens resolve.
   =================================================== */

/* ─── Alpine bootstrap ─── */
[x-cloak] { display: none !important; }

/* ─── Pre-Alpine theme icon visibility ───
   Default = light (corporate): sun hidden, moon shown.
   data-theme is set synchronously by IIFE before Alpine, so this is
   correct on first paint. */
.theme-icon-sun { display: none; }
[data-theme="business"] .theme-icon-sun { display: block; }
[data-theme="business"] .theme-icon-moon { display: none; }

/* ─── Theme transition guard ───
   Suppress all transitions during theme swap to avoid color flash. */
.theme-switching,
.theme-switching *,
.theme-switching *::before,
.theme-switching *::after {
    transition-duration: 0s !important;
}

/* ─── HTMX indicator ─── */
.htmx-indicator { display: none; }
.htmx-request .htmx-indicator { display: inline-block; }
.htmx-request.htmx-indicator { display: inline-block; }

/* ─── Sidebar navigation (TailAdmin) ───
   Colors derived from DaisyUI base-content / primary.
   Light/dark collapse into single rules; --bc / --p carry the
   per-theme contrast. */
.ta-menu-section {
    font-size: 0.6875rem;
    font-weight: 600;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    padding: 0 0.75rem;
    margin-bottom: 0.375rem;
    /* seo-2pu3: was /0.5 (3.32 light / 4.13 dark — sub-AA). Hierarchy is carried by
       uppercase + weight 600 + letter-spacing + 11px, NOT by low contrast. */
    color: var(--text-muted-aa);
}

.ta-menu-item {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    padding: 0.5rem 0.75rem;
    /* 0.5rem (rounded-lg) — unified shell-control radius (seo-h4wn). All other
       shell focusables (theme_toggle, sidebar_buttons, user-menu trigger +
       items, brand logo, super-mode badge) use rounded-lg. The original 6px
       was a TailAdmin-port leftover, not a documented hierarchy intent. */
    border-radius: 0.5rem;
    font-size: 0.875rem;
    font-weight: 500;
    color: oklch(var(--bc) / 0.7);
    transition: background 0.15s ease, color 0.15s ease;
    text-decoration: none;
}
.ta-menu-item:hover {
    background: oklch(var(--bc) / 0.08);
    color: oklch(var(--bc));
}
.ta-menu-item.active {
    background: oklch(var(--p) / 0.08);
    color: oklch(var(--p));
    font-weight: 600;
}
[data-theme="corporate"] .ta-menu-item.active {
    /* Darken primary on light surface to keep WCAG AA contrast (≥4.5:1).
       --p (64% 0.12 264) against primary/0.08 over white is too light as text;
       lowering L to 0.49 (seo-oeub) restores AA. Counterpart of the dark-theme
       lift to oklch(0.73 0.12 264). Chroma calmed 0.186→0.12 by seo-qzm; L+hue
       unchanged so the AA rationale holds (reverified L1 machine). */
    color: oklch(0.49 0.12 264);
}
[data-theme="business"] .ta-menu-item.active {
    /* Lighten primary on dark surface to keep WCAG AA contrast (≥4.5:1).
       Derived from --p (64% 0.12 264) by raising lightness to ~73%. */
    background: oklch(var(--p) / 0.15);
    color: oklch(73% 0.12 264);
}
.ta-menu-item svg { flex-shrink: 0; }

/* ─── Sidebar geometry ─── */
.sidebar-desktop { width: 260px; overflow: hidden; }
/* Collapsed state — managed by class on <html>, applied synchronously
   via IIFE before Alpine to avoid a layout jump. */
html.sidebar-is-collapsed .sidebar-desktop { width: 72px; }
html.sidebar-is-collapsed .sidebar-desktop .ta-menu-text,
html.sidebar-is-collapsed .sidebar-desktop .ta-menu-section,
html.sidebar-is-collapsed .sidebar-desktop .ta-logo-text,
html.sidebar-is-collapsed .sidebar-desktop .ta-version { display: none; }
html.sidebar-is-collapsed .sidebar-desktop .ta-menu-item {
    justify-content: center;
    padding: 0.5rem;
}
/* seo-3xtb.4: the pending-registrations counter in the COLLAPSED 72px rail.
   Hiding it would drop the one signal the rail still has room for; leaving the
   pill would put a two-character plate beside an icon in a lane whose ten other
   rows are a lone centred glyph, and `justify-content: center` would shove the
   icon off-centre. So the count becomes a DOT pinned to the icon's top-right —
   the Modernize collapsed idiom, and the same information at the resolution the
   rail affords («something is waiting»), with the exact number one hover away in
   the expanded state. The accessible name stays on the root, so the number is
   still announced. The row is made a containing block HERE rather than globally:
   `position: relative` on `.ta-menu-item` in every state would change nothing
   visible but would widen the blast radius of this rule to eleven nav rows on
   two shells. */
html.sidebar-is-collapsed .sidebar-desktop .ta-menu-item { position: relative; }
/* seo-3xtb.4 collapsed-rail DOT. The 20px glyph sits at x 26–46 / y +8–28 inside the
   47×36 item (measured 2026-09-11, visual-final-check L3 R2 HIGH): the first cut put
   the dot at top 0.35rem / right 0.9rem = INSIDE the glyph box, painted with the pill's
   10% tint only — 1.07:1 on the white rail, i.e. invisible. The dot therefore
   (1) anchors to the glyph's top-right corner, clear of it, (2) fills SOLID --wa
   (a non-text indicator: ≥3:1 on both rails), (3) wears a 2px ring of the rail
   colour (`--surface-body`, the shell surface alias the rail paints with) so it never
   touches the glyph,
   and (4) the root drops its inline line-box (line-height 0) so the pill is not
   pushed to the bottom of a 21px-tall anchor. The design-system `!important` colour
   lift on .ds-badge-tint only sets `color`; the dot carries no text, so that stays
   irrelevant here. */
html.sidebar-is-collapsed .sidebar-desktop .ta-menu-badge {
    position: absolute;
    top: 0.2rem;
    right: 0.3rem;
    margin: 0;
    line-height: 0;
}
html.sidebar-is-collapsed .sidebar-desktop .ta-menu-badge > span {
    min-width: 0;
    width: 0.5rem;
    height: 0.5rem;
    padding: 0;
    border-radius: 9999px;
    font-size: 0;
    line-height: 0;
    background-color: oklch(var(--wa));
    box-shadow: 0 0 0 2px var(--surface-body);
}
/* Animation only after Alpine init (toggled via x-init), so first paint
   doesn't visibly transition from default to saved width. */
.sidebar-animated { transition: width 0.2s ease; }

.sidebar-badge {
    font-size: 0.6rem;
    background: oklch(var(--bc) / 0.07);
    color: oklch(var(--bc) / 0.35);
    padding: 0.1rem 0.4rem;
    border-radius: 0.25rem;
    font-weight: 500;
}

/* ─── Modernize component overrides — card ───
   Light: Modernize float; dark: none (card surface == body surface). */
[data-theme="corporate"] .card {
    box-shadow: rgba(145,158,171,0.2) 0px 0px 2px 0px, rgba(145,158,171,0.12) 0px 12px 24px -4px;
}
[data-theme="business"] .card {
    box-shadow: none;
}

/* ─── seo-mp3n: business .ds-mc-card edge definition ───
   After seo-ojuq surface tokens unification, --surface-card === --surface-body
   in business (both navy oklch(var(--b1))). Card boundary невидим без отдельной
   visual cue. Shadow alone (см. --shadow-dark-md в design-system.css) даёт
   subtle drop, но edge всё ещё ambiguous. 1px subtle navy border (using existing
   --surface-popover-border token, не новый) даёт clear card outline без
   breaking surface symmetry. Scoped к business — corporate cards already have
   strong drop shadow from Modernize palette. */
[data-theme="business"] .ds-mc-card {
    border: 1px solid var(--surface-popover-border);
}

/* ─── Modernize component overrides — table ───
   Reuse the popover-border token for both themes. */
[data-theme="corporate"] .table tr,
[data-theme="business"]  .table tr { border-bottom-color: var(--surface-popover-border); }

/* ─── Cell link group hover ───
   Highlight cell text when hovering the named-group parent. */
.group\/proj:hover .proj-text,
.group\/task:hover .task-text,
.group\/user:hover .user-text { color: oklch(var(--p)); transition: color 0.15s; }

/* ─── Modernize component shape overrides (D19) ─── */
/* seo-utqy (D3): label weight 500 — Modernize's own button weight (the premium demo's
   shadcn `font-medium`); D19's 600 came from the free HTML snippet. */
.btn { border-radius: 0.375rem; font-weight: 500; }
.input, .select, .textarea { border-radius: 0.375rem; }
.modal-box { border-radius: 1rem; }

/* seo-s0aw: the at-rest border of an outlined form control on the b1 card surface.
   daisyui paints `.input-bordered`/`.select-bordered`/`.textarea-bordered` at
   `oklch(var(--bc) / .2)` — live canvas-composited 1.52:1 on the white card, 1.75:1 on
   the business one, below the 3:1 non-text floor (WCAG 1.4.11) in BOTH themes. In dark
   the control's own fill is byte-identical to the card behind it (rgb(28,37,54)), so
   that hairline is the ONLY thing marking the field: the bead's «поле почти невидимо».
   Same compliance-floor policy and the same measured alphas as `.ds-ctrl-border`
   (design-system.css, seo-6zb) — this is that boundary, on the atom instead of a class,
   so no callsite changes: corporate /0.47 = 3.06:1, business /0.40 = 3.16:1. Do not
   raise without a design decision; do not lower below 3:1.

   Specificity contract — BOTH halves stay at (0,1,0), the same as daisyui's own
   `.input-bordered`; they win over it only because this file loads after
   daisyui.min.css. That is why the dark half is wrapped in `:where()`, and the reason
   is the POPOVER rule, not focus: `.ds-surface-popover :is(.input, .textarea, .select)`
   is (0,2,0) but lives in design-system.css, which loads EARLIER — so it can only win
   on specificity. A bare `[data-theme="business"]` prefix here would be (0,2,0), tie
   with it, and win on file order, dropping every dark popover control from its
   surface-measured 0.42 to 0.40 = 2.98:1 on --b2 (mutation-verified: that single edit
   reddens tests/browser/shared_ui/test_popover_input_border_contrast.py[dark]).
   Focus is safe either way: `.input:focus` & co. are (0,2,0) AND sit below this block
   in this same file, so they outrank the light half on specificity and a hypothetical
   (0,2,0) dark half on order — the browser pin asserts the --p border in both themes.
   daisyui `.input:disabled` is (0,2,0) — a pseudo-class counts in the CLASS column, not
   the element one — so an inactive control keeps its --b2 border by specificity alone,
   with no dependence on file order (1.4.11 exempts it; same ruling as
   `.ds-ctrl-border:disabled`). `.ds-ctrl-border` is
   (0,1,0) too and loads earlier, but no element carries both (that class marks outlined
   BUTTONS and the page-size select wrapper), and the corporate alphas are identical.

   Supersedes the page-scoped patch of this same defect on `#kw-filters`
   (design-system.css, seo-jrp1) — removed with this change: its light /0.40 measured
   2.51:1 live, i.e. it was BELOW this floor and would now have lowered the border
   back under 3:1 on /tasks/{id}. Pins: tests/test_design_system_form_control_border.py
   + tests/browser/shared_ui/test_form_control_border_contrast.py. */
.input-bordered,
.select-bordered,
.textarea-bordered {
    border-color: oklch(var(--bc) / 0.47);
}
:where([data-theme="business"]) .input-bordered,
:where([data-theme="business"]) .select-bordered,
:where([data-theme="business"]) .textarea-bordered {
    border-color: oklch(var(--bc) / 0.4);
}

/* Checkbox: Modernize style — DaisyUI-semantic border unchecked,
   primary fill checked. Unchecked border /0.5 (was /0.2) — WCAG 1.4.11
   interactive boundary ≥3:1 on both canonical surfaces (seo-74f: /0.2
   measured 1.52:1 on white / 1.75:1 on business b1; /0.5 = 3.35:1 /
   4.13:1, single alpha clears both themes). Checked border/fill are
   pinned below (seo-60gh), not left to the DaisyUI --p rule. */
.checkbox {
    border-radius: 0.25rem;
    border-width: 1.5px;
    border-color: oklch(var(--bc) / 0.5);
}
/* seo-60gh (ensemble): the white ✓ belongs to the PRIMARY fill only. Declared on
   `.checkbox` it also painted the ✓ of a plain `.checkbox`, whose daisyui fill is
   --bc — near-white in dark — so the tick vanished (1.27:1 on /tasks/new freq-type
   boxes). Plain checkboxes are not a project idiom: every checkbox is
   checkbox-primary (task_new / admin defaults were the 7 strays, fixed in the same
   change); this scoping keeps a future stray from going white-on-white. */
.checkbox-primary {
    --chkfg: white;
}
.checkbox:focus-visible {
    outline: 2px solid oklch(var(--p));
    outline-offset: 2px;
    box-shadow: none;
}
/* seo-60gh: the checked fill of .checkbox-primary came from the raw --p (L 0.64 →
   rgb(103,138,213)), LIGHTER than the btn-primary fill the overrides pin to 0.52 —
   a white ✓ on it measured 3.40:1 in both themes (non-text floor 3:1 passed, AA
   4.5 not), and the ✓ is the only state channel of the control. Pin the fill to the
   same 0.52 L as the primary button (white on it = 5.58:1 measured, seo-qzm) — one primary
   ink for «pressed/selected» across buttons and checkboxes, both themes. */
.checkbox-primary:checked,
.checkbox-primary[aria-checked="true"] {
    /* daisyui paints the checked box with background-color from --p DIRECTLY (not
       via --chkbg, which only draws the ✓ cut-outs) — both must move together. */
    --chkbg: oklch(0.52 0.12 264);
    background-color: oklch(0.52 0.12 264);
    border-color: oklch(0.52 0.12 264);
}
/* dark: the 0.52 fill alone measured 2.57:1 against the task form's card surface
   (≈2.75:1 nominal on bare --b1) — the 1.4.11 boundary floor
   (3:1) that seo-74f set for the UNCHECKED border would regress on the checked box.
   Same answer as btn-primary in business: a lighter 0.60 border draws the edge
   (3.85:1 on --b1 / 3.33:1 on --b2); since seo-9z0m the business fill is 0.56 like the
   button's (3.27:1 on --b1, ✓ 4.70:1) — light keeps 0.52 / 5.58:1. */
[data-theme="business"] .checkbox-primary:checked,
[data-theme="business"] .checkbox-primary[aria-checked="true"] {
    /* seo-9z0m: one primary ink — the business fill follows the button to 0.56
       (3.27:1 on --b1, the white ✓ 4.70:1); the 0.60 edge stays. */
    --chkbg: oklch(0.56 0.12 264);
    background-color: oklch(0.56 0.12 264);
    border-color: oklch(0.60 0.12 264);
}

/* Form-controls focus — Modernize pattern (seo-bxfo).
   DaisyUI v5 default sets outline 2px oklch(--bc / 0.2) + offset 2px on
   .input:focus and .input:focus-within — the offset gap reads as a
   "thick pale halo" while the outline itself is near-invisible. Modernize
   Free uses border-color recolor with no ring (forms.html:
   focus:border-blue-600 focus:ring-0).

   :focus + :focus-visible cover real <input>.input — receives focus via
   both mouse and keyboard. :focus-within covers the <div class="input">
   wrapper case (search bar in data_table.html — wrapper holds chips +
   a child <input>, fires :focus-within from the child without ever
   getting :focus itself, so without this the DaisyUI halo stays as a
   visible "double border" outside the 1px wrapper border).

   seo-s0aw: the ring is BACK, but FLUSH — `outline-offset: 0`, the
   `.ds-focus-ring-flush` idiom already in this file. seo-bxfo's finding stands and is
   what offset 0 preserves: daisyui's ring was near-invisible (bc/0.2) AND held two
   pixels off the edge, so it read as a detached pale halo. What changed is the REST
   state. While rest was daisyui's bc/0.2 hairline, recolouring that one pixel to --p was
   a big step; now that seo-s0aw lifted rest to the 1.4.11 floor (0.47 / 0.40), the
   recolour moves the SAME pixel from 3.06:1 to 3.41:1 in light and 3.16:1 to 4.51:1 in
   dark — only 1.11:1 and 1.43:1 BETWEEN the two states, i.e. a hue swap at nearly equal
   luminance, identical for mouse and keyboard, with hover a no-op. WCAG 2.4.13 counts
   the pixels the indicator ADDS: 2px of --p laid on the bare card surface = 3.41:1 light
   / 4.51:1 dark. Offset 0 keeps the ring touching the (also --p) border, so the two read
   as ONE thick primary edge with no grey gap — never two concentric rings.
   `box-shadow: none` still kills daisyui's own focus shadow.
   Consequence for the DS vocabulary: form controls now DO grow an outline, so the
   «border-recolor, never an outline» wording of seo-bxfo is retired. The
   NON_OUTLINE_ALLOWLIST entries in test_focus_audit.py and scripts/visual_machine_layer.py
   stay LOAD-BEARING, just for a new reason: the shared audit requires
   `outline-offset ∈ {2px, -2px}` and these controls are the one family drawing at
   offset 0, so without the allowlist every field on every page would read as a
   violation. Whether to teach the audit about offset 0 instead is seo-5dyu.
   A callsite must NOT pin its own outline utility on such a control: `focus:outline-none`
   (Tailwind = a 2px TRANSPARENT outline at offset 2, and the Play CDN injects it after
   this file) silently replaced this ring with nothing on the kw-add textarea, and
   `.ds-focus-ring` (offset 2, later in this file) drew a SECOND concentric ring around
   four admin selects — both removed with this change. One owner per state.
   Pins: test_form_focus_modernize.py (shape) + test_form_control_border_contrast.py
   (measured, both themes) + tests/test_design_system_form_control_border.py (source). */
.input:focus,
.input:focus-visible,
.input:focus-within,
.select:focus,
.select:focus-visible,
.select:focus-within,
.textarea:focus,
.textarea:focus-visible,
.textarea:focus-within {
    outline: 2px solid oklch(var(--p));
    outline-offset: 0;
    border-color: oklch(var(--p));
    box-shadow: none;
}

/* Focus ring flush with element edge — outline-offset:0 (no gap, no overshoot).
   Use for buttons embedded inside a bordered container (e.g. clear-search ×
   inside .input.input-bordered) so the ring stays WITHIN the parent border. */
.ds-focus-ring-flush:focus-visible {
    outline: 2px solid oklch(var(--p));
    outline-offset: 0;
    border-radius: var(--radius-control);
}

/* Shell focus indicators — keyboard-only (seo-2ph8 follow-up to seo-bxfo).
   Sidebar nav links + opt-in .ds-focus-ring class for shell icon-buttons,
   logo, and standalone interactive elements without DaisyUI focus styles.
   :focus-visible only (not :focus) — mouse clicks shouldn't draw the ring,
   matches Modernize/WCAG 2.4.7 standard. */
.ta-menu-item:focus-visible,
.ds-focus-ring:focus-visible {
    outline: 2px solid oklch(var(--p));
    outline-offset: 2px;
}

/* Inset variant — dropdown rows where neighbours are touching. Positive
   offset visually leaks into adjacent row, negative pulls the ring inside
   the row geometry. */
.ds-focus-ring-inset:focus-visible {
    outline: 2px solid oklch(var(--p));
    outline-offset: -2px;
}

/* DaisyUI .btn focus — unify with the rest of the form/shell focus contract
   (primary outline). DaisyUI v5 default uses `outline: 2px solid currentColor`
   which for .btn-outline без semantic reads as a thick base-content ring
   doubling the existing border, and for .btn-error splits the
   keyboard affordance into per-variant colours. Semantic signal is already
   carried by fill/text — focus indicator stays primary for parity with
   .input/.select/.textarea, .ds-tr-cell-link, .ds-icon-btn. (seo-3n0o) */
.btn:focus-visible {
    outline: 2px solid oklch(var(--p));
    outline-offset: 2px;
}

/* ─── seo-2pu3: WCAG AA on button / control LABELS ───
   The brand --p (L0.64) yields white-on-fill only ~3.45:1 and --p-as-text ~3.1–3.45:1
   on dark surfaces — both below AA for the label. Fixed at the COMPONENT level, NOT on
   the --p/--er/--wa tokens, so links / active states / borders keep the brand hue+L.
   Filled buttons darken the FILL in LIGHT (specificity: bare `.btn-primary` = 1 class,
   loads after daisyui → wins; `.btn-outline.btn-primary` = 2 classes → its transparent
   fill is untouched); in BUSINESS the primary fill is 0.56 since seo-9z0m (see that
   block below — the dark surface bounds the fill from both sides). Primary-as-TEXT controls (outline buttons, active pagination) lift the text
   in dark, mirroring the existing .ds-tab-active / .ta-menu-item.active dark lift.
   Values verified ≥4.5:1 via visual_machine_layer.py both themes.
   seo-oeub (the follow-up polish): deepened the primary — filled button fill 0.57→0.52
   (hover 0.52→0.47; white label AA 4.63→5.69) and the LIGHT primary-as-text canon
   0.53→0.49 (deeper, less violet on white, more AA margin). The DARK canon stays 0.73/0.75:
   dark primary-text sits on primary-tinted surfaces already at the AA floor (L0.73 on the
   double-tint tab-counter = 4.34, L0.68 = 3.68 FAIL), so it CANNOT darken. Hence the
   per-theme asymmetry: light deepens, dark holds. */
/* Filled buttons — darken the FILL. :not(.btn-outline) so outline variants (which
   daisyui paints transparent via the 1-class `.btn-outline`) are NOT filled by this
   same-specificity-but-later rule. */
/* :not(.ds-cta-disabled) — the disabled-CTA muted style (var(--ds-dead-plate) fill, design-system.css)
   is 1-class; without this exclusion the 2-class rule below would repaint a disabled CTA with
   the full primary fill (+ its muted label → sub-AA). Disabled controls keep their muted look
   and are WCAG-exempt (detector skips [disabled]/[aria-disabled]).
   seo-qzm: all accent FILLS + primary-as-text below calmed chroma 0.186/0.163/0.158→0.12
   (L+hue preserved → AA ratios essentially unchanged, reverified L1 both themes).
   Error fill (btn-error) kept loud — alarm semantics. */
.btn-primary:not(.btn-outline):not(.ds-cta-disabled) { background-color: oklch(0.52 0.12 264); border-color: oklch(0.52 0.12 264); }
.btn-primary:not(.btn-outline):not(.ds-cta-disabled):hover { background-color: oklch(0.47 0.12 264); border-color: oklch(0.47 0.12 264); }
.btn-error:not(.btn-outline):not(.ds-cta-disabled) { background-color: oklch(0.577 0.223 27.3); border-color: oklch(0.577 0.223 27.3); }
.btn-error:not(.btn-outline):not(.ds-cta-disabled):hover { background-color: oklch(0.527 0.223 27.3); border-color: oklch(0.527 0.223 27.3); }
/* seo-utqy (D2): no `.btn-warning` override — amber marks a state, never an action, so no
   button of ours wears it (guarded by the `btn.warning_action` atom and the CTA / confirm /
   row-action validations). */

/* seo-i792 — filled primary/error have no visible BOUNDARY on the dark surfaces.
   A filled .btn carries no ring or shadow, so its fill IS its whole boundary, and the fill
   is what has to clear the 3:1 non-text floor of WCAG 1.4.11 against the surface behind it.
   Measured (canvas pixel readback, not token math — computing from the oklch tokens gives
   4.51:1 and is WRONG):
       primary        2.75:1 on --b1 · 2.38:1 on --b2   (0.52 fill; 3.27 / 2.82 since seo-9z0m lifted it to 0.56)
       primary:hover  2.21:1 · 1.91:1   ← hover DARKENED the fill, so it was the worst case
                                          (business hover no longer darkens — seo-9z0m below)
       error          3.16:1 · 2.73:1
       error:hover    2.63:1 · 2.28:1
   Nothing mechanical sees this: the contrast axes score the white LABEL on the button
   (5.58:1, passes) and the chroma-harmony check whitelists .btn-primary outright.
   Fix is border-colour only — .btn already carries a 1px border and box-sizing: border-box,
   so geometry does not move, the fill and the label stay exactly as they were, and the
   accent does not get louder (seo-qzm's calmed chroma is preserved: same C and H, +L on the
   1px edge alone).
   Scope is the whole class, checked rather than assumed: the then-present warning fill cleared
   it unaided (seo-utqy removed that variant — amber never paints an action). Disabled
   variants are WCAG-exempt and keep their muted look. */
/* seo-9z0m: the edge alone was the whole presence of the primary CTA in business — the
   0.52 fill sat at 2.75:1 on the card, under the 3:1 non-text floor, blue on dark blue,
   and hover DARKENED it further (2.21:1). The fill itself is lifted to 0.56/0.12/264:
   3.27:1 on --b1, 2.82:1 on --b2 (the 0.60 edge still draws 3.33:1 there), white label
   4.70:1. Canvas readback, business surfaces: at chroma 0.12 the fill can only live
   ABOVE 0.54 and BELOW 0.58 — at 0.54 the body sits on the 3:1 floor (2.995), at 0.58
   the white label drops under AA 4.5 (4.34; 0.60 = 3.99) — so the hover cannot move the
   fill either way: the dark hover keeps the 0.56 fill and brightens the EDGE to 0.73
   (6.35:1 on --b1, 5.49:1 on --b2, 1.94:1 against the fill), doubled to a 2px ring by an
   inset shadow of the same tone — a 1px ring at 1.94:1 against its own fill was the whole
   hover cue and the dark lens of the seo-9z0m ensemble read it as marginal — the same
   «hover = louder, never towards the surface» rule the row-action family follows
   (seo-np5b). Light theme untouched (5.58:1 fill on white, hover darkens to 0.47).
   The outline variant's hover/active FILL (`.btn-outline.btn-primary`, 0.47 in both
   themes, further below) is the same class in business — 2.21:1 on --b1 — and gets the
   same 0.56 fill + 0.73 ring there. */
[data-theme="business"] .btn-primary:not(.btn-outline):not(.ds-cta-disabled) {
    background-color: oklch(0.56 0.12 264);
    border-color: oklch(0.60 0.12 264);   /* 3.85:1 on --b1 · 3.33:1 on --b2 */
}
[data-theme="business"] .btn-primary:not(.btn-outline):not(.ds-cta-disabled):hover,
[data-theme="business"] .btn-outline.btn-primary:not(.ds-cta-disabled):hover,
[data-theme="business"] .btn-outline.btn-primary:not(.ds-cta-disabled):active {
    background-color: oklch(0.56 0.12 264);
    border-color: oklch(0.73 0.12 264);   /* 6.35:1 on --b1 · 5.49:1 on --b2 · 1.94:1 vs the fill */
    box-shadow: inset 0 0 0 1px oklch(0.73 0.12 264);   /* + the border = a 2px ring */
}
[data-theme="business"] .btn-error:not(.btn-outline):not(.ds-cta-disabled),
[data-theme="business"] .btn-error:not(.btn-outline):not(.ds-cta-disabled):hover {
    border-color: oklch(0.62 0.223 27.3); /* 3.79:1 on --b1 · 3.28:1 on --b2 */
}

/* Outline/text primary controls = primary AS TEXT on a surface → lift per-theme
   (parity with .ta-menu-item.active: darken on light, lighten on dark). */
[data-theme="corporate"] .btn-outline.btn-primary { color: oklch(0.49 0.12 264); border-color: oklch(0.49 0.12 264); }
[data-theme="business"]  .btn-outline.btn-primary { color: oklch(0.73 0.12 264); border-color: oklch(0.73 0.12 264); }
/* On :hover/:active the outline-primary fills in (daisyui outline→filled affordance) with the
   UN-darkened --p, but our at-rest `color` (0.49/0.73, 2-class) beats daisyui's --pc → dark
   text on light-primary fill ≈1.2–1.8, sub-AA. Invisible to the at-rest `contrast` axis;
   caught by `interactive-contrast` (seo-vpyg). Fix: drive hover/active to the SAME darkened
   fill + white as a filled .btn-primary:hover (measured 5.69 both themes) instead of letting
   the light --p through. :not(.ds-cta-disabled) keeps a disabled outline-CTA muted. */
.btn-outline.btn-primary:not(.ds-cta-disabled):hover,
.btn-outline.btn-primary:not(.ds-cta-disabled):active {
  background-color: oklch(0.47 0.12 264);
  border-color: oklch(0.47 0.12 264);
  color: #fff;
}
[data-theme="corporate"] .ds-page-active { color: oklch(0.49 0.12 264); }
[data-theme="business"]  .ds-page-active { color: oklch(0.73 0.12 264); }

/* ─── seo-bma: WCAG AA on daisyUI alert fills ───
   daisyui `.alert` paints `background-color:var(--alert-bg)`; `.alert-error` /
   `.alert-warning` set `--alert-bg` to `oklch(var(--er)/1)` / `oklch(var(--wa)/1)`
   (no color-mix). White-on-error (--erc) measured 3.79:1 and dark-on-warning
   (--wac) 4.3:1 corporate — both sub-AA. Darken the error fill / lighten the
   corporate warning fill to the filled-button levels of the time (.btn-error, and the amber
   0.60 fill seo-utqy later removed from buttons) → white-on-error 4.85:1, dark-on-warning
   ≥4.5:1. The semantic
   --er/--wa tokens stay untouched, so btn/badge/ds-stat-tile/ds-row-action/
   ds-filter-chip keep the brand hue+L. business alert-warning (9.5:1) and
   alert-success (6.8:1) already pass → left at daisyui default. */
.alert-error { background-color: oklch(0.577 0.223 27.3); }
[data-theme="corporate"] .alert-warning { background-color: oklch(0.60 0.12 70); }

/* UI-Kit TOC nav links (seo-oeub) — opaque per-theme primary, SAME canon as
   .ds-page-active / .ds-tab-active. Replaces a kit-only text-primary/80 whose 80%
   alpha made the rendered link lightness background-dependent (flipped between
   themes) — a style with no match on the real site. Opaque → stable + AA both
   themes (light 0.49 ≈ 6.5:1 on white, dark 0.73 ≈ 5.2:1 on navy). */
[data-theme="corporate"] .ds-toc-nav a { color: oklch(0.49 0.12 264); }
[data-theme="business"]  .ds-toc-nav a { color: oklch(0.73 0.12 264); }

/* Shell breadcrumb anchors — container lives in layouts/{admin,dashboard}.html.
   Canonical pages render breadcrumb as <span> (not focusable), anchors only
   appear on out-of-scope pages, but the container itself is chain-in-scope.
   border-radius matches .btn / .input shape token. (seo-3n0o) */
.ds-breadcrumb-bar a:focus-visible {
    outline: 2px solid oklch(var(--p));
    outline-offset: 2px;
    border-radius: 0.375rem;
}

/* ─── Global tooltip element (#_tip) ───
   Triggered by data-tip / data-tip-collapsed / title fallback (tooltip.js).
   Surface and text colors come from .ds-tooltip-surface (design-system.css).
   Light: dark surface against light page = strong contrast.
   Dark: surface elevated to slate-700 + brighter border + deeper shadow,
   because slate-800 fill blends into the sidebar and the dark drop-shadow
   disappears on a dark page. */
#_tip {
    position: fixed;
    z-index: 9999;
    pointer-events: none;
    padding: 6px 12px;
    border-radius: 8px;
    font-size: 12px;
    font-weight: 500;
    line-height: 1.4;
    white-space: nowrap;
    font-family: 'IBM Plex Sans', sans-serif;
    transform: translate(-50%, -100%);
    box-shadow: 0 8px 24px rgba(0,0,0,.35), 0 2px 6px rgba(0,0,0,.2);
    /* seo-rtke: backdrop-filter: blur(6px) removed — both --tooltip-surface
       tokens are OPAQUE rgb() fills, so the blur had zero visible effect while
       still promoting a compositor layer (a known source of stale-position
       flashes when a reused fixed element is teleported). */
}
