/* ============================================================
   WorkPlan Studio — design system
   ============================================================ */

:root {
    color-scheme: light;

    --bg: #f4f5f8;
    --surface: #ffffff;
    --surface-2: #f0f2f5;
    --border: #e6e8ee;
    --border-strong: #d3d7df;

    --text: #1b1f27;
    --text-muted: #5f6878;
    /* The smallest text in the app (table headers, axis ticks) uses --text-soft at
       ~11 px, so it is measured against the two grounds it actually lands on and
       kept clear of 4.5:1 rather than exactly on it: 5.4:1 on --surface, 5.0:1 on
       --bg, 4.9:1 on --surface-2. A pair sitting at 4.50 fails on the next nudge. */
    --text-soft: #626a7a;

    --primary: #4f46e5;
    --primary-hover: #4338ca;
    --primary-soft: #eef2ff;
    --primary-accent: #7c3aed;
    --on-primary: #ffffff;

    --sidebar-bg: #1c2030;
    --sidebar-bg-2: #151826;
    --sidebar-text: #b3bacb;
    --sidebar-muted: #949cb0;      /* 5.9:1 on the sidebar's lightest stop */
    --sidebar-hover: rgba(255, 255, 255, .06);
    --sidebar-active: rgba(255, 255, 255, .11);
    --sidebar-accent: #a5b4fc;

    --topbar-bg: rgba(255, 255, 255, .82);
    --scrim: rgba(17, 22, 33, .45);

    /* Each status colour is also its own foreground on the matching -soft ground
       (pills, badges, banners), so the pairs are measured that way: green 5.4:1,
       amber 5.6:1, red 5.1:1, slate 6.2:1. The reds were the one real failure —
       #dc2626 on #fee2e2 is 3.95:1, below AA at the 12–13 px these pills use. */
    --green: #137334;   --green-soft: #dcfce7;   --green-border: #b7e2c4;
    --amber: #9a4a06;   --amber-soft: #fef3c7;
    --red: #c01919;     --red-soft: #fee2e2;     --red-border: #f3c9c9;   --red-hover: #9f1414;
    --slate: #475569;   --slate-soft: #e4e8ee;

    --radius-sm: 7px;
    --radius: 10px;
    --radius-lg: 14px;

    --shadow-sm: 0 1px 2px rgba(16, 24, 40, .06);
    --shadow: 0 1px 3px rgba(16, 24, 40, .08), 0 1px 2px rgba(16, 24, 40, .04);
    --shadow-lg: 0 12px 28px rgba(16, 24, 40, .16);

    --sidebar-w: 256px;
}

/* Dark theme. app.js resolves the user's choice (system / light / dark) to an
   effective data-theme before Blazor boots and keeps it in sync with the OS,
   so the stylesheet only ever needs one dark block. */
:root[data-theme="dark"] {
    color-scheme: dark;
    --bg: #0f1117;
    --surface: #171a23;
    --surface-2: #1f2330;
    --border: #262b38;
    --border-strong: #343a4a;

    --text: #e6e8ef;
    --text-muted: #a3abbd;
    --text-soft: #98a1b4;          /* 6.7:1 on --surface, 7.3:1 on --bg */

    --primary: #818cf8;
    --primary-hover: #a5b4fc;
    --primary-soft: rgba(129, 140, 248, .16);
    --primary-accent: #a78bfa;
    --on-primary: #0f1117;

    --sidebar-bg: #0b0d14;
    --sidebar-bg-2: #07080d;
    --sidebar-text: #b3bacb;
    --sidebar-muted: #8a92a6;

    --topbar-bg: rgba(23, 26, 35, .84);
    --scrim: rgba(0, 0, 0, .6);

    --green: #4ade80;   --green-soft: rgba(74, 222, 128, .14);   --green-border: rgba(74, 222, 128, .3);
    --amber: #fbbf24;   --amber-soft: rgba(251, 191, 36, .14);
    --red: #f87171;     --red-soft: rgba(248, 113, 113, .14);    --red-border: rgba(248, 113, 113, .3);   --red-hover: #ef4444;
    --slate: #b0b8c9;   --slate-soft: rgba(148, 163, 184, .18);

    --shadow-sm: 0 1px 2px rgba(0, 0, 0, .4);
    --shadow: 0 1px 3px rgba(0, 0, 0, .5), 0 1px 2px rgba(0, 0, 0, .3);
    --shadow-lg: 0 16px 36px rgba(0, 0, 0, .55);
}

.recovery-shell {
    min-height: 100vh;
    display: grid;
    place-items: center;
    align-content: center;
    gap: 0.75rem;
    padding: 2rem;
    background: var(--bg);
}

.recovery-card {
    width: min(42rem, 100%);
}

/* The persona switcher sits above the card, right-aligned with it, because the
   sentence inside the card points at it. */
.recovery-bar {
    width: min(42rem, 100%);
    display: flex;
    justify-content: flex-end;
}

* { box-sizing: border-box; }

/* The whole rem scale is anchored here. It is a percentage, not a pixel value, so
   the browser's own "default font size" preference — the one setting a reader with
   low vision actually changes — still multiplies through. 93.75% of the usual 16 px
   default is the 15 px the design was drawn at, so nothing moves for everyone else. */
html { font-size: 93.75%; }

html, body {
    margin: 0;
    font-family: "Segoe UI", system-ui, -apple-system, Roboto, Helvetica, Arial, sans-serif;
    color: var(--text);
    background: var(--bg);
    -webkit-font-smoothing: antialiased;
}

body { font-size: 1rem; }

h1, h2, h3 { margin: 0; font-weight: 600; letter-spacing: -.01em; }
h1:focus, h2:focus, h3:focus { outline: none; }

:where(a, button, select, input, textarea, [tabindex]):focus-visible {
    outline: 3px solid var(--primary);
    outline-offset: 2px;
}
h1 { font-size: 1.6rem; }
h2 { font-size: 1.05rem; }
h3 { font-size: .95rem; }
p { margin: 0; }
a { color: var(--primary); text-decoration: none; }
a:hover { text-decoration: underline; }

.muted { color: var(--text-muted); }
.mono { font-family: "Cascadia Code", "Consolas", ui-monospace, monospace; font-size: .86em; }

/* ----- App shell ----- */
.app-shell { display: flex; min-height: 100vh; }

.sidebar {
    width: var(--sidebar-w);
    flex-shrink: 0;
    background: linear-gradient(180deg, var(--sidebar-bg), var(--sidebar-bg-2));
    color: var(--sidebar-text);
    display: flex;
    flex-direction: column;
    position: sticky;
    top: 0;
    height: 100vh;
}

.brand { display: flex; align-items: center; gap: .7rem; padding: 1.25rem 1.2rem; }
.brand-mark {
    width: 38px; height: 38px; flex-shrink: 0;
    border-radius: 10px;
    background: linear-gradient(135deg, var(--primary), var(--primary-accent));
    color: #fff; font-weight: 700; font-size: .9rem;
    display: flex; align-items: center; justify-content: center;
    box-shadow: 0 4px 12px rgba(79, 70, 229, .4);
}
.brand-text { display: flex; flex-direction: column; line-height: 1.2; }
.brand-name { color: #fff; font-weight: 600; font-size: 1.02rem; }
.brand-tag { color: var(--sidebar-muted); font-size: .72rem; }

.nav-menu { display: flex; flex-direction: column; gap: 2px; padding: .5rem .7rem; flex: 1; }
.nav-link {
    display: flex; align-items: center; gap: .7rem;
    padding: .6rem .75rem;
    border-radius: var(--radius-sm);
    color: var(--sidebar-text);
    font-weight: 500; font-size: .92rem;
    transition: background .12s, color .12s;
}
.nav-link:hover { background: var(--sidebar-hover); color: #fff; text-decoration: none; }
.nav-link.active { background: var(--sidebar-active); color: #fff; }
.nav-link.active .nav-icon { color: var(--sidebar-accent); }
.nav-icon { width: 19px; height: 19px; flex-shrink: 0; color: var(--sidebar-muted); fill: none; stroke: currentColor; stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round; }
.nav-group { display: flex; flex-direction: column; gap: 2px; margin-top: .7rem; }
.nav-group + .nav-link { margin-top: .7rem; }
.nav-heading { padding: .2rem .75rem .3rem; font-size: .68rem; font-weight: 600; letter-spacing: .08em; text-transform: uppercase; color: var(--sidebar-muted); }

.sidebar-footer {
    padding: .9rem 1.2rem;
    font-size: .72rem; color: var(--sidebar-muted);
    border-top: 1px solid rgba(255, 255, 255, .06);
}

.content-area { flex: 1; min-width: 0; display: flex; flex-direction: column; }

.topbar {
    height: 60px; flex-shrink: 0;
    display: flex; align-items: center; gap: 1rem;
    padding: 0 1.6rem;
    background: var(--topbar-bg);
    backdrop-filter: blur(8px);
    border-bottom: 1px solid var(--border);
    position: sticky; top: 0; z-index: 20;
}
.topbar-spacer { flex: 1; }

.hamburger {
    display: none;
    background: none; border: none; cursor: pointer;
    color: var(--text); padding: 6px; border-radius: 8px;
}
.hamburger:hover { background: var(--surface-2); }
.hamburger svg { width: 22px; height: 22px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; }

/* The shell fills the window at every size. Data-dense blocks (Gantt, tables,
   the weekly preview) want every pixel a wide monitor offers, so there is no
   global cap; the reading measure lives on the blocks that need one — prose,
   form fields and chat bubbles set their own max-width below. */
.page-body { padding: 1.8rem; width: 100%; }

/* ----- Culture selector ----- */
.culture-selector { display: inline-flex; background: var(--surface-2); border-radius: 8px; padding: 3px; gap: 2px; }
/* WCAG 2.2 target size (2.5.8): the two buttons sit 2 px apart, so the spacing
   exception does not apply and each box has to reach 24 px in both directions. */
.culture-btn {
    border: none; background: none; cursor: pointer;
    padding: .45rem .7rem; border-radius: 6px; min-height: 24px; min-width: 24px;
    font-size: .8rem; font-weight: 600; color: var(--text-muted);
}
.culture-btn.active { background: var(--surface); color: var(--primary); box-shadow: var(--shadow-sm); }
.culture-btn:hover:not(.active) { color: var(--text); }

/* ----- Page header ----- */
.page-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 1rem; margin-bottom: 1.4rem; }
.page-sub { color: var(--text-muted); font-size: .9rem; margin-top: .25rem; }
.head-actions { display: flex; gap: .5rem; }
/* WCAG 2.2 AA 2.5.8 Target Size (Minimum), for the controls that are controls in
   their own right. The criterion excepts a link sitting inside a sentence, whose
   height is set by the surrounding line-height; none of these are that. Each was
   measured under the floor at 375 px: a card-head "View all" at 51x20, a work
   plan number in a table cell at 44x13, the import page's example download at
   304x20, and a column's sort button at 29x15. Only the height was ever wrong,
   so only the height is set - the text keeps its own size. */
.link,
.data-table a,
.field > a,
.th-sort { min-height: 24px; display: inline-flex; align-items: center; }

.link.back { font-size: .82rem; color: var(--text-muted); display: inline-flex; align-items: center; min-height: 24px; padding: .25rem .35rem .25rem 0; margin-bottom: .1rem; }
.link.back:hover { color: var(--primary); }

/* ----- Buttons ----- */
.btn {
    display: inline-flex; align-items: center; gap: .4rem;
    border: 1px solid transparent; border-radius: var(--radius-sm);
    padding: .5rem .9rem; font-size: .88rem; font-weight: 600;
    cursor: pointer; transition: background .12s, border-color .12s, color .12s;
    white-space: nowrap; line-height: 1;
    /* WCAG 2.2 AA 2.5.8 Target Size (Minimum). Set on the base rather than on
       the one variant that was short, so a future size modifier cannot drop a
       button back under the floor by changing padding or font size. */
    min-height: 24px;
}
.btn-plus { font-size: 1.1em; line-height: 0; margin-top: -1px; }
.btn-primary { background: var(--primary); color: var(--on-primary); }
.btn-primary:hover { background: var(--primary-hover); }
.btn-ghost { background: transparent; color: var(--text-muted); }
.btn-ghost:hover { background: var(--surface-2); color: var(--text); }
.btn-outline { background: var(--surface); border-color: var(--border-strong); color: var(--text); }
.btn-outline:hover { border-color: var(--text-soft); }
.btn-outline.danger { color: var(--red); border-color: var(--red-border); }
.btn-outline.danger:hover { background: var(--red-soft); }
.btn-danger { background: var(--red); color: var(--on-primary); }
.btn-danger:hover { background: var(--red-hover); }
.btn.full { width: 100%; justify-content: center; }

.icon-btn {
    background: none; border: none; cursor: pointer;
    width: 30px; height: 30px; border-radius: 7px;
    display: inline-flex; align-items: center; justify-content: center;
    color: var(--text-muted);
}
.icon-btn:hover { background: var(--surface-2); color: var(--text); }
.icon-btn.danger:hover { background: var(--red-soft); color: var(--red); }
.icon-btn:disabled { opacity: .35; cursor: not-allowed; }
.icon-btn svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }

/* ----- Cards ----- */
.card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); box-shadow: var(--shadow-sm); overflow: hidden; }
.card-head { display: flex; align-items: center; justify-content: space-between; padding: 1rem 1.2rem; border-bottom: 1px solid var(--border); }
.card-head.pad { padding: 1rem 1.2rem; }
.card-body { padding: 1.2rem; }
.card-body.no-pad, .card.no-pad { padding: 0; }
.card.no-pad .card-head { border-bottom: 1px solid var(--border); }

/* ----- Stat cards ----- */
.stat-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 1rem; margin-bottom: 1.5rem; }
.stat-card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: 1.1rem 1.2rem; box-shadow: var(--shadow-sm); position: relative; overflow: hidden; }
.stat-card::before { content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 4px; }
.stat-card.accent-indigo::before { background: var(--primary); }
.stat-card.accent-green::before { background: var(--green); }
.stat-card.accent-amber::before { background: var(--amber); }
.stat-card.accent-slate::before { background: var(--slate); }
.stat-top { display: flex; align-items: center; justify-content: space-between; }
.stat-value { font-size: 1.9rem; font-weight: 700; letter-spacing: -.02em; }
.stat-icon { width: 26px; height: 26px; fill: none; stroke: currentColor; stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round; color: var(--text-soft); }
.stat-label { color: var(--text-muted); font-size: .86rem; margin-top: .15rem; }

/* ----- Dashboard layout ----- */
.dash-columns { display: grid; grid-template-columns: 360px 1fr; gap: 1rem; align-items: start; }

.status-bar { display: flex; height: 12px; border-radius: 6px; overflow: hidden; background: var(--surface-2); gap: 2px; margin-bottom: 1rem; }
.status-seg { min-width: 0; }
.status-seg.status-draft { background: var(--slate); }
.status-seg.status-released { background: var(--green); }
.status-seg.status-archived { background: var(--amber); }
.legend { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: .55rem; }
.legend li { display: flex; align-items: center; gap: .6rem; }
.legend-count { margin-left: auto; font-weight: 600; color: var(--text); }

/* ----- Tables -----
   A data table is the one block in this app that legitimately wants more width
   than a phone has. It must therefore scroll, not be clipped: `.card` clips to
   its radius, so a table dropped straight into a card loses its right-hand
   columns — and the right-hand column is always Actions. `.table-scroll` is the
   container that scrolls instead, and `.card` stops clipping horizontally
   wherever a table is still unwrapped (see the `:has()` rule below).

   The right-edge shade is the affordance. It is painted with the classic
   background-attachment trick — two fixed "cover" layers over two local
   "shadow" layers — so it appears only while the table actually overflows and
   fades out at each end of the scroll, with no script and no resize observer. */
.table-scroll {
    overflow-x: auto;
    overscroll-behavior-x: contain;
    background:
        linear-gradient(to right, var(--surface) 30%, rgba(255, 255, 255, 0)) left center / 28px 100% no-repeat local,
        linear-gradient(to left,  var(--surface) 30%, rgba(255, 255, 255, 0)) right center / 28px 100% no-repeat local,
        linear-gradient(to right, rgba(16, 24, 40, .12), rgba(255, 255, 255, 0)) left center / 12px 100% no-repeat scroll,
        linear-gradient(to left,  rgba(16, 24, 40, .12), rgba(255, 255, 255, 0)) right center / 12px 100% no-repeat scroll;
}
:root[data-theme="dark"] .table-scroll {
    background:
        linear-gradient(to right, var(--surface) 30%, rgba(23, 26, 35, 0)) left center / 28px 100% no-repeat local,
        linear-gradient(to left,  var(--surface) 30%, rgba(23, 26, 35, 0)) right center / 28px 100% no-repeat local,
        linear-gradient(to right, rgba(0, 0, 0, .45), rgba(0, 0, 0, 0)) left center / 12px 100% no-repeat scroll,
        linear-gradient(to left,  rgba(0, 0, 0, .45), rgba(0, 0, 0, 0)) right center / 12px 100% no-repeat scroll;
}
/* The region is a tab stop so it can be scrolled from the keyboard; without it the
   columns are reachable by mouse and by nothing else. */
.table-scroll:focus-visible { outline: 3px solid var(--primary); outline-offset: -3px; }

/* The header row stays put while the rows below it scroll, and the first column
   stays put while the table scrolls sideways — otherwise, four columns in, the
   reader no longer knows which row they are on. Both need an opaque background
   of their own, since a sticky cell paints over what passes beneath it. */
.table-scroll .data-table { min-width: 100%; width: auto; }
.table-scroll .data-table thead th { position: sticky; top: 0; z-index: 2; background: var(--surface); }
.table-scroll .data-table th.sticky-col,
.table-scroll .data-table td.sticky-col { position: sticky; left: 0; background: var(--surface); }
.table-scroll .data-table thead th.sticky-col { z-index: 3; }
.data-table.hover tbody tr:hover td.sticky-col, .data-table tr.clickable:hover td.sticky-col { background: var(--surface-2); }

/* Until every list page wraps its table, a card that holds one directly scrolls
   rather than clipping. It is the weaker fix — the card header scrolls with the
   table — but it is the difference between "the Edit button is behind a swipe"
   and "the Edit button does not exist on this device". */
.card:has(> .data-table),
.card:has(> .card-body > .data-table) { overflow-x: auto; overflow-y: hidden; }

.data-table { width: 100%; border-collapse: collapse; font-size: .9rem; }
.data-table th { text-align: left; padding: .7rem 1rem; font-size: .74rem; text-transform: uppercase; letter-spacing: .04em; color: var(--text-soft); font-weight: 600; border-bottom: 1px solid var(--border); white-space: nowrap; }
.data-table td { padding: .7rem 1rem; border-bottom: 1px solid var(--border); }
.data-table tbody tr:last-child td { border-bottom: none; }
.data-table .num { text-align: right; font-variant-numeric: tabular-nums; }
.data-table .center { text-align: center; }
.data-table th.num { text-align: right; }
.data-table.hover tbody tr:hover, .data-table tr.clickable:hover { background: var(--surface-2); }
.data-table tr.clickable { cursor: pointer; }
.actions-col { text-align: right; white-space: nowrap; width: 1%; }

/* ----- Badges & pills ----- */
.badge { display: inline-flex; align-items: center; padding: .2rem .6rem; border-radius: 100px; font-size: .74rem; font-weight: 600; }
.badge.status-draft { background: var(--slate-soft); color: var(--slate); }
.badge.status-released { background: var(--green-soft); color: var(--green); }
.badge.status-archived { background: var(--amber-soft); color: var(--amber); }
.pill { display: inline-flex; padding: .15rem .55rem; border-radius: 100px; font-size: .74rem; font-weight: 600; }
.pill.ok { background: var(--green-soft); color: var(--green); }
.pill.off { background: var(--surface-2); color: var(--text-muted); }

/* ----- Toolbar (search + filter) ----- */
.toolbar { display: flex; gap: .8rem; margin-bottom: 1.1rem; flex-wrap: wrap; }
.search { position: relative; flex: 1; min-width: 220px; max-width: 380px; }
.search svg { position: absolute; left: .7rem; top: 50%; transform: translateY(-50%); width: 16px; height: 16px; fill: none; stroke: var(--text-soft); stroke-width: 2; stroke-linecap: round; }
.search input { width: 100%; padding: .55rem .8rem .55rem 2.1rem; border: 1px solid var(--border-strong); border-radius: var(--radius-sm); font-size: .9rem; background: var(--surface); }
.search input:focus { outline: none; border-color: var(--primary); box-shadow: 0 0 0 3px var(--primary-soft); }

.seg { display: inline-flex; background: var(--surface-2); border-radius: var(--radius-sm); padding: 3px; gap: 2px; }
.seg button { border: none; background: none; cursor: pointer; padding: .4rem .8rem; border-radius: 6px; font-size: .82rem; font-weight: 600; color: var(--text-muted); white-space: nowrap; }
.seg button.active { background: var(--surface); color: var(--primary); box-shadow: var(--shadow-sm); }
.seg button:hover:not(.active) { color: var(--text); }

/* ----- Forms ----- */
/* EditableArea's wrapper. A fieldset is the only element that can disable a
   whole form at once, and it arrives with a border, padding and a min-width of
   min-content that would break every grid inside it. */
.form-fieldset { border: 0; margin: 0; padding: 0; min-width: 0; }

.form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem 1.1rem; }
.field { display: flex; flex-direction: column; gap: .35rem; }
.field.wide { grid-column: 1 / -1; }
/* Two shapes of field coexist: `<label class="field"><span>Name</span><input></label>`
   (implicit association) and `<div class="field"><label for="x">Name</label>…` —
   the second is used wherever a hint or an error has to sit *outside* the label,
   because anything inside a label is folded into the control's accessible name. */
.field > span:not(.field-hint):not(.field-error),
.field > label { font-size: .82rem; font-weight: 600; color: var(--text-muted); }
/* The label is the target: clicking the words toggles the box, so 2.5.8
   measures this and not the 13px control inside it. Four of the five wrap
   onto two lines and clear 24px on their own; the one whose text fits on
   one line came to 19. */
.field.check { flex-direction: row; align-items: center; gap: .5rem; min-height: 24px; }
.field.check span { color: var(--text); }
.input { width: 100%; padding: .5rem .7rem; border: 1px solid var(--border-strong); border-radius: var(--radius-sm); font-size: .9rem; background: var(--surface); color: var(--text); font-family: inherit; }
.input:focus { outline: none; border-color: var(--primary); box-shadow: 0 0 0 3px var(--primary-soft); }
.input.error { border-color: var(--red); }
.input.mini { padding: .4rem .5rem; }
.input.num { text-align: right; font-variant-numeric: tabular-nums; }
select.input { cursor: pointer; }
.field-error { font-size: .76rem; color: var(--red); }
.form-banner { padding: .65rem 1.2rem; font-size: .85rem; }
.form-banner.error { background: var(--red-soft); color: var(--red); border-bottom: 1px solid var(--red-border); }

/* ----- Editor ----- */
.editor-grid { display: grid; grid-template-columns: 1fr 300px; gap: 1rem; margin-bottom: 1rem; align-items: start; }
.summary-card { position: sticky; top: 76px; }
.summary { margin: 0; display: flex; flex-direction: column; gap: .7rem; }
.summary > div { display: flex; align-items: center; justify-content: space-between; }
.summary dt { color: var(--text-muted); font-size: .85rem; }
.summary dd { margin: 0; font-weight: 600; font-variant-numeric: tabular-nums; }
.summary .highlight { border-top: 1px solid var(--border); padding-top: .7rem; margin-top: .1rem; }
.summary .highlight dt { color: var(--text); font-weight: 600; }
.summary .highlight dd { color: var(--primary); font-size: 1.1rem; }

/* A labelled control inside a table cell: the label text is for assistive
   technology only, so the wrapper must not add a line box of its own. */
.cell-field { display: block; }

.op-table th, .op-table td { padding: .45rem .6rem; }
.op-table td { vertical-align: middle; }
.op-table .narrow { width: 70px; }
.op-table .wc-col { width: 230px; }
.op-table .input.mini.num { width: 80px; }

/* ----- Empty / loading ----- */
.empty-state { text-align: center; padding: 3.5rem 1rem; color: var(--text-muted); }
.empty-state.small { padding: 2rem 1rem; }
.empty-state svg { width: 42px; height: 42px; fill: none; stroke: var(--border-strong); stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; margin-bottom: .8rem; }
.empty-state p { font-size: .92rem; }

.loading-row { display: flex; justify-content: center; padding: 3rem; }
.spinner { width: 28px; height: 28px; border: 3px solid var(--border); border-top-color: var(--primary); border-radius: 50%; animation: spin .7s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }

/* ----- Modal ----- */
.modal-backdrop-custom { position: fixed; inset: 0; background: var(--scrim); display: flex; align-items: center; justify-content: center; z-index: 100; padding: 1rem; animation: fade .12s ease; }
@keyframes fade { from { opacity: 0; } }
.modal-card { background: var(--surface); border-radius: var(--radius-lg); box-shadow: var(--shadow-lg); width: 100%; max-width: 560px; overflow: hidden; animation: pop .14s ease; }
@keyframes pop { from { transform: translateY(8px) scale(.98); opacity: 0; } }
.modal-head { display: flex; align-items: center; justify-content: space-between; padding: 1.1rem 1.3rem; border-bottom: 1px solid var(--border); }
.modal-body { padding: 1.3rem; }
.modal-body .form-grid { gap: .9rem 1.1rem; }
.modal-foot { display: flex; justify-content: flex-end; gap: .5rem; padding: 1rem 1.3rem; background: var(--surface-2); border-top: 1px solid var(--border); }

/* ----- About ----- */
.about-grid { display: grid; grid-template-columns: minmax(0, 1fr) 320px; gap: 1rem; align-items: start; }
.about-side { display: flex; flex-direction: column; gap: 1rem; }
.lead { font-size: 1.02rem; line-height: 1.6; color: var(--text); }
.about-sub { margin-top: 1.4rem; margin-bottom: .7rem; color: var(--text-muted); text-transform: uppercase; font-size: .76rem; letter-spacing: .05em; }
.tech-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: .7rem; }
.tech-list li { display: flex; gap: .65rem; align-items: flex-start; font-size: .92rem; line-height: 1.45; }
.tick { width: 18px; height: 18px; flex-shrink: 0; margin-top: 2px; fill: none; stroke: var(--green); stroke-width: 2.4; stroke-linecap: round; stroke-linejoin: round; }
.tech-badges { display: flex; flex-wrap: wrap; gap: .45rem; margin-top: 1.4rem; }
.tech-badge { background: var(--primary-soft); color: var(--primary); font-size: .78rem; font-weight: 600; padding: .3rem .65rem; border-radius: 7px; }
.small-text { font-size: .85rem; margin-bottom: .9rem; }

/* ----- 404 ----- */
.notfound { text-align: center; padding: 5rem 1rem; }
.notfound-code { font-size: 4.5rem; font-weight: 800; color: var(--primary-soft); line-height: 1; }
.notfound h1 { margin: .3rem 0 .6rem; }
.notfound .btn { margin-top: 1.2rem; }

/* ----- Mobile drawer ----- */
.scrim { display: none; }
@media (max-width: 920px) {
    .dash-columns, .editor-grid, .about-grid { grid-template-columns: 1fr; }
    .stat-grid { grid-template-columns: repeat(2, 1fr); }
    .summary-card { position: static; }
}
@media (max-width: 720px) {
    /* `transform` alone slides the drawer off-screen but leaves its seven links
       focusable: tabbing from the skip link used to walk through a navigation
       nobody can see. `visibility: hidden` takes them out of the tab order and is
       delayed until the slide has finished so the close still animates. The
       markup also sets `inert`, for the browsers that support it. */
    .sidebar {
        position: fixed; left: 0; top: 0; z-index: 60;
        transform: translateX(-100%);
        visibility: hidden;
        transition: transform .2s ease, visibility 0s linear .2s;
    }
    .app-shell.nav-open .sidebar {
        transform: translateX(0); visibility: visible; box-shadow: var(--shadow-lg);
        transition: transform .2s ease, visibility 0s;
    }
    .app-shell.nav-open .scrim { display: block; position: fixed; inset: 0; background: rgba(0, 0, 0, .4); z-index: 50; }
    .hamburger { display: inline-flex; }
    .page-body { padding: 1.1rem; }
    .page-head { flex-direction: column; }
    .form-grid { grid-template-columns: 1fr; }
    .data-table { font-size: .84rem; }

    /* iOS Safari zooms the whole layout in when a focused field is under 16 px and
       never zooms back out, so every tap on every form in the app used to leave the
       user pinching. The value is absolute on purpose: it is a browser threshold,
       not a design size. */
    .input, .search input { font-size: 16px; }
}

/* ----- Scheduling ----- */
/* Result first, parameter rail second - see the comment at the top of
   Pages/Schedule.razor and docs/adr/0026. The rail is on the right because that
   is the only side where the markup order, the focus order and the reading order
   all agree with the single-column order a phone gets. It stays sticky, so it
   is still in reach while the reader is somewhere down the Gantt. */
.sched-layout { display: grid; grid-template-columns: 1fr 290px; gap: 1rem; align-items: start; }
.param-card { position: sticky; top: 76px; }
.param-grid { display: flex; flex-direction: column; gap: .85rem; }
.sched-results { display: flex; flex-direction: column; gap: 1rem; min-width: 0; }
.sched-results .stat-grid { margin-bottom: 0; }
.sched-results .stat-value { font-size: 1.5rem; }

.btn-ico { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round; }

/* Gantt chart */
.gantt { display: flex; flex-direction: column; gap: 6px; overflow-x: auto; scroll-behavior: smooth; }
.gantt:focus-visible, .chat-thread:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; border-radius: var(--radius-sm); }
.gantt-row { display: flex; align-items: center; gap: .8rem; }
.gantt-label { width: 150px; flex-shrink: 0; font-size: .8rem; color: var(--text-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* 32px, so a bar inset by 4 is 24 tall and clears 2.5.8 in that dimension.
   Their width is the one thing here that cannot be padded - it is the
   operation's duration - see docs/TESTING.md on target size. */
.gantt-track { position: relative; flex: 1; height: 32px; background: var(--surface-2); border-radius: 6px; min-width: 340px; }
.gantt-grid { position: absolute; top: 0; bottom: 0; width: 1px; background: var(--border-strong); opacity: .7; }
/* Bars and closed stretches are <button>s: they carry the only explanation of why
   a machine ran or stood still, so they have to be reachable by something other
   than a mouse pointer. A sliver of a bar is still a 24 px-tall target, and the
   roving tabindex (see Schedule.razor) keeps the whole chart to one tab stop. */
.gantt-bar, .gantt-closed { border: 0; font: inherit; margin: 0; background-color: transparent; }
.gantt-bar { position: absolute; top: 4px; bottom: 4px; min-width: 3px; border-radius: 4px; display: flex; align-items: center; padding: 0 .4rem; overflow: hidden; box-shadow: var(--shadow-sm); cursor: pointer; }
.gantt-bar span { font-size: .7rem; font-weight: 600; color: #fff; white-space: nowrap; text-shadow: 0 1px 1px rgba(0, 0, 0, .3); }
.gantt-bar:focus-visible, .gantt-closed:focus-visible { outline: 3px solid var(--primary); outline-offset: 1px; z-index: 5; }
.gantt-bar[aria-pressed="true"], .gantt-closed[aria-pressed="true"] { box-shadow: 0 0 0 2px var(--surface), 0 0 0 4px var(--primary); z-index: 4; }

/* The explanation the bars open. It is a live region, so it exists from the first
   render and is only filled in on activation — a region inserted already populated
   is not announced. */
.gantt-detail { margin-top: .9rem; padding: .6rem .8rem; border-radius: var(--radius-sm); background: var(--surface-2); font-size: .86rem; line-height: 1.45; min-height: 1.4em; }
.gantt-detail:empty { padding: 0; background: none; }
.gantt-hint { font-size: .76rem; color: var(--text-muted); margin-top: .6rem; }
.gantt-table-toggle { margin-left: auto; }
.gantt-axis { display: flex; align-items: center; gap: .8rem; margin-top: 2px; height: 16px; }
.gantt-axis .gantt-track { height: 16px; background: none; min-width: 340px; }
.gantt-tick { position: absolute; top: 0; transform: translateX(-50%); font-size: .68rem; color: var(--text-soft); white-space: nowrap; }
.gantt-legend-note { font-size: .76rem; }

/* a late job's bars get a red ring so tightening the targets is immediately visible */
.gantt-bar.late { outline: 2px solid var(--red); outline-offset: -2px; }
.gantt-head-meta { display: flex; align-items: center; gap: .9rem; }
.gantt-legend { display: inline-flex; align-items: center; gap: .35rem; font-size: .76rem; color: var(--red); font-weight: 600; }
.gantt-legend-swatch { width: 12px; height: 12px; border-radius: 3px; }
.gantt-legend-swatch.late { background: var(--red-soft); outline: 2px solid var(--red); outline-offset: -2px; }

/* Job colour palette (shared by Gantt bars and the table dots) */
.job-color-0 { background: #4f46e5; }
.job-color-1 { background: #0e7490; }
.job-color-2 { background: #15803d; }
.job-color-3 { background: #b45309; }
.job-color-4 { background: #be185d; }
.job-color-5 { background: #7c3aed; }
.job-color-6 { background: #b91c1c; }
.job-color-7 { background: #0f766e; }
/* The dot carries the job's letter as well as its colour. Eight hues are the only
   thing that told two jobs apart on the chart and in this table; a letter survives
   colour blindness, a greyscale print and a forced-colours theme. */
.color-dot {
    display: inline-flex; align-items: center; justify-content: center;
    width: 16px; height: 16px; border-radius: 50%; margin-right: .5rem; vertical-align: -3px;
    font-size: .62rem; font-weight: 700; color: #fff; line-height: 1;
}

.pill.ontime { background: var(--green-soft); color: var(--green); }
.pill.late { background: var(--red-soft); color: var(--red); }
.late-text { color: var(--red); font-weight: 600; }

@media (max-width: 920px) {
    .sched-layout { grid-template-columns: 1fr; }
    .param-card { position: static; }
}

/* ============================================================
   Blazor framework styles (loading, errors)
   ============================================================ */
/* The three EditForm state classes and the framework's default error-boundary
   block used to live here. The app contains no EditForm anywhere, and App.razor
   supplies its own ErrorContent, so none of them could ever match; the boundary
   block also carried the only untranslated English string in the stylesheet. */

#blazor-error-ui {
    color-scheme: light only;
    background: lightyellow;
    bottom: 0;
    box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
    box-sizing: border-box;
    display: none;
    left: 0;
    padding: 0.6rem 1.25rem 0.7rem 1.25rem;
    position: fixed;
    width: 100%;
    z-index: 1000;
}
#blazor-error-ui .dismiss { cursor: pointer; position: absolute; right: 0.75rem; top: 0.5rem; }

/* Boot screen: shown by index.html until the WebAssembly runtime starts. */
.boot { min-height: 100vh; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 1rem; background: var(--bg); color: var(--text); }
.boot-mark {
    width: 56px; height: 56px; border-radius: 16px; display: flex; align-items: center; justify-content: center;
    background: linear-gradient(135deg, var(--primary), var(--primary-accent)); color: #fff; font-weight: 700; font-size: 1.15rem;
    box-shadow: 0 8px 24px rgba(79, 70, 229, .35);
}
.boot-note { font-size: .84rem; color: var(--text-muted); }
.loading-progress { display: block; width: 4.5rem; height: 4.5rem; }
.loading-progress circle { fill: none; stroke: var(--border); stroke-width: 0.5rem; transform-origin: 50% 50%; transform: rotate(-90deg); }
.loading-progress circle:last-child { stroke: var(--primary); stroke-dasharray: calc(3.141 * var(--blazor-load-percentage, 0%) * 0.8), 500%; transition: stroke-dasharray 0.05s ease-in-out; }
.loading-progress-text { text-align: center; font-weight: 600; font-variant-numeric: tabular-nums; color: var(--text-muted); font-size: .9rem; }
.loading-progress-text:after { content: var(--blazor-load-percentage-text, "Loading"); }

/* ----- Dashboard glance cards ----- */
.dash-columns.glance { grid-template-columns: 1fr 1fr; margin-top: 1rem; }
.glance-list { margin: 0; display: grid; grid-template-columns: 1fr 1fr; gap: .8rem 1rem; }
.glance-list dt { font-size: .76rem; text-transform: uppercase; letter-spacing: .04em; color: var(--text-soft); font-weight: 600; }
.glance-list dd { margin: .15rem 0 0; font-weight: 600; font-size: .95rem; }
.glance-list dd .muted { font-weight: 400; font-size: .84rem; }
.glance-kpis { display: grid; grid-template-columns: repeat(3, 1fr); gap: .8rem; margin-bottom: .9rem; }
.glance-kpi { padding: .7rem .8rem; border-radius: var(--radius-sm); background: var(--surface-2); }
.glance-kpi strong { display: block; font-size: 1.3rem; letter-spacing: -.02em; }
.glance-kpi span { font-size: .78rem; color: var(--text-muted); }
.glance-kpi.late strong { color: var(--red); }
.glance-foot { display: flex; align-items: center; justify-content: space-between; gap: 1rem; font-size: .82rem; }
@media (max-width: 920px) {
    .dash-columns.glance { grid-template-columns: 1fr; }
}

/* ----- Schedule assistant ----- */
.assistant-head-left { display: flex; align-items: center; gap: .6rem; }
.assistant-actions { display: flex; align-items: center; gap: .5rem; }
.btn-sm { padding: .35rem .7rem; font-size: .82rem; }
.pill.ai { background: var(--primary-soft); color: var(--primary); }
.assistant-sub { font-size: .84rem; margin-bottom: .9rem; }
.assistant-lines { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: .6rem; }
.assistant-line { display: flex; gap: .6rem; align-items: baseline; font-size: .9rem; line-height: 1.45; }
.assistant-dot { width: 7px; height: 7px; border-radius: 50%; flex-shrink: 0; margin-top: .45em; background: var(--slate); }
.assistant-line.tone-good .assistant-dot { background: var(--green); }
.assistant-line.tone-info .assistant-dot { background: var(--primary); }
.assistant-line.tone-warning .assistant-dot { background: var(--amber); }
.assistant-note { margin-top: .9rem; padding: .5rem .7rem; font-size: .82rem; color: var(--amber); background: var(--amber-soft); border-radius: var(--radius-sm); }
.assistant-privacy { font-size: .8rem; color: var(--text-muted); line-height: 1.4; margin: 0; }

/* ----- Schedule chat ----- */
.chat { margin-top: 1.2rem; padding-top: 1rem; border-top: 1px solid var(--border); }
.chat-title { font-size: .95rem; font-weight: 600; margin: 0 0 .25rem; }
.chat-intro { font-size: .82rem; margin: 0 0 .9rem; line-height: 1.4; }
.chat-thread { list-style: none; margin: 0 0 .9rem; padding: 0; display: flex; flex-direction: column; gap: .7rem; max-height: 28rem; overflow-y: auto; }
/* The thread is rendered before the first question so that it is a live region by
   the time one is asked; empty, it must not reserve space — and it must not be
   display:none either, or it leaves the accessibility tree and announces nothing. */
.chat-thread.empty { margin-bottom: 0; }
.chat-turn { display: flex; flex-direction: column; gap: .3rem; max-width: 92%; }
.chat-turn.user { align-self: flex-end; align-items: flex-end; }
.chat-turn.assistant { align-self: flex-start; align-items: flex-start; }
.chat-bubble { padding: .55rem .8rem; border-radius: var(--radius); font-size: .9rem; line-height: 1.5; white-space: pre-line; overflow-wrap: anywhere; }
.chat-turn.user .chat-bubble { background: var(--primary); color: #fff; border-bottom-right-radius: 4px; }
.chat-turn.assistant .chat-bubble { background: var(--surface-2); color: var(--text); border-bottom-left-radius: 4px; }
.chat-source { font-size: .7rem; }
.chat-note { margin-top: 0; }
.chat-busy { display: flex; align-items: center; gap: .5rem; font-size: .84rem; color: var(--text-muted); margin-bottom: .8rem; }
.chat-busy .spinner { width: 16px; height: 16px; border-width: 2px; }
.chat-suggestions { display: flex; flex-wrap: wrap; gap: .4rem; margin-bottom: .7rem; }
.chip { border: 1px solid var(--border); background: var(--surface); color: var(--text); border-radius: 100px; padding: .3rem .75rem; font: inherit; font-size: .8rem; cursor: pointer; transition: background .12s, border-color .12s, color .12s; }
.chip:hover:not(:disabled) { background: var(--primary-soft); border-color: var(--primary); color: var(--primary); }
.chip:disabled { opacity: .55; cursor: default; }
.chat-input { display: flex; gap: .5rem; flex-wrap: wrap; }
.chat-input .input { flex: 1 1 14rem; min-width: 0; }
@media (max-width: 640px) {
    .chat-turn { max-width: 100%; }
}

.field-hint { font-size: .74rem; color: var(--text-muted); line-height: 1.35; }

/* Visible to assistive technology only. */
.sr-only {
    position: absolute;
    width: 1px; height: 1px;
    padding: 0; margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}

/* ----- Production orders ----- */
.badge.order-draft { background: var(--slate-soft); color: var(--slate); }
.badge.order-released { background: var(--green-soft); color: var(--green); }
.badge.order-cancelled { background: var(--red-soft); color: var(--red); }
.pill.frozen { background: var(--primary-soft); color: var(--primary); margin-left: .5rem; }

/* ----- Working time: calendars on the Gantt ----- */
.gantt-closed {
    position: absolute; top: 0; bottom: 0; min-width: 3px; padding: 0;
    background: repeating-linear-gradient(135deg, rgba(100, 108, 128, .18) 0 4px, transparent 4px 8px);
    border-left: 1px solid rgba(100, 108, 128, .18);
    cursor: pointer;
}
.gantt-closed.closed-break { background: repeating-linear-gradient(135deg, rgba(79, 70, 229, .16) 0 3px, transparent 3px 6px); }
.gantt-closed.closed-sunday, .gantt-closed.closed-holiday { background: rgba(180, 83, 9, .14); }
.gantt-closed.closed-absence { background: repeating-linear-gradient(135deg, rgba(220, 38, 38, .16) 0 4px, transparent 4px 8px); }
.gantt-closed.closed-rest { background: rgba(21, 128, 61, .12); }
.gantt-legend-swatch.closed { display: inline-block; vertical-align: -2px; margin-right: .3rem; background: repeating-linear-gradient(135deg, rgba(100, 108, 128, .35) 0 3px, transparent 3px 6px); border: 1px solid var(--border-strong); }
.gantt-bar.paused { background-image: repeating-linear-gradient(135deg, rgba(255, 255, 255, .22) 0 6px, transparent 6px 12px); }

/* ----- Working time page ----- */
.wt-layout { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 1rem; align-items: start; margin-bottom: 1rem; }
.wt-actions { margin-top: 1rem; justify-content: flex-end; }
.wt-summary { margin-top: .9rem; font-size: .9rem; }
.wt-applications { margin-top: .4rem; }
.input-unit { display: flex; align-items: center; gap: .5rem; }
.input-unit .input { width: 90px; }
.field.inline { flex-direction: row; align-items: center; gap: .5rem; }
.form-banner.success { background: var(--green-soft); color: var(--green); border-bottom: 1px solid var(--green-border); border-radius: var(--radius-sm); margin-bottom: 1rem; }
.form-banner.error { border-radius: var(--radius-sm); margin-bottom: 1rem; }
.pill.law { background: var(--slate-soft); color: var(--slate); font-family: "Cascadia Code", "Consolas", ui-monospace, monospace; font-weight: 600; margin-right: .35rem; }
.rule-list { margin: 0; display: flex; flex-direction: column; gap: .9rem; }
.rule dt { font-weight: 600; margin-bottom: .2rem; }
.rule dd { margin: 0; color: var(--text-muted); font-size: .88rem; line-height: 1.5; }
.legend-inline { flex-direction: row; flex-wrap: wrap; gap: .4rem 1rem; margin-top: .7rem; font-size: .78rem; color: var(--text-muted); }
.legend-inline li { gap: .35rem; }
.week-swatch { display: inline-block; width: 12px; height: 12px; border-radius: 3px; border: 1px solid var(--border-strong); }

.week-strip { display: flex; flex-direction: column; gap: 4px; }
.week-row, .week-axis { display: flex; align-items: center; gap: .6rem; }
.week-day { width: 34px; flex-shrink: 0; font-size: .76rem; color: var(--text-muted); text-align: right; }
.week-axis > span:first-child { width: 34px; flex-shrink: 0; }
.week-track { position: relative; flex: 1; height: 18px; background: var(--surface-2); border-radius: 4px; overflow: hidden; }
.week-axis .week-track { height: 14px; background: none; overflow: visible; }
.week-seg { position: absolute; top: 0; bottom: 0; }
.week-tick { position: absolute; top: 0; transform: translateX(-50%); font-size: .66rem; color: var(--text-soft); }
.week-tick:first-child { transform: none; }
.week-tick:last-child { transform: translateX(-100%); }
.seg-working { background: var(--primary); }
.seg-break { background: #a5b4fc; }
.seg-offshift { background: var(--slate-soft); }
.seg-rest { background: #bbf7d0; }
.seg-sunday { background: var(--amber-soft); }
.seg-holiday { background: var(--amber-soft); }
.seg-absence { background: var(--red-soft); }

@media (max-width: 920px) {
    .wt-layout { grid-template-columns: 1fr; }
}

/* ----- Persona (who you work as) ----- */
.persona { position: relative; }
.persona-summary {
    list-style: none; cursor: pointer;
    display: inline-flex; align-items: center; gap: .55rem;
    padding: .3rem .6rem .3rem .35rem; border-radius: 100px;
    border: 1px solid var(--border); background: var(--surface);
    font-size: .84rem; font-weight: 600; color: var(--text);
}
.persona-summary::-webkit-details-marker { display: none; }
.persona-summary:hover { border-color: var(--border-strong); }
.persona-summary:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }
.persona-caret { width: 14px; height: 14px; fill: none; stroke: var(--text-soft); stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.persona[open] .persona-caret { transform: rotate(180deg); }
.persona-avatar {
    width: 26px; height: 26px; border-radius: 50%; flex-shrink: 0;
    display: inline-flex; align-items: center; justify-content: center;
    font-size: .74rem; font-weight: 700; color: var(--on-primary);
}
.persona-avatar.role-planner { background: var(--primary); }
.persona-avatar.role-supervisor { background: var(--amber); }
.persona-avatar.role-guest { background: var(--slate); }
.persona-panel {
    position: absolute; right: 0; top: calc(100% + 6px); z-index: 40;
    width: min(320px, 90vw); padding: .6rem;
    background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
    box-shadow: var(--shadow-lg);
    display: flex; flex-direction: column; gap: .25rem;
}
.persona-hint { font-size: .76rem; color: var(--text-muted); padding: .2rem .5rem .5rem; margin: 0; line-height: 1.4; }
.persona-option {
    display: flex; align-items: center; gap: .7rem; text-align: left;
    width: 100%; padding: .5rem .55rem; border-radius: var(--radius-sm);
    border: 1px solid transparent; background: none; cursor: pointer; color: var(--text); font: inherit;
}
.persona-option:hover { background: var(--surface-2); }
.persona-option:focus-visible { outline: 2px solid var(--primary); outline-offset: -2px; }
.persona-option.active { border-color: var(--primary); background: var(--primary-soft); }
.persona-text { display: flex; flex-direction: column; line-height: 1.3; }
.persona-role { font-weight: 600; font-size: .88rem; }
.persona-desc { font-size: .76rem; color: var(--text-muted); }

.readonly-notice {
    display: flex; align-items: center; gap: .6rem;
    padding: .6rem .9rem; margin-bottom: 1rem;
    background: var(--slate-soft); color: var(--slate);
    border-radius: var(--radius-sm); font-size: .84rem;
}
.readonly-notice svg { width: 16px; height: 16px; flex-shrink: 0; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }

@media (max-width: 720px) {
    .persona-name { display: none; }
}

/* ----- Theme toggle & skip link ----- */
.skip-link {
    position: absolute; left: 1rem; top: -100px; z-index: 200;
    padding: .55rem .9rem; border-radius: var(--radius-sm);
    background: var(--primary); color: var(--on-primary); font-weight: 600;
}
.skip-link:focus { top: .75rem; outline: 3px solid var(--primary-accent); }

.theme-toggle {
    background: var(--surface); border: 1px solid var(--border); cursor: pointer;
    width: 34px; height: 34px; border-radius: 50%;
    display: inline-flex; align-items: center; justify-content: center; color: var(--text-muted);
}
.theme-toggle:hover { border-color: var(--border-strong); color: var(--text); }
.theme-toggle svg { width: 17px; height: 17px; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }

/* ----- Reading measures on a wide screen -----
   Text and form fields stop growing once the window is wider than they need;
   tables, the Gantt and the weekly preview keep every pixel. The measures live
   behind a breakpoint on purpose: below it they must not bind, or a font that
   renders a little wider on another operating system would wrap one line and
   shift every screenshot under it (which is exactly how the CI caught them). */
@media (min-width: 1700px) {
    .page-sub, .assistant-sub, .chat-intro { max-width: 820px; }
    .assistant-line { max-width: 900px; }
    .chat-turn { max-width: min(92%, 780px); }
    .form-grid { max-width: 940px; }
    .modal-body .form-grid { max-width: none; }
    .about-grid { max-width: 1180px; }
    .editor-grid { grid-template-columns: minmax(0, 1000px) 300px; }
}

/* ----- Windows high-contrast / forced colours -----
   In a forced-colours theme the browser throws away every author colour and every
   background image and repaints with the user's palette. That is the right default
   for chrome, and wrong for the three places in this app where a colour or a hatch
   *is* the datum. Those opt out explicitly with forced-color-adjust: none; the
   hatched closed-time stretches cannot (a background-image is stripped regardless),
   so they switch to a border style, which does survive. */
@media (forced-colors: active) {
    .job-color-0, .job-color-1, .job-color-2, .job-color-3,
    .job-color-4, .job-color-5, .job-color-6, .job-color-7,
    .status-seg, .week-seg, .assistant-dot, .persona-avatar { forced-color-adjust: none; }

    .gantt-bar { border: 1px solid CanvasText; }
    .gantt-bar.late { outline: 3px solid Highlight; outline-offset: -3px; }

    .gantt-closed { background: Canvas; border: 2px solid CanvasText; }
    .gantt-closed.closed-break { border-style: dotted; }
    .gantt-closed.closed-sunday, .gantt-closed.closed-holiday { border-style: double; border-width: 4px; }
    .gantt-closed.closed-absence { border-style: dashed; }
    .gantt-closed.closed-rest { border-style: solid; }

    /* Pills, badges and banners are drawn as a tinted ground with no border, so
       without one they lose every edge and read as loose words. */
    .badge, .pill, .tech-badge, .chip, .glance-kpi, .readonly-notice,
    .form-banner, .assistant-note, .week-swatch, .gantt-legend-swatch { border: 1px solid CanvasText; }

    .card, .stat-card, .modal-card { border: 1px solid CanvasText; }
    .btn, .icon-btn, .culture-btn, .seg button, .theme-toggle { border: 1px solid ButtonText; }
    .btn-primary, .seg button.active, .culture-btn.active { border-color: Highlight; }
    .nav-link.active { border: 1px solid Highlight; }

    /* The scroll shade is a gradient, so it is stripped; an edge rule replaces it
       as the "there is more to the side" affordance. */
    .table-scroll { background: none; border-inline: 1px solid CanvasText; }
}

@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after { animation-duration: .001ms !important; animation-iteration-count: 1 !important; transition-duration: .001ms !important; }
    /* The drawer's visibility swap is delayed by the slide; with the slide gone the
       delay would leave the closed drawer focusable for 200 ms. */
    .sidebar { transition-delay: 0s !important; }
    .gantt { scroll-behavior: auto; }
}

/* ----- Account chip (top bar, only when an API is configured) -----
   Deliberately the persona chip's shape and avatar: the two modes occupy the
   same slot and should read as the same control, one of which happens to be
   backed by a real account. The narrow-screen rule above already hides
   .persona-name, so the chip degrades to the avatar like the persona does. */
.persona.account {
    display: inline-flex; align-items: center; gap: .5rem;
    padding: .3rem .35rem .3rem .35rem; border-radius: 100px;
    border: 1px solid var(--border); background: var(--surface);
    font-size: .84rem; font-weight: 600; color: var(--text);
}
.btn.small { padding: .32rem .6rem; font-size: .8rem; }
.sign-in-card { max-width: 520px; }

/* ----- Controls that had to ship as inline styles -----
   Three places needed a button that does not look like a button: a sortable
   column header, the picker's "create a cost center" affordance, and the error
   summary's list. They were written inline because the streams that added them
   did not own this file; they belong here, where the focus ring and the colour
   tokens are, and where a reader looking for "why is that header clickable"
   finds them. */

/* A column header that sorts. The reset is deliberate and complete: a <button>
   inherits none of a <th>'s typography, and half a reset leaves a control that
   looks right in one theme and wrong in the other. */
.th-sort {
    background: none; border: 0; padding: 0;
    font: inherit; color: inherit; text-align: inherit;
    cursor: pointer;
}
.th-sort:hover { color: var(--text); }
/* No :focus-visible rule here on purpose. The global one at the top of this file
   uses :where(), which contributes no specificity, so any rule written here would
   win and would have to re-state the ring correctly — and a near-miss (a token
   that does not exist, say) silently removes it. */

/* A button that reads as a link, for an action that opens a form rather than
   navigating. It is a button and not an anchor because it goes nowhere. */
.link-button {
    background: none; border: 0; padding: .2rem 0;
    font: inherit; text-align: left; cursor: pointer;
}

/* The error summary's list: tight to the heading above it, indented enough for
   the markers to sit outside the text. */
.error-summary-list { margin: .35rem 0 0; padding-left: 1.1rem; }

/* ----- CSV import -----
   The drop zone hides a real <input type=file> over its whole area rather than
   faking one: the file picker cannot be opened from script without a user
   gesture on a real input, and a fake one would leave keyboard users with a
   control that looks operable and is not. :focus-within is what puts the ring
   on the zone when that invisible input takes focus. */
    .import-drop { position: relative; border: 2px dashed var(--border-strong); border-radius: var(--radius); padding: 1.6rem 1rem; text-align: center; background: var(--surface-2); }
    .import-drop.over { border-color: var(--primary); background: var(--primary-soft); }
    .import-drop input[type=file] { position: absolute; inset: 0; width: 100%; height: 100%; opacity: 0; cursor: pointer; }
    .import-drop:focus-within { outline: 3px solid var(--primary); outline-offset: 2px; }
    .import-drop-icon { width: 30px; height: 30px; fill: none; stroke: var(--text-soft); stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round; }
    .import-drop label { font-weight: 600; display: block; margin-top: .3rem; }
    .import-map-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); gap: .9rem 1rem; }
    .import-counts { display: flex; flex-wrap: wrap; gap: .5rem; margin-bottom: 1rem; }
    .import-bar { height: 6px; border-radius: 3px; background: var(--surface-2); overflow: hidden; }
    .import-bar > span { display: block; height: 100%; background: var(--primary); }
/* ----- Export menu -----
   A menu button, not a <details>: the panel needs arrow-key navigation, which
   means real role="menu"/"menuitem" elements. The backdrop is a transparent
   full-screen layer that closes the menu on an outside click without any
   JavaScript; it is aria-hidden and not focusable, so it is invisible to the
   keyboard and to assistive technology. */
.export-menu { position: relative; display: inline-flex; }
.export-trigger { min-width: 7.5rem; justify-content: center; }
.export-trigger:disabled { opacity: .55; cursor: not-allowed; }
.export-spinner { width: 14px; height: 14px; border-width: 2px; }

.export-backdrop { position: fixed; inset: 0; z-index: 39; background: transparent; }

.export-panel {
    position: absolute; right: 0; top: calc(100% + 6px); z-index: 40;
    width: min(260px, 90vw); padding: .4rem;
    background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
    box-shadow: var(--shadow-lg);
    display: flex; flex-direction: column; gap: .1rem;
}
.export-option {
    display: flex; align-items: baseline; justify-content: space-between; gap: .6rem;
    width: 100%; padding: .5rem .6rem; border-radius: var(--radius-sm);
    border: 1px solid transparent; background: none; cursor: pointer;
    color: var(--text); font: inherit; text-align: left;
}
.export-option:hover { background: var(--surface-2); }
.export-option:focus-visible { outline: 2px solid var(--primary); outline-offset: -2px; }
.export-option-name { font-size: .88rem; font-weight: 600; }
.export-option-ext { font-size: .74rem; color: var(--text-muted); font-family: "Cascadia Code", "Consolas", ui-monospace, monospace; }
.export-hint { font-size: .72rem; color: var(--text-muted); line-height: 1.4; margin: .3rem .6rem .2rem; }

.export-error { font-size: .8rem; color: var(--red); margin-top: .4rem; }

/* On a wide screen the head actions sit at the right of the page, so a panel
   anchored to the trigger's right edge opens leftwards into the page. Below the
   breakpoint the head stacks and the actions start at the left margin, and the
   same anchoring sends the panel off the left edge of the screen — measured at
   360 px: the panel's left edge landed at -131 px and every item was
   unreachable. Anchoring to the trigger's left edge there opens it the other
   way, and the width is clamped so it cannot run off the right instead. */
@media (max-width: 480px) {
    .export-panel { right: auto; left: 0; width: min(260px, calc(100vw - 2rem)); }
}

/* ----- "prove this is optimal" -----
   Sits under the KPI cards because that is what it qualifies. The answer is a
   sentence, not a badge: "optimal", "n % above the optimum" and "not proved"
   are three different things and a coloured pill would blur them. */
/* The optimality proof. A card rather than a link-sized button under the KPIs:
   it is the one question on the page whose answer the heuristic cannot give. */
.proof-card { padding: 1.1rem 1.2rem; margin-bottom: 1.2rem; border-left: 4px solid var(--primary); }
.proof-head { display: flex; flex-wrap: wrap; align-items: flex-start; justify-content: space-between; gap: .8rem 1.5rem; }
.proof-intro { flex: 1 1 32ch; min-width: 0; }
.proof-intro h2 { margin: 0 0 .3rem; font-size: 1.05rem; }
.proof-lead { margin: 0; color: var(--text-soft); font-size: .88rem; max-width: 72ch; }
.proof-action { flex: 0 0 auto; min-height: 44px; }
.proof-verdict { display: flex; flex-wrap: wrap; align-items: center; gap: .6rem 1.4rem; margin-top: 1rem; padding-top: .9rem; border-top: 1px solid var(--border); }
.proof-verdict .badge { font-size: .82rem; padding: .3rem .75rem; }
.badge.verdict-proved { background: var(--green-soft); color: var(--green); }
.badge.verdict-better { background: var(--amber-soft); color: var(--amber); }
.badge.verdict-open { background: var(--surface-2); color: var(--text); }
.proof-facts { display: flex; flex-wrap: wrap; gap: .4rem 1.4rem; margin: 0; }
.proof-facts > div { display: flex; flex-direction: column; }
.proof-facts dt { font-size: .72rem; color: var(--text-muted); text-transform: uppercase; letter-spacing: .04em; }
.proof-facts dd { margin: 0; font-weight: 600; font-variant-numeric: tabular-nums; }
.proof-answer { margin: .7rem 0 0; color: var(--text-soft); font-size: .86rem; max-width: 72ch; }
/* Not display:none: a live region that is hidden when the answer arrives is not announced. */
.proof-answer:empty { margin: 0; }
@media (max-width: 640px) { .proof-action { width: 100%; } }

/* Another tab wrote to the shared database. A notice, not a dialog: reading is
   still fine, and only the next save would be refused. */
.cross-tab-notice {
    display: flex;
    align-items: center;
    gap: .75rem;
    flex-wrap: wrap;
    margin-bottom: 1rem;
    padding: .6rem .9rem;
    border: 1px solid var(--amber);
    border-radius: var(--radius-sm);
    background: var(--amber-soft);
    color: var(--text);
}

.cross-tab-notice .btn-small {
    font-size: .85rem;
}

/* ----- Pager ----- */
.pager { display: flex; align-items: center; justify-content: space-between; gap: 1rem; flex-wrap: wrap; margin-top: 1rem; }
.pager-range { margin: 0; color: var(--text-muted); font-size: .85rem; }
.pager-controls { display: flex; align-items: center; gap: .3rem; flex-wrap: wrap; }
.pager-gap { padding: 0 .2rem; color: var(--text-muted); }
/* 24 px is the floor WCAG 2.2 sets for a pointer target, and .3rem of padding
   measured 23 px on a phone - close enough to look right and still wrong. */
.btn-small { padding: .35rem .7rem; font-size: .85rem; min-height: 24px; min-width: 24px; }
