/* ═══════════════════════════════════════════════════════════════════
   WerkWyse site CSS — small overrides on top of the compiled theme
   (css/werkwyse-bootstrap.css). Loaded after the theme and vendor CSS.
   ═══════════════════════════════════════════════════════════════════ */

/* Column-aligned digits everywhere (tables, totals, rates) */
body {
    font-variant-numeric: tabular-nums;
}

/* Tab hover: fill the full border box instead of Bootstrap's default
   transparent bottom, which looked broken against the tab content */
.nav-tabs {
    --bs-nav-tabs-link-hover-border-color: var(--bs-border-color) var(--bs-border-color) var(--bs-border-color);
}

/* #42 filter offcanvas: the header joins the topbar row-1 band (61px) so
   its bottom border continues row 1's — stock Bootstrap 1rem-padding
   header is 57px, leaving the two lines 4px apart (KHR report). */
.offcanvas-header {
    min-height: 61px;
}

/* #47 dashboard: Chart.js needs an explicit-height wrapper for
   responsive resizing (maintainAspectRatio: false in werkwyse-ui.js). */
.chart-wrap {
    position: relative;
    height: 280px;
}

/* #67 chart palette — VARIANT B (Firdaus's demo review): the stock
   Chart.js default palette (docs/latest/general/colors) everywhere —
   blue/red/teal/orange/purple/yellow for pie slices, blue + red for the
   two-series bars (chargeable/fee = blue, nonchargeable/cost = red),
   blue for single-series bars. SAME hexes in both themes (the stock
   experience — no dark tints); the palette's own grey stays RESERVED
   for the Unassigned (NULL FK) slice. Consumed by js/werkwyse-ui.js via
   getComputedStyle; the MutationObserver rebuild picks values up on
   theme flip. --bs-primary/--bs-secondary are deliberately NOT these
   (theme chrome stays untinted). */
:root {
    --ww-chart-1: #36a2eb; /* chart.js blue */
    --ww-chart-2: #ff6384; /* chart.js red */
    --ww-chart-3: #4bc0c0; /* chart.js teal */
    --ww-chart-4: #ff9f40; /* chart.js orange */
    --ww-chart-5: #9966ff; /* chart.js purple */
    --ww-chart-6: #ffcd56; /* chart.js yellow */
    --ww-chart-unassigned: #c9cbcf;
    /* folded sub-1% named slices — a darker grey than Unassigned: Other
       IS real data, Unassigned is a missing FK. */
    --ww-chart-other: #6b7280;
    --ww-chart-chargeable: #36a2eb;
    --ww-chart-nonchargeable: #ff6384;
}

[data-bs-theme="dark"] {
    --ww-chart-1: #36a2eb;
    --ww-chart-2: #ff6384;
    --ww-chart-3: #4bc0c0;
    --ww-chart-4: #ff9f40;
    --ww-chart-5: #9966ff;
    --ww-chart-6: #ffcd56;
    --ww-chart-unassigned: #c9cbcf;
    --ww-chart-other: #6b7280;
    --ww-chart-chargeable: #36a2eb;
    --ww-chart-nonchargeable: #ff6384;
}

/* #70 chart-color badges — a CATEGORICAL badge set on the same
   --ww-chart-* vars the charts read, so a category badge and its pie
   slice/bar share one hex by construction (can't drift apart). Dark
   text on all seven: every chart hex fails white-text WCAG, and the
   vars are theme-invariant so one ruleset covers both color modes
   (purple is the marginal one at 4.19:1 — avoid it for high-traffic
   categories). Grammar: categories/tags only, NEVER status — success/
   warning/danger stay semantic (text-bg-*); grey stays reserved for
   "unassigned/other/none", matching its chart meaning. */
.text-bg-chart-blue    { background-color: var(--ww-chart-1); color: #212529; }
.text-bg-chart-red     { background-color: var(--ww-chart-2); color: #212529; }
.text-bg-chart-teal    { background-color: var(--ww-chart-3); color: #212529; }
.text-bg-chart-orange  { background-color: var(--ww-chart-4); color: #212529; }
.text-bg-chart-purple  { background-color: var(--ww-chart-5); color: #212529; }
.text-bg-chart-yellow  { background-color: var(--ww-chart-6); color: #212529; }
.text-bg-chart-grey    { background-color: var(--ww-chart-unassigned); color: #212529; }

/* #48: cap long flush lists (the unused-activities card) at chart height. */
.list-scroll {
    max-height: 280px;
    overflow-y: auto;
}

/* Dashboard grid rhythm (demo feedback 2026-08-01): the grid ROWS own
   the vertical spacing (g-4 mb-4 gutters) — cards inside row columns
   carry NO bottom margin. The c-card component's baked-in mb-4 can't be
   attribute-patched (cotton drops extra attrs — the #51 gotcha), so
   it's suppressed here: card mb-4 + row gutter doubled the gap (48px
   next to the rhythm's 24px). !important: Bootstrap's .mb-4 utility
   carries it.

   Equal-height rows (#66): every in-row card fills its stretched grid
   column, so the row's cards — and their footers, via the stock flex
   card's growing .card-body — level out. height:100% is only safe
   BECAUSE the margin above is zeroed: with mb-4 in play, a 100%-high
   card in a stretched column absorbs its own margin and overflows the
   row (which is why Bootstrap's $card-height:100% Sass default was
   rejected — house-wide it would hit every detail page's c-card). The
   same cotton gotcha keeps .h-100 off c-card's root, so the utility
   route can't reach in-row component cards — this scoped rule is the
   seam. */
.dashboard-grid .row > [class*="col"] > .card {
    margin-bottom: 0 !important;
    height: 100%;
}

/* Stretched-row fill (Firdaus 2026-08-21, post-#69 demo review): the
   #66 rule stretches the card, Bootstrap's flex card grows the
   .card-body — but a chart-wrap keeps its declared height and leaves a
   dead band below the canvas (most visible: Firm hours next to the tall
   Completion card). Let the wrap grow with the body: only bodies whose
   direct child is a chart-wrap become flex columns (stat cards and
   empty-state bodies untouched); the wrap's declared height (280px
   default, or the adaptive inline height) stays the floor via
   flex-basis — nothing shrinks. Hand-rolled cards (no .card-body:
   flush lists/tables) instead pin their footer with margin-top:auto —
   on c-cards that's a no-op because the grown body already consumes
   the free space. */
.dashboard-grid .card-body:has(> .chart-wrap) {
    display: flex;
    flex-direction: column;
}

.dashboard-grid .card-body:has(> .chart-wrap) > .chart-wrap {
    flex: 1 1 auto;
}

.dashboard-grid .card > .card-footer {
    margin-top: auto;
}

/* Completion banner progress track (demo feedback 2026-08-01): the
   theme's card bg == secondary-bg in BOTH modes, so the stock track
   (var(--bs-secondary-bg)) was invisible on the card. The palette's
   secondary stone at 35% alpha reads as a neutral rail in both modes —
   darker on the light card, lighter on the dark one — without a second
   "data" color fighting the teal fill (Firdaus-approved mock). */
.dashboard-grid .progress {
    background-color: rgba(120, 113, 108, 0.35);
}

/* Row headers (#90 Part 2, §2.4 — urus anatomy): icon (primary accent)
   + full-strength title directly above a logical card group (2+ cards
   that are one idea — personal: Work days, Annual leave); a row of
   unrelated topics gets none. The h2 keeps the document outline honest;
   the 8px margin-bottom (against the 24px row rhythm above it) binds
   the header to the group it names. The anatomy also carries urus's
   optional subtitle line and right-slot controls — unused today. */
.dashboard-grid .dashboard-row-header {
    font-size: 1rem;
    font-weight: 600;
    margin-bottom: 0.5rem;
}

.dashboard-grid .dashboard-row-header > i {
    color: var(--bs-primary);
}

/* ═══════════════════════════════════════════════════════════════════
   GLOBAL HTMX PROGRESS BAR (#htmx-progress)
   Thin top-of-page bar (YouTube/NProgress style) shown during HTMX
   requests. Created and driven by js/werkwyse-ui.js (#115: counted at
   htmx:beforeSend → trickle, completed on htmx:afterRequest → done +
   fade), with a ~120ms delay-to-show so fast swaps stay flicker-free.
   Port of urus-web's mechanism (urus.css / urus-ui.js).

   #115 defense-in-depth: visibility rides on htmx's own .htmx-request
   class — htmx strips that class from history snapshots, so a restored
   (dirty-snapshot) bar can never render even before JS removes it. The
   ungated rule must not come back. The done-fade rule below is
   untouched: done only applies once the tracker is empty, i.e. no
   .htmx-request remains in the body, so the fade wins exactly as before.

   Orange (not brand teal): contrasts with both the teal accents and
   the neutral chrome in either mode.
   ═══════════════════════════════════════════════════════════════════ */
#htmx-progress {
    position: fixed;
    top: 0;
    left: 0;
    width: 0;
    height: 3px;
    background: var(--bs-orange, #fd7e14);
    box-shadow: 0 0 8px var(--bs-orange, #fd7e14);
    z-index: 9999;
    opacity: 0;
    pointer-events: none;
    transition: width 0.2s ease;
}
body:has(.htmx-request) #htmx-progress.htmx-progress-active {
    opacity: 1;
}
#htmx-progress.htmx-progress-done {
    opacity: 0;
    transition: width 0.2s ease, opacity 0.3s ease 0.1s;
}

/* ═══════════════════════════════════════════════════════════════════
   SHELL (#26) — urus layout port: viewport-locked app shell with a
   240px sectioned sidebar; #id_main is the only content scroll region.
   Rule blocks ported from urus-web's urus.css; all colors via
   var(--bs-*) theme tokens (no new tokens). Drawer/backdrop/active-sync
   behavior lives in js/werkwyse-ui.js.
   ═══════════════════════════════════════════════════════════════════ */
:root {
    --sidebar-width: 240px;
}

.werkwyse-sidebar {
    width: var(--sidebar-width);
    flex-shrink: 0;
    display: flex;
    flex-direction: column;
    background: var(--bs-secondary-bg);
    border-right: 1px solid var(--bs-border-color);
    transition: margin-left 0.3s ease, transform 0.3s ease;
}

.werkwyse-content-wrapper {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    overflow: hidden;
}

/* Content scroll region — the page itself never scrolls */
#id_main {
    flex: 1;
    overflow-y: auto;
}

/* #37 item 5 (urus parity): wider horizontal padding around the content
   cards than Bootstrap's 0.75rem container default */
#id_main .container-xxl {
    padding-left: 1.5rem;
    padding-right: 1.5rem;
}

/* #31 toasts/flashes: the channel box lives outside #id_main, so the card
   inset rule doesn't reach it — align it explicitly. Alerts drop their
   default bottom margin (the content region's pt-4 below already provides
   the gap); stacked alerts keep a 1rem gap between themselves. */
#alert-container {
    padding-left: 1.5rem;
    padding-right: 1.5rem;
}
#alert-container .alert {
    margin-bottom: 0;
}
#alert-container .alert + .alert {
    margin-top: 1rem;
}

/* Mobile (< 992px): sidebar becomes a fixed drawer over a backdrop.
   100dvh accounts for the mobile browser URL bar so the pinned
   utilities bar doesn't clip; 100vh fallback for older engines. */
@media (max-width: 991.98px) {
    .werkwyse-sidebar {
        position: fixed;
        top: 0;
        left: 0;
        height: 100vh;
        height: 100dvh;
        z-index: 1040;
        transform: translateX(-100%);
    }
    .werkwyse-sidebar.show {
        transform: translateX(0);
    }
    #sidebar-backdrop {
        position: fixed;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        background: rgba(0, 0, 0, 0.5);
        z-index: 1035;
        display: none;
    }
    #sidebar-backdrop.show {
        display: block;
    }
}

/* ═══════════════════════════════════════════════════════════════════
   TOPBAR (#27) — two-row bar ported from urus.css.
   Row 1: hamburger + page title (61px, matches the sidebar brand band)
   Row 2: hoisted contextual tabs (nav-underline, 3px active)
   ═══════════════════════════════════════════════════════════════════ */
#topbar {
    background: var(--bs-secondary-bg);
    flex-shrink: 0;
}
#topbar-row1 {
    display: flex;
    align-items: center;
    min-height: 61px;
    border-bottom: 1px solid var(--bs-border-color);
    padding: 0 1.5rem; /* #37 item 5: match the wider content padding */
    gap: 0.5rem;
}
#topbar-row1 > #topbar-title {
    min-width: 0; /* allow flex truncation — text-truncate needs this */
}

/* #40 (Bootstrap-breadcrumb amendment): the row-1 crumb rides the
   breadcrumb component. The divider is the docs' SVG chevron via the
   component's own CSS var — the #6c757d fill reads on both themes (an
   SVG fill can't follow --bs-breadcrumb-divider-color). Bootstrap
   mutes the ACTIVE item — the inverse of our muted-prefix grammar — so
   it is set back to body color; the h1 inside it must not inflate to
   UA heading styles. */
#topbar-title {
    --bs-breadcrumb-divider: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8'%3E%3Cpath d='M2.5 0L1 1.5 3.5 4 1 6.5 2.5 8l4-4-4-4z' fill='%236c757d'/%3E%3C/svg%3E");
}
#topbar-title .breadcrumb-item.active {
    color: var(--bs-body-color);
}
.topbar-crumb-current {
    font: inherit;
    margin: 0;
}

/* Bootstrap ships no min-width utility — the crumb's truncation chain
   (nav → ol → active li) pins it explicitly (utilities convention:
   !important). */
.min-w-0 {
    min-width: 0 !important;
}
#topbar-row2 {
    padding: 0 1.5rem; /* #37 item 5: match the wider content padding */
}

/* nav-underline: primary = selected (matches the sidebar convention) */
.nav-underline {
    --bs-nav-underline-link-active-color: var(--bs-link-color);
}
.nav-underline .nav-link {
    color: var(--bs-body-color);
    padding: 0.75rem 1rem;
}
.nav-underline .nav-link:hover:not(.active) {
    background: var(--bs-tertiary-bg);
    border-bottom-color: var(--bs-border-color);
}
#subnav {
    --bs-nav-underline-border-width: 3px;
    scrollbar-width: none;
}
#subnav::-webkit-scrollbar {
    display: none;
}
#subnav .nav-link.active {
    font-weight: 600;
}
/* #37 (item 4): tab label + count badge never wrap onto two lines */
#subnav .nav-link {
    white-space: nowrap;
}

/* Row-2 right-edge fade on narrow screens: hints at horizontal scroll
   when tabs overflow (mask on the nav itself; invisible when no overflow) */
@media (max-width: 767.98px) {
    #subnav {
        mask-image: linear-gradient(to right, #000 calc(100% - 2rem), transparent 100%);
        -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 2rem), transparent 100%);
    }
}

/* Desktop: sidebar collapses via negative margin (topbar hamburger) */
@media (min-width: 992px) {
    .werkwyse-sidebar.collapsed {
        margin-left: calc(-1 * var(--sidebar-width));
    }
}

/* Sidebar section group headers (small caps) */
.sidebar-heading {
    font-size: 0.6875rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--bs-secondary-color);
    padding: 0.75rem 0.75rem 0.25rem;
    margin-top: 1rem;
    margin-bottom: 0;
}
.sidebar-heading:first-child {
    margin-top: 0;
}

/* ═══════════════════════════════════════════════════════════════════
   BRAND MARK — the wordmark is a generated inline SVG
   (_component/_brand_mark.html, scripts/build-brandmark.py from the
   vendored Dune Rise outlines): tight viewBox centers exactly, no font
   metrics; fill/stroke currentColor flips with data-bs-theme like FA.
   ═══════════════════════════════════════════════════════════════════ */
.brand-mark {
    display: block;
    margin: 0 auto;
    height: auto;
}
.brand-mark-sidebar {
    width: 12rem;
}
.brand-mark-auth {
    width: min(100%, 26rem);
}

/* Teal 61px brand band (matches urus; works in both color modes).
   #37 item 1: centered vertically AND horizontally. 2026-08-03: the
   font-based wordmark (letterspacing could never center exactly)
   became the SVG brand mark above. */
#sidebar-brand {
    min-height: 61px;
    display: flex;
    align-items: center;
    justify-content: center;
    background-color: var(--bs-primary);
    border-bottom: 1px solid var(--bs-border-color);
}

/* Primary nav — teal text + 3px left indicator when active */
#sidebar-nav .nav-link {
    color: var(--bs-body-color);
    border-left: 3px solid transparent;
    border-radius: 0;
    padding-left: 0.75rem;
    padding-right: 0.75rem;
}
#sidebar-nav .nav-link.active {
    color: var(--bs-link-color);
    font-weight: 600;
    border-left-color: currentcolor;
}
#sidebar-nav .nav-link:hover:not(.active) {
    background: var(--bs-tertiary-bg);
}

/* Utilities bar (user, notifications placeholder, logout) — pinned bottom
   via mt-auto; the theme toggle moved to the topbar (#37 items 2+7) */
#sidebar-utilities {
    min-height: 57px;
}
#sidebar-utilities .flex-fill {
    min-width: 0; /* let text-truncate shrink the user name in the flex row */
}
#sidebar-utilities .nav-link {
    color: var(--bs-body-color);
}
#sidebar-utilities .nav-link:hover {
    background: var(--bs-tertiary-bg);
}

/* Sidebar nav scrolls internally; brand + utilities stay put */
.sidebar-nav-scroll {
    flex: 1;
    overflow-y: auto;
}

/* ═══════════════════════════════════════════════════════════════════
   GLOBAL SEARCH (#36) — topbar search input + results dropdown.
   Ported from urus-web (static/css/urus.css:919-1018); all colors via
   var(--bs-*) theme tokens, so dark mode holds by construction. The
   htmx-indicator display rules are the first in the project — the
   spinner is wired via the input's hx-indicator, so the request class
   lands on .global-search-spinner (not on the input itself).
   ═══════════════════════════════════════════════════════════════════ */

/* --- Wrapper: positions the icon, spinner, and close button over the input --- */
.global-search-wrapper {
    width: 260px;
}
#global-search-mobile.global-search-wrapper {
    width: auto; /* mobile variant grows to fill topbar row 1 (flex-grow-1) */
}

/* --- Input: padded left for icon, right for spinner --- */
.global-search-input {
    padding-left: 2.25rem !important;
    padding-right: 2rem !important;
    background-color: var(--bs-body-bg) !important;
}
/* Remove the browser-native clear (×) button on type="search" */
.global-search-input::-webkit-search-cancel-button {
    -webkit-appearance: none;
}

/* --- Magnifying glass icon (left) --- */
.global-search-icon {
    position: absolute;
    left: 0.75rem;
    top: 50%;
    transform: translateY(-50%);
    color: var(--bs-secondary-color);
    font-size: 0.875rem;
    pointer-events: none;
    z-index: 4;
}

/* --- HTMX spinner (right) — htmx adds .htmx-request to the hx-indicator
       target (this span) for the request's lifetime --- */
.global-search-spinner {
    position: absolute;
    right: 0.5rem;
    top: 50%;
    transform: translateY(-50%);
    z-index: 4;
}
.global-search-spinner .htmx-indicator {
    display: none;
}
.global-search-spinner.htmx-request .htmx-indicator {
    display: inline-block;
}
.global-search-spinner .spinner-border {
    width: 0.75rem;
    height: 0.75rem;
}

/* --- Mobile close button (right, after spinner position) --- */
.global-search-close {
    position: absolute;
    right: 0.25rem;
    top: 50%;
    transform: translateY(-50%);
    z-index: 4;
    background: none;
    border: none;
    padding: 0.25rem;
    color: var(--bs-secondary-color);
    line-height: 1;
    cursor: pointer;
}
.global-search-close:hover {
    color: var(--bs-body-color);
}

/* --- Results dropdown (hidden until the JS sync shows it) --- */
.global-search-dropdown {
    display: none;
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    margin-top: 0.25rem;
    background: var(--bs-body-bg);
    border: 1px solid var(--bs-border-color);
    border-radius: var(--bs-border-radius);
    box-shadow: var(--bs-box-shadow-sm);
    max-height: 400px;
    overflow-y: auto;
    z-index: 1050;
    scrollbar-width: thin;
}

.global-search-group-header {
    font-size: 0.7rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    padding: 0.5rem 0.75rem 0.25rem;
    color: var(--bs-secondary-color);
    border-bottom: 1px solid var(--bs-border-color-translucent);
}

.global-search-item {
    display: flex;
    flex-direction: column;
    padding: 0.5rem 0.75rem;
    text-decoration: none;
    color: var(--bs-body-color);
    border-bottom: 1px solid var(--bs-border-color-translucent);
}
.global-search-item:last-child {
    border-bottom: none;
}
.global-search-item:hover {
    background: var(--bs-tertiary-bg);
    color: var(--bs-body-color);
}

/* ═══════════════════════════════════════════════════════════════════
   CONTENT SLICE 1 (#28) — urus table hierarchy + info tip.
   Rule blocks ported from urus-web's urus.css; all colors via
   var(--bs-*) theme tokens. Applies to tables inside the elevation
   cards (base_list.html, compliance_report.html) and globally to
   thead (e.g. the calendar grid).
   ═══════════════════════════════════════════════════════════════════ */

/* Table visual hierarchy (inside cards on secondary-bg):
   thead (tertiary-bg) > group header (subtle tint) > body rows (card bg)
   Column headers don't wrap — long headers scroll via .table-responsive. */
.table > thead {
    --bs-table-bg: var(--bs-tertiary-bg);
}
.table > thead th {
    white-space: nowrap;
}

/* Group header rows — subtle tint, less prominent than thead.
   Uses emphasis-color-rgb (black in light, white in dark) for auto-adaptation.
   Bootstrap's compile-time .table-secondary doesn't adapt to dark mode
   (Bootstrap issue #39272), so we override for both modes. */
.table-secondary,
.table-light {
    --bs-table-color: var(--bs-body-color);
    --bs-table-bg: rgba(var(--bs-emphasis-color-rgb), 0.06);
    --bs-table-border-color: var(--bs-border-color);
    --bs-table-hover-color: var(--bs-body-color);
    --bs-table-hover-bg: rgba(var(--bs-emphasis-color-rgb), 0.08);
}

/* Tables in cards: horizontal breathing room for first/last cells */
.card .table th:first-child,
.card .table td:first-child {
    padding-left: 1rem;
}
.card .table th:last-child,
.card .table td:last-child {
    padding-right: 1rem;
}

/* Tables in cards: prevent double bottom border when no card-footer */
.card.overflow-hidden .table-responsive:last-child .table > :last-child > tr:last-child > * {
    border-bottom-width: 0;
}

/* Table header — sortable columns use body color, not primary */
#id_table thead a {
    color: var(--bs-body-color);
}
#id_table thead a:hover {
    color: var(--bs-body-color);
}

/* <c-info-tip> metric-definition hover icon (ⓘ) — slightly smaller than
   the label text so it reads as an adornment */
.urus-info-tip {
    cursor: help;
    font-size: 0.8em;
}

/* ═══════════════════════════════════════════════════════════════════
   MODAL DIALOGS (#32) — <dialog class="werkwyse-modal">
   Native <dialog> instead of Bootstrap Modal JS; the outer dialog is
   transparent while Bootstrap's .modal-dialog / .modal-content /
   .modal-header / etc. classes still style the inner content. The
   browser handles focus trap, Escape, and top-layer stacking.

   Bootstrap's .modal-header/.modal-body/.modal-footer read --bs-modal-*
   custom properties that are ONLY defined inside `.modal`. Since we use
   <dialog>, we redeclare the same vars here so the inner padding /
   border / radius render correctly.

   Ported wholesale from urus-web (static/css/urus.css:1063-1109), class
   renamed urus-modal → werkwyse-modal.
   ═══════════════════════════════════════════════════════════════════ */
dialog.werkwyse-modal {
    padding: 0;
    border: 0;
    background: transparent;
    color: inherit;
    max-width: 100vw;
    max-height: 100vh;
    margin: auto;
    overflow: visible;

    --bs-modal-zindex: 1055;
    --bs-modal-width: 500px;
    --bs-modal-padding: 1rem;
    --bs-modal-margin: 0.5rem;
    --bs-modal-color: var(--bs-body-color);
    --bs-modal-bg: var(--bs-body-bg);
    --bs-modal-border-color: var(--bs-border-color-translucent);
    --bs-modal-border-width: var(--bs-border-width);
    --bs-modal-border-radius: var(--bs-border-radius-lg);
    --bs-modal-box-shadow: var(--bs-box-shadow-sm);
    --bs-modal-inner-border-radius: calc(var(--bs-border-radius-lg) - (var(--bs-border-width)));
    --bs-modal-header-padding-x: 1rem;
    --bs-modal-header-padding-y: 1rem;
    --bs-modal-header-padding: 1rem 1rem;
    --bs-modal-header-border-color: var(--bs-border-color);
    --bs-modal-header-border-width: var(--bs-border-width);
    --bs-modal-title-line-height: 1.5;
    --bs-modal-footer-gap: 0.5rem;
    --bs-modal-footer-bg: ;
    --bs-modal-footer-border-color: var(--bs-border-color);
    --bs-modal-footer-border-width: var(--bs-border-width);
}
dialog.werkwyse-modal::backdrop {
    background: rgba(0, 0, 0, 0.5);
    backdrop-filter: blur(2px);
}

/* #86 polish: every house dialog scrolls its BODY, viewport-bounded —
   header/footer stay pinned. The app shell's body is viewport-locked, so
   without this a long dialog's content clips with nothing to scroll
   (there is no page behind to scroll either). Stock
   .modal-dialog-scrollable's height math assumes .modal's fixed
   positioning, which this <dialog> port lacks — scope the rule to the
   dialog instead of adopting the stock class. */
dialog.werkwyse-modal .modal-content {
    display: flex;
    flex-direction: column;
    max-height: calc(100vh - 2 * var(--bs-modal-margin));
}
dialog.werkwyse-modal .modal-body {
    overflow-y: auto;
}

/* #107: the dual-submit footer (Cancel · Save & add another · Save) wraps
   under 576px — three buttons no longer fit one row on the narrowest
   phones. Stock Bootstrap already wraps (flex + gap); this pins the
   behavior house-side so a future footer change can't silently regress
   it. */
@media (max-width: 575.98px) {
    dialog.werkwyse-modal .modal-footer {
        flex-wrap: wrap;
    }
}

/* #86 (eyeball round 3): the picker table sits ON the modal surface.
   Modals are the TOP of the theme's elevation ladder (tertiary-bg), so
   the fullpage card bg (secondary-bg) inside a dialog is a step DOWN —
   a darker "hole". The card keeps only its frame (border, radius, cell
   padding, last-row rule); the thead gets a tint band for separation,
   and header labels render body color like the fullpage #id_table rule. */
#modal .modal-body .card {
    background: transparent;
}
#modal .modal-body .table > thead {
    --bs-table-bg: rgba(var(--bs-emphasis-color-rgb), 0.07);
}
#modal .modal-body thead a {
    color: var(--bs-body-color);
}

/* #94: the hybrid choice input — closed, the box IS a .form-select (the
   widget template carries that class verbatim), so the only additions
   are the wrapper's positioning context (the options panel is a
   .dropdown-menu, absolutely positioned against the nearest positioned
   ancestor — without this it anchors to the card or the page) and the
   pointer cursor a native <select> shows. */
.hybrid-choice {
    position: relative;
}
.hybrid-choice [data-hybrid-box] {
    cursor: pointer;
}
/* The options list scrolls inside the panel at seven rows tall. */
.hybrid-choice-options {
    max-height: 224px;
    overflow-y: auto;
}

/* #108: the manage-icon anatomy — a configured wrapper flexes (the box
   keeps the width; the icons ride right of it, the options panel is
   position:fixed while open so the flex row doesn't disturb it). The
   icons are bare links; the disabled ✎ dims AND drops pointer events
   (the JS disable path relies on both). */
.hybrid-choice[data-manage-create-url] {
    display: flex;
    gap: 0.25rem;
    align-items: center;
}
.hybrid-choice[data-manage-create-url] [data-hybrid-box] {
    flex: 1 1 auto;
    min-width: 0;
}
.hybrid-choice .hybrid-manage-icon {
    flex: 0 0 auto;
    padding: 0.25rem;
    color: var(--bs-secondary-color);
    text-decoration: none;
    white-space: nowrap;
}
.hybrid-choice .hybrid-manage-icon:hover:not(.disabled) {
    color: var(--bs-emphasis-color);
}
.hybrid-choice .hybrid-manage-icon.disabled {
    opacity: 0.4;
    pointer-events: none;
}

/* #97: the day-ledger Allocation/Remark cells (ClampColumn) — long text
   clamps to 2 lines with an ellipsis below lg; at lg+ it truncates to a
   single line (the per-column max-widths make text-overflow bite inside
   the flexible table cell). Full text always rides the title tooltip. */
.werkwyse-cell-clamp {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}
@media (min-width: 992px) {
    .werkwyse-cell-clamp {
        display: block;
        white-space: nowrap;
        text-overflow: ellipsis;
    }
    .werkwyse-cell-alloc {
        max-width: 24rem;
    }
    .werkwyse-cell-remark {
        max-width: 18rem;
    }
}

/* #111 prose editor (django-prose-editor): map the package's theming
   custom properties to the house Bootstrap vars. Both themes ride
   [data-bs-theme], so the editor surface, menubar and dialogs follow
   the flip for free — the package ships light-only fallbacks (#fff/#333)
   and would otherwise glare in dark mode. */
.prose-editor,
.prose-editor-dialog {
    --prose-editor-background: var(--bs-body-bg);
    --prose-editor-foreground: var(--bs-body-color);
    --prose-editor-border-color: var(--bs-border-color);
    --prose-editor-active-color: var(--bs-primary-bg-subtle);
    --prose-editor-disabled-color: var(--bs-secondary-bg);
    --prose-editor-typographic: var(--bs-border-color);
}

/* #111 UI/UX round (A2): the notes tab list clamps each note card's body
   at 5 lines with an automatic ellipsis — the full text is one click
   away on the note read verb (the clamp's target). */
.note-clamp {
    display: -webkit-box;
    -webkit-line-clamp: 5;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

/* #111 UI/UX round (A2): inside the note form card the editor sits
   DIRECT in the card body (no card-in-card) — strip the ProseMirror
   surface's own border/background so it merges with the card body, and
   give the menubar one light separator under it instead of its boxed
   chrome. The card-body class is dropped (the table-in-card grammar,
   base_list): the surface rides flush to the card border and the
   toolbar/content carry their own inset, like the table cells' first/
   last padding. Scoped to the note form; other editor surfaces keep
   the package look. Both themes ride the house vars. */
.note-form-editor .prose-editor .ProseMirror {
    background: transparent;
    border: 0;
    border-radius: 0;
    padding: 0.5rem 1rem 1rem;
    min-height: 12rem;
}
/* #128: the flush/borderless surface above has no border to recolor —
   the browser's default contenteditable focus ring reads as a stray
   blue box around the whole work area. The caret + active menubar
   carry the focus signal (the bare-textarea convention; WCAG 2.4.7
   satisfied), so the outline retires on focus. NO :focus-visible
   fallback: text-entry elements match :focus-visible on EVERY focus
   including mouse (the spec assumes typing intent), so a fallback
   would reintroduce the reported box for everyone (Firdaus ruling,
   #128 note_3917076621). */
.note-form-editor .prose-editor .ProseMirror:focus {
    outline: none;
}
.note-form-editor .prose-menubar:not(:empty) {
    background: transparent;
    box-shadow: none;
    border-bottom: 1px solid var(--bs-border-color);
    padding: 4px 1rem;
}
.note-form-editor .errorlist {
    margin: 0;
    padding: 0.5rem 1rem 0;
}

/* #129: the controller's readonly display inputs (the day-page date
   middles, the proxy calendar middle, the Set-day-type value). The
   focus ring on a form control is Bootstrap's .form-control:focus
   chrome — a teal border tint + box-shadow halo, NOT the UA outline
   (outline: 0 lives in the same Bootstrap rule) — so both levers are
   needed. The substitute focus signal is the caret (readonly inputs
   place a caret + allow selection on click); readonly, never disabled,
   tab order kept — the #128 Q5 posture (note_3917710331). */
.ww-display-input:focus {
    border-color: var(--ww-control-border);
    box-shadow: none;
}

/* #121 field-grid convention: a field's span is a 1-4 fraction of the
   fullwidth card (--ww-span, declared by the field wrapper). Each
   field-grid row's spans sum to 4 (the eight legal compositions).
   Breakpoints live ONLY in this block — templates never write col-* or
   breakpoint classes on field wrappers again. Below 768px every field
   stacks; 768-991px is uniformly 2-up with short-row grow (a lone
   second item grows to fill the line — no mid-group stagger); from
   992px the declared span decides. */
.ww-field {
    flex: 0 0 auto;
    width: 100%;
}
@media (min-width: 768px) {
    .ww-field {
        flex: 1 1 auto;
        width: 50%;
    }
}
@media (min-width: 992px) {
    .ww-field {
        flex: 0 0 auto;
        width: calc(100% * var(--ww-span, 4) / 4);
    }
    .ww-span-1 {
        --ww-span: 1;
    }
    .ww-span-2 {
        --ww-span: 2;
    }
    .ww-span-3 {
        --ww-span: 3;
    }
    .ww-span-4 {
        --ww-span: 4;
    }
}
/* Two adjacent Bootstrap .row.g-3 (back-to-back field grids) butt
   together: the second row's negative top margin cancels its children's
   gutter margin — the urus gotcha, ported one-liner. */
.ww-field-grid + .ww-field-grid {
    margin-top: 0;
}
