/* ─────────────────────────────────────────────────────────────────────────────
   Tritium window system — visual language for the new (clean-slate) dialog stack.
   Theme-aware via MudBlazor palette vars, so it follows light/dark automatically.
   Tokens up top are the knobs (radius, shadow layers, border, motion, density).
   ───────────────────────────────────────────────────────────────────────────── */
:root {
    --tt-win-radius: 14px;
    --tt-win-border: 1px solid var(--tt-border);
    --tt-win-bar-h: 44px;
    --tt-taskbar-h: 46px;
    --tt-chrome-top: 64px;   /* MudBlazor app-bar height — a maximized "respect chrome" window starts here */
    /* Layered shadow = soft ambient + tighter key light, for real depth. */
    --tt-win-shadow: 0 1px 1px rgba(0,0,0,.05), 0 10px 26px -10px rgba(0,0,0,.22), 0 28px 56px -18px rgba(0,0,0,.28);
    --tt-win-shadow-active: 0 2px 3px rgba(0,0,0,.08), 0 16px 36px -12px rgba(0,0,0,.32), 0 40px 80px -20px rgba(0,0,0,.40);
}

/* The window system's OWN theme palette, stamped on the host wrapper from IThemeService — so windows +
   taskbar are always readable regardless of whether MudBlazor's palette vars reach these app-root elements. */
.tt-scope { display: contents; }
.tt-theme-dark {
    --tt-fg: #e9e9ec;
    --tt-fg-dim: rgba(233,233,236,.62);
    --tt-surface: #202024;
    --tt-elev: #2b2b31;
    --tt-border: rgba(255,255,255,.14);
}
.tt-theme-light {
    --tt-fg: #1f2023;
    --tt-fg-dim: rgba(31,32,35,.60);
    --tt-surface: #ffffff;
    --tt-elev: #f3f3f5;
    --tt-border: rgba(0,0,0,.12);
}

/* ── Backdrop (modal only) ── */
.tt-win-backdrop {
    position: fixed; inset: 0; z-index: 1400;
    background: rgba(20, 22, 28, .38);
    backdrop-filter: blur(3px) saturate(1.05);
    animation: ttFadeIn .14s ease;
}

/* ── Window shell ── */
.tt-win {
    position: fixed;
    display: flex; flex-direction: column;
    min-width: 280px; min-height: 180px;
    background: var(--tt-surface);
    color: var(--tt-fg);
    border: var(--tt-win-border);
    border-radius: var(--tt-win-radius);
    box-shadow: var(--tt-win-shadow);
    overflow: hidden;
    /* subtle top highlight so the surface reads as raised */
    background-image: linear-gradient(to bottom, rgba(255,255,255,.04), transparent 64px);
    transition: box-shadow .18s ease;
    animation: ttWinIn .17s cubic-bezier(.2,.8,.2,1);
    /* 🔴 Do NOT add `transform` here. `will-change: transform` makes this element the containing block for
       every position:fixed DESCENDANT — which breaks the app's custom pickers (employee/user/contact/product):
       they measure the anchor with getBoundingClientRect (viewport coords) and set top/left on a fixed popup,
       so the popup lands offset by the window's origin AND gets clipped by this element's overflow:hidden.
       Drag/resize move the window with left/top anyway, so promoting transform bought nothing. */
    will-change: left, top, width, height;
}
.tt-win--active { box-shadow: var(--tt-win-shadow-active); }
.tt-win--dragging { transition: none; user-select: none; }

/* Maximize · cover chrome (default) — Windows-style: fills from the very top edge down to the top of the
   orchestrator taskbar, covering the app bar but never hiding behind the taskbar. The --tt-tb-* insets are
   stamped on .tt-scope from the live taskbar settings (both 0 when the bar is hidden or auto-hidden). */
/* ── Page scrollbar while a window is maximized ──────────────────────────────
   The maximized window covers the page completely, so the page's scrollbar is dead weight — and it eats
   the right edge where the window's close button sits. body.tt-win-maximized is stamped by
   TritiumWindowHost from the LIVE window list, so restore / minimize / close all clear it.
   Both html and body are targeted because either can be the scrolling element.
   NOTE: deliberately NO `scrollbar-gutter: stable` — reserving the gutter would keep the dead strip and
   defeat the point. The page reflows ~15px, but it's hidden behind the maximized window while it matters. */
body.tt-win-maximized { overflow: hidden; }
html:has(body.tt-win-maximized) { overflow: hidden; }

/* width:100% (NOT 100vw) — 100vw INCLUDES the page scrollbar gutter, so a maximized window slid underneath
   the scrollbar and buried its own close button. On a fixed element 100% resolves to the viewport WITHOUT
   the scrollbar, so the window stops exactly where the scrollbar starts. With the scrollbar hidden (above)
   100% simply becomes the full viewport width, so the window covers edge to edge. */
.tt-win--max {
    left: 0 !important; top: var(--tt-tb-top, 0px) !important;
    width: 100% !important;
    height: calc(100vh - var(--tt-tb-top, 0px) - var(--tt-tb-bottom, 0px)) !important;
    border-radius: 0; border: none;
}
/* Maximize · respect chrome — starts below the app bar (keeps it usable) and still stops above the taskbar. */
.tt-win--max-respect {
    left: 0 !important;
    top: calc(var(--tt-chrome-top, 64px) + var(--tt-tb-top, 0px)) !important;
    width: 100% !important;   /* not 100vw — see the note on .tt-win--max */
    height: calc(100vh - var(--tt-chrome-top, 64px) - var(--tt-tb-top, 0px) - var(--tt-tb-bottom, 0px)) !important;
    border-radius: 0; border: none; border-top: 1px solid var(--mud-palette-lines-default);
}
.tt-win--closing { animation: ttWinOut .14s ease forwards; }
/* Attention pulse — a singleton re-open surfaces the existing window and flashes this ring. */
.tt-win--attention { animation: ttWinAttention .65s cubic-bezier(.2,.8,.2,1); }
@keyframes ttWinAttention {
    0%, 100% { box-shadow: var(--tt-win-shadow-active); }
    35%      { box-shadow: 0 0 0 3px var(--mud-palette-primary), var(--tt-win-shadow-active); }
}

/* ── Title bar (the "toolbar") ── */
.tt-win__bar {
    flex: 0 0 var(--tt-win-bar-h); height: var(--tt-win-bar-h);
    display: flex; align-items: center; gap: 8px;
    padding: 0 6px 0 12px;
    /* Subtle lift off the body surface, from the window system's own theme palette. */
    color: var(--tt-fg);
    background: color-mix(in srgb, var(--tt-fg) 7%, var(--tt-surface));
    border-bottom: 1px solid var(--tt-border);
    cursor: grab; user-select: none;
    /* Touch: drag the bar instead of scrolling/zooming the page; no iOS long-press callout/selection. */
    touch-action: none; -webkit-user-select: none; -webkit-touch-callout: none;
    transition: opacity .18s ease, backdrop-filter .18s ease;
}
.tt-win--dragging .tt-win__bar { cursor: grabbing; }

/* Unfocused windows: fade + frost the title bar (a hint of transparency, like the taskbar glass) so the
   window that actually has focus is obvious at a glance. The active window keeps a solid, full-strength bar. */
.tt-win:not(.tt-win--active) .tt-win__bar {
    opacity: .78;
    backdrop-filter: blur(6px) saturate(1.05);
}
.tt-win:not(.tt-win--active) .tt-win__title,
.tt-win:not(.tt-win--active) .tt-win__icon { opacity: .8; }
/* The title only takes the room it needs so an adornment can sit right beside it; when there is no
   adornment the title itself absorbs the slack (see the :last-of-type rule below). */
.tt-win__title { flex: 0 1 auto; min-width: 0; font-size: .9rem; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* Identity widget beside the title (favourite stars, a status chip). The auto end-margin keeps it next to
   the title and pushes the min/max/close cluster to the far edge. */
.tt-win__adorn {
    flex: 0 0 auto; display: inline-flex; align-items: center; gap: 4px;
    margin-inline-start: 8px; margin-inline-end: auto;
    cursor: default;   /* not a drag handle — the JS drag start skips this subtree */
}
.tt-win__adorn .mud-icon-root { color: inherit !important; fill: currentColor !important; }
/* No adornment → the title stretches as before, so the buttons stay right-aligned. */
.tt-win__title:not(:has(+ .tt-win__adorn)) { flex: 1 1 auto; }
.tt-win__icon { display: inline-flex; opacity: .9; }
.tt-win__dirty {
    flex: 0 0 auto; width: 8px; height: 8px; border-radius: 50%;
    background: var(--mud-palette-warning);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--mud-palette-warning) 28%, transparent);
}
.tt-win__btns { flex: 0 0 auto; display: flex; align-items: center; gap: 2px; }

/* Accent toolbars — colored bar + readable text on top. */
.tt-win__bar.tt-bar--primary   { background: var(--mud-palette-primary);   color: var(--mud-palette-primary-text);   border-bottom-color: transparent; }
.tt-win__bar.tt-bar--secondary { background: var(--mud-palette-secondary); color: var(--mud-palette-secondary-text); border-bottom-color: transparent; }
.tt-win__bar.tt-bar--tertiary  { background: var(--mud-palette-tertiary);  color: var(--mud-palette-tertiary-text);  border-bottom-color: transparent; }
.tt-win__bar.tt-bar--info      { background: var(--mud-palette-info);      color: var(--mud-palette-info-text);      border-bottom-color: transparent; }
.tt-win__bar.tt-bar--success   { background: var(--mud-palette-success);   color: var(--mud-palette-success-text);   border-bottom-color: transparent; }
.tt-win__bar.tt-bar--warning   { background: var(--mud-palette-warning);   color: var(--mud-palette-warning-text);   border-bottom-color: transparent; }
.tt-win__bar.tt-bar--error     { background: var(--mud-palette-error);     color: var(--mud-palette-error-text);     border-bottom-color: transparent; }
.tt-win__bar.tt-bar--dark      { background: var(--mud-palette-dark);      color: var(--mud-palette-dark-text);      border-bottom-color: transparent; }
/* Guaranteed-contrast text on accent bars (white, dark on the light warning accent) — independent of whether
   MudBlazor's *-text vars resolve correctly at these app-root elements. */
.tt-win__bar[class*="tt-bar--"]  { color: #fff; }
.tt-win__bar.tt-bar--warning     { color: rgba(0,0,0,.87); }

/* Title-bar + taskbar foreground follows the bar's text color in light + dark. MudBlazor v9 paints the icon
   <svg> (.mud-svg-icon) with its OWN color (text-disabled), so inheriting on the button isn't enough — we
   override the icon element directly, on both color and fill. */
.tt-win__bar .tt-win__btn,
.tt-win__bar .mud-icon-button,
.tt-win__bar .mud-icon-button-label,
.tt-win__bar .mud-icon-root,
.tt-win__bar .mud-svg-icon,
.tt-taskbar .mud-icon-root,
.tt-taskbar .mud-svg-icon { color: inherit !important; }
.tt-win__bar .mud-svg-icon,
.tt-taskbar .mud-svg-icon { fill: currentColor !important; }
.tt-win__btn:hover { background-color: color-mix(in srgb, currentColor 16%, transparent); }

/* ── Touch-friendly window buttons (tt-winbtn-touch on <body>, set from LookAndFeelSettings) ──
   Round Size.Small icon buttons are a poor target for a finger — swap min/max/close for wide, square
   (not round) buttons that fill the full bar height, like a native OS title bar. Flush (no gap) so
   they read as one continuous strip; .tt-win's own overflow:hidden + border-radius (see top of file)
   naturally rounds the outer corner of the last (close) button, so nothing extra is needed here.
   Default (no body class, or tt-winbtn-condensed) keeps today's look completely untouched. */
body.tt-winbtn-touch .tt-win__btns { gap: 0; }
body.tt-winbtn-touch .tt-win__btn {
    width: 48px;
    height: var(--tt-win-bar-h);
    border-radius: 0 !important;
}
body.tt-winbtn-touch .tt-win__btn .mud-icon-root { font-size: 1.35rem; }

/* ── Body ── */
.tt-win__body { flex: 1 1 auto; overflow: auto; overscroll-behavior: contain; padding: var(--tt-pad, 16px); }
/* Tighter than the first pass: content (especially a tab strip, which brings its own padding) was sitting
   too far from the window edge. The three steps stay distinct. */
.tt-den-compact     { --tt-pad: 4px; }
.tt-den-comfortable { --tt-pad: 8px; }
.tt-den-spacious    { --tt-pad: 16px; }

/* TritiumDataGrid's wrapper when FillHeight=false (every existing, non-windowed caller — the vast
   majority of grids in the app): box-model-INVISIBLE. `display:contents` makes it act as if its children
   were direct children of ITS OWN parent, with zero layout effect — this is global (not scoped, since the
   scoped-css file that used to hold this was removed alongside the rest of this section) so it's guaranteed
   active regardless of where TritiumDataGrid is used. */
.tritium-grid-host { display: contents; }

/* ── Window ↔ grid-panel symbiosis (docs/page-window-contract.md) ────────────────────────────────
   🔴 DELIBERATELY GLOBAL, not a scoped .razor.css file — that was tried (TritiumDataGrid.razor.css,
   2026-07-20) and, despite `::deep`, did NOT reach MudDataGrid's internals correctly in practice (still no
   scrollbar). MudBlazor does not use Blazor's CSS-isolation scoping for its own internal markup — it ships
   one global stylesheet — and reaching into a compiled THIRD-PARTY component's internal DOM via `::deep`
   is not something this codebase had an independently proven case of; the one precedent
   (TritiumGridToolbar.razor.css) pierces into CONSUMER-SUPPLIED content, a different situation. Rather
   than keep debugging an unproven mechanism, this reverts to plain global CSS — exactly what was already
   confirmed working before the FillHeight refactor — just updated for the new wrapper class names
   (.tritium-grid-fill-host / .tritium-grid--fill, from TritiumDataGrid.razor). No isolation boundary,
   no `::deep`, nothing to fight.

   .tt-win__body defaults to `overflow:auto` with no bounded height on its child, so without this the panel
   just grows to fit everything (window body scrolls). Fix: bound .tt-win__body into a flex column
   (`overflow:hidden` — it must not scroll itself), the panel fills it via flex, the filter bar keeps its
   own natural height, and the grid gets everything left over. */
.tt-win__body:has(> .tt-win-grid-panel) { display: flex; flex-direction: column; padding: 0; overflow: hidden; }
.tt-win__body > .tt-win-grid-panel {
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    flex-direction: column;
    overflow: hidden;
    padding: var(--tt-pad, 16px);
}
/* The filter bar stays its natural size — targets BOTH shapes seen so far: pages that wrap
   TritiumGridToolbar in an extra `<div class="mb-2">` (Products) and pages that place it bare, where
   `.ttgt-root` (its own scoped root class) is the direct child instead (Bank Accounts, Branches, and
   everything since). Without `.ttgt-root` here, a bare toolbar had no EXPLICIT flex-shrink:0 — it
   happened to hold its height anyway via the browser's flex-grow:0 default, but that was relying on an
   implicit default rather than a stated rule, and a page that also has a THIRD sibling between the
   toolbar and the grid (e.g. Customer Statements' aging-summary block, itself carrying `.mb-2`) needs
   every one of its non-grid children accounted for explicitly, not by accident. */
.tt-win-grid-panel > .mb-1,
.tt-win-grid-panel > .mb-2,
.tt-win-grid-panel > .mb-3,
.tt-win-grid-panel > .mt-2,
.tt-win-grid-panel > .ttgt-root {
    flex: 0 0 auto;
}
/* More sibling shapes found converting Bank Reconciliation / Budgets / Chart of Accounts (2026-07-20):
   an info/warning MudAlert, the collapsed help MudExpansionPanels, and a SECOND embedded (FitContent,
   non-FillHeight) TritiumDataGrid sharing the panel with a primary FillHeight one (Bank Reconciliation's
   suggestions grid; Budgets' own grid when pinned to a fixed 240px while its variance grid fills the rest).
   The grid case needs its own two-level selector: a non-FillHeight grid's `.tritium-grid-host` wrapper is
   `display:contents` (invisible in the box tree, so its child is promoted to be an actual flex item of
   .tt-win-grid-panel) — but the DOM `>` combinator can't reach past that wrapper, so `.mb-2`/`.ttgt-root`-
   style single-level selectors don't match it; this reaches the promoted grid root directly instead. */
.tt-win-grid-panel > .mud-alert,
.tt-win-grid-panel > .mud-expansion-panels {
    flex: 0 0 auto;
}
.tt-win-grid-panel > .tritium-grid-host > .tritium-grid {
    flex: 0 0 auto;
}
/* Chart of Accounts' tree view (2026-07-20): a fourth archetype — neither TritiumDataGrid nor a bare
   MudTable, just a hand-rolled scrollable <div><MudTreeView>. Simpler than the table cases: no internal
   toolbar/pager to split out, so the div itself just needs to BE the bounded, scrolling flex item. Was
   previously an inline `height:calc(100vh - 300px)` — the exact viewport-relative pattern that breaks
   inside a window (see HeightOffset) — replaced with this class, applied only when InWindow. */
.tt-win-grid-panel > .coa-tree-view {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
}
/* ...TritiumDataGrid's FillHeight wrapper (see the component's own doc comment) fills what's left. Global,
   so it reaches MudDataGrid's internals with plain descendant/child selectors — no isolation boundary. */
.tritium-grid-fill-host {
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    flex-direction: column;
    overflow: hidden;
}
.tritium-grid-fill-host > .pinned-icons-row {
    flex: 0 0 auto;
}
/* .mud-table-container is NOT MudDataGrid's own root — it's a CHILD of the root (the element carrying
   `.tritium-grid--fill`), and that root has no flex participation of its own by default. Making the root
   itself the flex column (toolbar/pager natural height, container flex:1 + the real overflow-y:auto) is
   what actually produces the one working scrollbar — confirmed the hard way converting Products. */
.tritium-grid-fill-host .tritium-grid--fill {
    flex: 1 1 auto;
    min-height: 0 !important;
    height: auto !important;
    display: flex;
    flex-direction: column;
    overflow: hidden;
}
.tritium-grid-fill-host .tritium-grid--fill > .mud-table-toolbar,
.tritium-grid-fill-host .tritium-grid--fill > .mud-table-pagination {
    flex: 0 0 auto;
}
.tritium-grid-fill-host .tritium-grid--fill > .mud-table-container {
    flex: 1 1 auto;
    min-height: 0;
    height: auto !important;
    overflow-y: auto;
}

/* Same idea for a page using a BARE MudTable instead of TritiumDataGrid (Fiscal Years) — no wrapper
   component to split the "flex item" and "flex container" roles across, so one element (the table's own
   root, carrying .fy-years-table) plays BOTH: a flex item relative to .tt-win-grid-panel (flex:1 1 auto)
   AND a flex container for its own children (display:flex), exactly as .tt-win-grid-panel itself does one
   level up. Originally judged unnecessary (a plain MudTable has no internal scroll region to fight) — that
   was true of what renders by default, not of the anchored, internally-scrolling experience actually
   wanted; without this, expanding a row just grew the whole page instead of scrolling within the table. */
.tt-win-grid-panel .fy-years-table {
    flex: 1 1 auto;
    min-height: 0 !important;
    height: auto !important;
    display: flex;
    flex-direction: column;
    overflow: hidden;
}
.tt-win-grid-panel .fy-years-table > .mud-table-toolbar,
.tt-win-grid-panel .fy-years-table > .mud-table-pagination {
    flex: 0 0 auto;
}
.tt-win-grid-panel .fy-years-table > .mud-table-container {
    flex: 1 1 auto;
    min-height: 0;
    height: auto !important;
    overflow-y: auto;
}

/* Calendar (2026-07-20) — a FIFTH archetype: a fully bespoke month/week/day/agenda UI (app.css `.cal-*`
   classes), not a TritiumDataGrid or a MudTable. Unlike every other page converted so far, its INTERNAL
   scroll regions (.cal-week-grid-scroll, .cal-month-grid, .cal-agenda) were already correctly built as
   `flex:1; overflow-y:auto` — the only problem is the OUTERMOST root, `.cal-layout`, being pinned to
   `height: calc(100vh - 100px)` (app.css) instead of filling whatever bounded container it's actually in.
   A single-point fix: override that one height so it fills a windowed `.tt-win__body` instead of the real
   viewport — everything below it already does the right thing once it has real space to work with. No
   Razor markup changes needed beyond the InWindow parameter itself (.cal-layout is unconditionally the
   page's root already; this rule is naturally inert outside a window, same reasoning as .tt-win-grid-panel
   always being present). */
.tt-win__body:has(> .cal-layout) { display: flex; flex-direction: column; padding: 0; overflow: hidden; }
.tt-win__body > .cal-layout {
    flex: 1 1 auto;
    min-height: 0;
    height: auto;
}

/* Side-by-side master-detail MudGrid layout (a SIXTH archetype, first seen on Data Vault 2026-07-20, then
   reused as-is for Drafting Library since the shape is identical): a MudGrid row of independent MudPaper
   cards (tree/grid on the left, detail on the right), each historically pinned via its own INLINE
   `Style="height:calc(100vh - Npx)"` rather than a CSS class. Since MudBlazor's `.mud-grid` is
   `display:flex` with the flexbox-default `align-items:stretch` and no explicit height of its own, the fix
   is two-part: bound `.tt-win-split-layout` (the MudGrid) itself to the window like every other root, and
   let `align-items:stretch` (already the default — nothing to declare) size each MudItem/panel to match.
   Each panel's OWN inline style/Height parameter switches to `100%` when InWindow (in the page itself, not
   here — inline styles beat any class selector short of `!important`, so it's cleaner to make the value
   itself conditional in Razor, same call as Chart of Accounts' tree view in §5g). */
.tt-win__body:has(> .tt-win-split-layout) { display: flex; flex-direction: column; padding: 0; overflow: hidden; }
.tt-win__body > .tt-win-split-layout {
    flex: 1 1 auto;
    min-height: 0;
    height: auto;
    padding: var(--tt-pad, 16px);
}
/* Drafting Library variant: the split layout isn't the page root here — it sits below a header row
   (icon + title + Import/New buttons) inside a .tt-win-grid-panel, so it needs to fill what THAT panel
   leaves over instead of the window body directly. .tt-win-grid-panel already zeroes .tt-win__body's own
   padding and supplies its own, so no padding here (unlike the body-direct-child rule above). */
.tt-win-grid-panel > .tt-win-split-layout {
    flex: 1 1 auto;
    min-height: 0;
    height: auto;
}

/* Internal Messages (2026-07-20) — same shape as Calendar's fix: `.msg-explorer-layout` (app.css) is a
   CSS Grid root pinned to `height: calc(100vh - 85px)`. Everything below it was ALREADY correctly built
   as `flex:1; min-height:0; overflow:hidden` (the preview pane, the grid's own wrapper div) — only this
   one root anchor needed bounding to the window instead of the real viewport. (Two OTHER viewport-relative
   spots in this same page — the message-list panel's default split height and its inner MudDataGrid's
   `Height` — turned out to be fixable unconditionally in app.css/MessageExplorerPanel.razor itself, not
   window-specific, so they aren't here; see the page-window-contract.md write-up.) */
.tt-win__body:has(> .msg-explorer-layout) { display: flex; flex-direction: column; padding: 0; overflow: hidden; }
.tt-win__body > .msg-explorer-layout {
    flex: 1 1 auto;
    min-height: 0;
    height: auto;
}

/* CAD drawing editor (2026-07-20) — CadDesignerPage's `.cad-page` (app.css) is the same "hand-rolled root
   pinned to calc(100vh-Npx)" shape as .cal-layout/.msg-explorer-layout; `.cad-page-bar`/`.cad-page-body`
   underneath were already correctly flex-based. One-rule fix, same technique. (`.cad-dialog-body`, used by
   the SEPARATE CadDesignerDialog fullscreen-modal host, is untouched — different class, different
   component, not part of this page's conversion.) */
.tt-win__body:has(> .cad-page) { display: flex; flex-direction: column; padding: 0; overflow: hidden; }
.tt-win__body > .cad-page {
    flex: 1 1 auto;
    min-height: 0;
    height: auto;
}

/* Check Designer (2026-07-20) — same shape again: `.ck-designer` (app.css) pinned to
   `calc(100vh - var(--mud-appbar-height,64px) - 2.5rem + 20px)`; `.ck-body`/`.ck-row` underneath already
   correctly flex-based (`flex:1; min-height:0`). One-rule fix, same technique — the JS canvas interop
   (checkDesigner.init) reads element bounding boxes at drag-time, not at layout-definition time, so
   bounding the ROOT differently doesn't touch its math. */
.tt-win__body:has(> .ck-designer) { display: flex; flex-direction: column; padding: 0; overflow: hidden; }
.tt-win__body > .ck-designer {
    flex: 1 1 auto;
    min-height: 0;
    height: auto;
}

/* Pinned action bar — stays glued to the bottom border while the body scrolls (Save/Cancel live here). */
.tt-win__footer {
    flex: 0 0 auto;
    display: flex; align-items: center; justify-content: flex-end; gap: 8px;
    padding: 10px 16px;
    border-top: 1px solid var(--tt-border);
    background: color-mix(in srgb, var(--tt-fg) 4%, var(--tt-surface));
}

/* Leading group in the action bar — record-level actions (print, archive, labels, help) that used to sit
   beside a dialog title. The title bar is chrome now, so they live here, pushed away from Save/Cancel. */
.tt-win__footer-lead { display: flex; align-items: center; gap: 8px; margin-inline-end: auto; }

/* ── Resize handles ── */
.tt-win__rz { position: absolute; z-index: 2; touch-action: none; }
.tt-win__rz[data-dir="n"]  { top: -3px; left: 10px; right: 10px; height: 8px; cursor: ns-resize; }
.tt-win__rz[data-dir="s"]  { bottom: -3px; left: 10px; right: 10px; height: 8px; cursor: ns-resize; }
.tt-win__rz[data-dir="e"]  { right: -3px; top: 10px; bottom: 10px; width: 8px; cursor: ew-resize; }
.tt-win__rz[data-dir="w"]  { left: -3px; top: 10px; bottom: 10px; width: 8px; cursor: ew-resize; }
.tt-win__rz[data-dir="ne"] { top: -4px; right: -4px; width: 14px; height: 14px; cursor: nesw-resize; }
.tt-win__rz[data-dir="nw"] { top: -4px; left: -4px; width: 14px; height: 14px; cursor: nwse-resize; }
.tt-win__rz[data-dir="se"] { bottom: -4px; right: -4px; width: 14px; height: 14px; cursor: nwse-resize; }
.tt-win__rz[data-dir="sw"] { bottom: -4px; left: -4px; width: 14px; height: 14px; cursor: nesw-resize; }

/* ── Orchestrator taskbar ── */
.tt-taskbar {
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 1350;
    height: var(--tt-taskbar-h);
    display: flex; align-items: center; gap: 6px;
    padding: 0 10px;
    color: var(--tt-fg);
    /* Chrome, not content: a click (or a double-click landing a few px off target) was selecting a
       chip/clock/brand label as text instead of just focusing or activating it. `user-select: none` used
       to be scoped to individual elements only WHILE dragging; applying it to the whole bar at rest is
       what actually stops the selection, and -webkit-user-select covers Safari/iOS, which ignores the
       unprefixed property. */
    user-select: none;
    -webkit-user-select: none;
    /* Same glass RECIPE as .dashboard-panel (app.css): blur(16px) saturate(170%) over a color-mix'd fill —
       one visual language for every piece of glass in the app, not a bar-specific formula. Only the
       PERCENT differs, and for a real reason: the bar is app chrome, not content, so it should read as a
       solid ledge things sit on rather than another floating pane — hence more opaque than a panel's 58%.
       Two knobs, both here:
         --tt-tb-darken   how far the surface is pushed toward black before it's made translucent
         --tt-tb-opacity  how much of that darkened surface remains
       Darkening BEFORE the transparency mix is what keeps this readable in light mode, where the theme
       surface is near-white — mixing straight to transparent would only make it thinner and paler, never
       actually darker. Panels don't need this step; they aren't asked to look "darker than surface". */
    --tt-tb-darken: 22%;
    --tt-tb-opacity: 76%;
    background: color-mix(in srgb,
        color-mix(in srgb, #000 var(--tt-tb-darken), var(--tt-surface)) var(--tt-tb-opacity),
        transparent);
    backdrop-filter: blur(16px) saturate(170%);
    -webkit-backdrop-filter: blur(16px) saturate(170%);
    border-top: 1px solid var(--tt-border);
    box-shadow: 0 -6px 20px -12px rgba(0,0,0,.35);
}
.tt-taskbar--top { top: 0; bottom: auto; border-top: none; border-bottom: 1px solid var(--tt-border); box-shadow: 0 6px 20px -12px rgba(0,0,0,.35); }
.tt-taskbar--autohide { transform: translateY(calc(100% - 4px)); transition: transform .2s ease; }
.tt-taskbar--top.tt-taskbar--autohide { transform: translateY(calc(-100% + 4px)); }
.tt-taskbar--autohide:hover { transform: none; }
/* Invisible hover hot-zone above the hidden bar — otherwise there's nothing on-screen to hover to summon it back. */
.tt-taskbar--autohide::before { content: ""; position: absolute; left: 0; right: 0; top: -16px; height: 16px; }
.tt-taskbar--top.tt-taskbar--autohide::before { top: auto; bottom: -16px; }
/* Left zone: [logo] Tritium │ [Module] │ — the brand is the reserved launcher activator. */
.tt-tb-brand { display: flex; align-items: center; gap: 7px; padding: 0 8px; font-weight: 700; font-size: .82rem; }
.tt-tb-brand__logo { width: 18px; height: 18px; display: block; border-radius: 4px; flex: 0 0 auto; }
.tt-tb-brand__name { white-space: nowrap; }

/* The brand is the app-launcher activator in Start placement — reset the button chrome so it still looks
   like plain branding, and only then hint that it's clickable. */
.tt-tb-brand--btn {
    background: none;
    border: 0;
    color: inherit;
    font: inherit;
    font-weight: 700;
    font-size: .82rem;
    height: 26px;
    border-radius: 6px;
    cursor: pointer;
    transition: background-color .15s ease;
}
.tt-tb-brand--btn:hover  { background: rgba(255,255,255,.14); }
.tt-tb-brand--btn:active { background: rgba(255,255,255,.22); }
/* Hairline separator between left-zone segments. */
.tt-tb-sep { flex: 0 0 auto; width: 1px; align-self: stretch; margin: 9px 3px; background: var(--tt-border); }
/* Active-module button → that module's Home. */
.tt-tb-module {
    flex: 0 0 auto;
    display: inline-flex; align-items: center;
    height: 28px; max-width: 220px; padding: 0 10px; margin: 0 2px;
    border: 0; border-radius: 8px; background: transparent;
    color: inherit; font: inherit; font-size: .82rem; font-weight: 600;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis; cursor: pointer;
    transition: background-color .12s ease;
}
.tt-tb-module:hover { background: color-mix(in srgb, var(--tt-fg) 12%, transparent); }
.tt-tb-module:focus-visible { outline: 2px solid var(--tt-accent, var(--mud-palette-primary)); outline-offset: -2px; }
.tt-tb-strip { flex: 1 1 auto; display: flex; align-items: center; gap: 6px; overflow-x: auto; }
.tt-tb-chip {
    display: inline-flex; align-items: center; gap: 6px;
    max-width: 260px; height: 32px; padding: 0 12px;   /* room for "Title — Record name" */
    border-radius: 8px; border: 1px solid var(--tt-border);
    background: var(--tt-elev);
    cursor: pointer; white-space: nowrap; font-size: .82rem;
    transition: background .12s ease, border-color .12s ease;
}
.tt-tb-chip:hover { background: color-mix(in srgb, var(--tt-fg) 12%, var(--tt-surface)); }
.tt-tb-chip__label { overflow: hidden; text-overflow: ellipsis; }
.tt-tb-chip--min { opacity: .6; }
.tt-tb-chip--active { border-color: var(--tt-accent, var(--mud-palette-primary)); box-shadow: inset 0 0 0 1px var(--tt-accent, var(--mud-palette-primary)); }
/* ── Clock = the progressive-collapse control (styled to match .tt-tb-module on the left) ──
   It never collapses: it is the only way back. The caret shows which way the next click goes. */
.tt-tb-clock {
    flex: 0 0 auto;
    display: inline-flex; align-items: center; gap: 6px;
    height: 28px; padding: 0 10px; margin: 0 2px;
    border: 0; border-radius: 8px; background: transparent;
    color: inherit; font: inherit; font-size: .8rem; font-weight: 600;
    font-variant-numeric: tabular-nums; white-space: nowrap; cursor: pointer;
    transition: background-color .12s ease;
}
.tt-tb-clock:hover { background: color-mix(in srgb, var(--tt-fg) 12%, transparent); }
.tt-tb-clock:focus-visible { outline: 2px solid var(--tt-accent, var(--mud-palette-primary)); outline-offset: -2px; }
/* MudIcon sizes its svg from font-size (width/height are 1em). */
.tt-tb-clock .tt-tb-clock__caret { opacity: .6; font-size: 1.05rem; }

/* ⌃ reveal button — present only while something is collapsed. */
.tt-tb-chev {
    flex: 0 0 auto;
    display: inline-flex; align-items: center; justify-content: center;
    width: 28px; height: 28px;
    border: 0; border-radius: 8px; background: transparent;
    color: inherit; cursor: pointer;
    transition: background-color .12s ease, transform .18s ease;
}
.tt-tb-chev:hover { background: color-mix(in srgb, var(--tt-fg) 12%, transparent); }
.tt-tb-chev--on { background: color-mix(in srgb, var(--tt-fg) 16%, transparent); transform: rotate(180deg); }

/* Collapsible icon group — one box in three guises: inline, animated shut, or floated as the flyout. */
.tt-tb-icons {
    flex: 0 0 auto;
    display: flex; align-items: center; gap: 6px;
    max-width: 60vw; opacity: 1;
    /* `overflow:hidden` is what makes the max-width collapse animate — but it also clips anything that
       overhangs an icon, which swallowed the Suitcase's MudBadge count against the bar's top edge.
       Overflow clips at the PADDING box, so pad vertically to create drawing room and cancel that padding
       with an equal negative margin, leaving the bar's layout untouched. (overflow-x:hidden +
       overflow-y:visible is not an option — per spec the `visible` axis computes to `auto` and scrolls.) */
    overflow: hidden;
    padding: 10px 0;
    margin: -10px 0;
    transition: max-width .24s cubic-bezier(.2,.8,.2,1), opacity .16s ease;
}
.tt-tb-icons--hidden { max-width: 0; opacity: 0; pointer-events: none; }
/* Floated above the bar. .tt-taskbar is position:fixed, so absolute resolves against it and
   `bottom:100%` lands exactly on the bar's top edge. z:1100 keeps it UNDER launcher panels (1200). */
.tt-tb-icons--flyout {
    position: absolute; right: 8px; bottom: calc(100% + 10px);
    max-width: none; overflow: visible; opacity: 1;
    padding: 6px; margin: 0;   /* reset the inline state's clip-room padding/negative margin */
    gap: 4px;
    background: var(--tt-surface);
    border: 1px solid var(--tt-border); border-radius: 10px;
    box-shadow: var(--tt-win-shadow-active);
    z-index: 1100;
    animation: ttFadeIn .12s ease;
}
/* Click-outside veil for the flyout — below the flyout AND below launcher panels. */
.tt-tb-veil { position: absolute; inset: -100vh -100vw; z-index: 1090; }

/* User tile wrapper — collapses only at full collapse. */
.tt-tb-userwrap {
    flex: 0 0 auto; display: flex; align-items: center;
    max-width: 320px; opacity: 1; overflow: hidden;
    transition: max-width .24s cubic-bezier(.2,.8,.2,1), opacity .16s ease;
}
.tt-tb-userwrap--hidden { max-width: 0; opacity: 0; pointer-events: none; }

@media (prefers-reduced-motion: reduce) {
    .tt-tb-icons, .tt-tb-userwrap, .tt-tb-chev { transition: none; }
    .tt-tb-icons--flyout { animation: none; }
}
.tt-tb-btn { flex: 0 0 auto; }
.tt-taskbar .tt-tb-btn:hover { background-color: color-mix(in srgb, var(--tt-fg) 14%, transparent); }

/* ── Launcher panels hosted IN the taskbar (Notes / Suitcase / Messages) ──────────────────────────
   🔴 These were all written as drop-DOWN panels anchored to the 48px top app bar
   (`position:fixed; top:48px; right:8px`). Two things break when the button moves into the bottom bar:

     1. `.tt-taskbar` has `backdrop-filter`, and filter/backdrop-filter/transform make an element the
        CONTAINING BLOCK for its `position:fixed` descendants. So `top:48px` is measured from the BAR's
        top edge, not the viewport — on a 46px bar docked at the bottom that puts the panel just past the
        bottom of the screen (button clicks, nothing appears). Same family of bug as the `.tt-win`
        transform gotcha that broke the custom pickers.
     2. Even positioned correctly, a bottom bar has to open its panels UPWARD.

   Re-anchor them above the bar. `bottom:100%` is now relative to the taskbar, which is exactly what we
   want here — the containing block works FOR us once the panel opens upward. The overlay has to be blown
   out past the bar's box so click-outside still covers the whole viewport. */
.tt-taskbar .msc-panel-dropdown,
.tt-taskbar .notes-panel-dropdown,
.tt-taskbar .msg-panel-dropdown {
    top: auto;
    bottom: calc(100% + 8px);
    right: 8px;
    max-height: calc(100vh - 70px);
    border-radius: 8px;
}
.tt-taskbar .msc-panel-overlay,
.tt-taskbar .notes-panel-overlay,
.tt-taskbar .msg-panel-overlay { inset: -100vh -100vw; }

/* ── Taskbar user tile (avatar ▾ menu) ── */
.tt-taskbar .tt-tb-user { flex: 0 0 auto; text-transform: none; margin-inline-start: 2px; }
.tt-taskbar .tt-tb-user .mud-button-label { font-weight: 600; max-width: 160px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* The menu popover renders at app root (outside .tt-scope) on a plain MudBlazor surface. */
.tt-user-menu__hd { padding: 8px 16px 4px; }
.tt-user-menu__hd-name { font-weight: 700; font-size: .86rem; line-height: 1.2; }
.tt-user-menu__hd-sub { font-size: .72rem; opacity: .6; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 240px; }
/* The Language submenu activator is a MudButton, not a MudMenuItem — normalize it so the row is
   indistinguishable from the plain items above it (left-aligned, same padding/weight, no ALL-CAPS). */
.tt-user-menu__sub { display: block; width: 100%; }
.tt-user-menu__sub .mud-button-root {
    width: 100%; justify-content: flex-start;
    padding: 6px 16px; border-radius: 0;
    font-size: .875rem; font-weight: 400; line-height: 1.5;
    text-transform: none; letter-spacing: normal;
}
.tt-user-menu__sub .mud-button-icon-start { margin-left: 0; margin-right: 12px; }

/* ── Orchestrator context menus (arrange / per-window) ── */
.tt-ctx-overlay { position: fixed; inset: 0; z-index: 1998; }
.tt-ctx {
    position: fixed; z-index: 2000; min-width: 196px;
    background: var(--tt-surface); color: var(--tt-fg);
    border: 1px solid var(--tt-border); border-radius: 10px;
    box-shadow: var(--tt-win-shadow-active);
    padding: 6px; font-size: .85rem;
    animation: ttFadeIn .1s ease;
}
.tt-ctx-item {
    display: flex; align-items: center; gap: 10px;
    padding: 7px 10px; border-radius: 6px; cursor: pointer; white-space: nowrap;
    color: var(--tt-fg);
}
.tt-ctx-item:hover { background: color-mix(in srgb, var(--tt-fg) 12%, var(--tt-surface)); }
.tt-ctx-item .mud-icon-root { color: inherit !important; fill: currentColor !important; opacity: .85; }
.tt-ctx-item--danger { color: var(--mud-palette-error); }
.tt-ctx-sep { height: 1px; margin: 4px 6px; background: var(--tt-border); }
/* Which window am I acting on? — the composed "Title — Record" heading. */
.tt-ctx-hd {
    padding: 4px 10px 8px; margin-bottom: 2px;
    border-bottom: 1px solid var(--tt-border);
    font-size: .78rem; font-weight: 600; opacity: .7;
    max-width: 280px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* ── Grouped taskbar chip (Windows-style combine) + its fly-up ── */
.tt-tb-chip--group { padding-right: 6px; }
.tt-tb-count {
    min-width: 18px; height: 18px; padding: 0 5px;
    display: inline-flex; align-items: center; justify-content: center;
    border-radius: 9px; font-size: .7rem; font-weight: 700;
    background: var(--tt-accent, var(--mud-palette-primary)); color: #fff;
}
.tt-tb-flyout {
    position: fixed; z-index: 2000; min-width: 240px; max-width: 420px;
    background: var(--tt-surface); color: var(--tt-fg);
    border: 1px solid var(--tt-border); border-radius: 10px;
    box-shadow: var(--tt-win-shadow-active);
    padding: 6px; animation: ttFadeIn .1s ease;
}
.tt-tb-flyout__hd { font-size: .7rem; text-transform: uppercase; letter-spacing: .05em; opacity: .55; padding: 4px 10px 6px; }
.tt-tb-flyout__item { display: flex; align-items: center; gap: 8px; padding: 6px 8px; border-radius: 6px; cursor: pointer; }
.tt-tb-flyout__item:hover { background: color-mix(in srgb, var(--tt-fg) 12%, var(--tt-surface)); }
.tt-tb-flyout__item--min { opacity: .6; }
.tt-tb-flyout__label { flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: .85rem; }
.tt-tb-flyout__x { display: inline-flex; padding: 2px; border-radius: 5px; opacity: .55; cursor: pointer; }
.tt-tb-flyout__x:hover { opacity: 1; background: color-mix(in srgb, var(--tt-fg) 16%, transparent); }
.tt-tb-flyout .mud-svg-icon { color: inherit !important; fill: currentColor !important; }

/* Dirty signalling on the taskbar: a bullet on the chip, and a pulsing warning count on a group. */
.tt-tb-chip__dirty {
    flex: 0 0 auto; width: 7px; height: 7px; border-radius: 50%;
    background: var(--mud-palette-warning);
    box-shadow: 0 0 0 2px color-mix(in srgb, var(--mud-palette-warning) 30%, transparent);
}
.tt-tb-count--dirty {
    background: var(--mud-palette-warning); color: #000;
    animation: ttCountPulse 1.5s ease-in-out infinite;
}
@keyframes ttCountPulse {
    0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--mud-palette-warning) 65%, transparent); }
    50%      { box-shadow: 0 0 0 6px color-mix(in srgb, var(--mud-palette-warning) 0%, transparent); }
}
@media (prefers-reduced-motion: reduce) { .tt-tb-count--dirty { animation: none; } }

/* ── Motion ── */
@keyframes ttWinIn  { from { opacity: 0; transform: translateY(8px) scale(.97); } to { opacity: 1; transform: none; } }
@keyframes ttWinOut { from { opacity: 1; transform: none; } to { opacity: 0; transform: translateY(6px) scale(.98); } }
@keyframes ttFadeIn { from { opacity: 0; } to { opacity: 1; } }

@media (prefers-reduced-motion: reduce) {
    .tt-win, .tt-win--closing, .tt-win-backdrop { animation: none; }
}

/* ── Touch devices (iPad et al.): fatter resize handles + roomier tap targets ── */
@media (pointer: coarse) {
    .tt-win__rz[data-dir="n"], .tt-win__rz[data-dir="s"] { height: 18px; }
    .tt-win__rz[data-dir="e"], .tt-win__rz[data-dir="w"] { width: 18px; }
    .tt-win__rz[data-dir="ne"], .tt-win__rz[data-dir="nw"],
    .tt-win__rz[data-dir="se"], .tt-win__rz[data-dir="sw"] { width: 26px; height: 26px; }
    .tt-tb-chip { height: 38px; }
    .tt-ctx-item, .tt-tb-flyout__item { padding: 11px 12px; }
}

/* ── Responsive: below tablet, windows become full-screen sheets (no drag/resize) ── */
@media (max-width: 768px) {
    .tt-win {
        left: 0 !important; top: 0 !important;
        width: 100vw !important; height: 100dvh !important;
        border-radius: 0; border: none;
    }
    .tt-win__rz { display: none; }
    .tt-win__bar { cursor: default; }
}
