/* Design tokens (:root) now live in static/tokens.css, shared with the
   company portal. Add new tokens THERE, not here. */

* {
    letter-spacing: 0;
}

/* Background gradient comes from .brand-shell-bg (tokens.css) on <body>. */
body {
    color: var(--ink);
    font-family: var(--font-sans);

    /* Bootstrap's link colour, retuned to the brand. Nothing in this shell
       overrode it, so every UNCLASSED <a> drew in #0d6efd — the company names
       on the client list and a couple on the distribution dashboard were the
       last Bootstrap blue left in the operator plane. Most pages never showed
       it only because their links all carry a class that sets its own colour.

       --brand rather than --brand-deep because tokens.css says so: it is
       annotated "primary — buttons, links, focus", and the tenant portal has
       always drawn links at that value. The -rgb pair is what Bootstrap 5.3
       actually reads (`rgba(var(--bs-link-color-rgb), var(--bs-link-opacity))`);
       setting only the non-rgb variable changes nothing. Keep the two in
       step if --brand ever moves. */
    --bs-link-color: var(--brand);
    --bs-link-color-rgb: 12, 110, 98;
    --bs-link-hover-color: var(--brand-deep);
    --bs-link-hover-color-rgb: 13, 77, 77;
}

/* The operator shell's chrome — the top bar that replaced the 280px deep-teal
   sidebar — is in components.css, which both shells load. `.app-shell`,
   `.sidebar` and their drawer lived here; they are gone, not moved, because the
   bar that replaced them was already written for the tenant portal and a second
   copy of it here is the drift the shared layer exists to prevent.

   `.brand` below stays: the anonymous screens (login, forgot password, the two
   error cards) still draw the lockup, and they never had a sidebar. */

.brand {
    align-items: center;
    display: flex;
    gap: 12px;
    margin-bottom: 26px;
}

.brand small,
.brand strong {
    display: block;
}

.brand small {
    color: #a7f3d0;
    font-size: 12px;
}

/* One-shot entrance for the brand mark on the anonymous screens (login,
   forgot password). Deliberately NOT used in the app shell: the sidebar mark is
   persistent chrome and must never animate. */
.logo-bounce {
    animation: logoIn 420ms var(--ease-out) both;
}

.main-content {
    padding: 30px;
    /* Grid items default to min-width:auto, which lets a wide .excel-grid
       (min-width:1120px) inflate this column past the viewport and scroll the
       whole page. min-width:0 lets it shrink so .excel-grid-wrap's overflow:auto
       scrolls the table inside its own panel instead. */
    min-width: 0;
}

/* --- Ultra-wide ceiling ---------------------------------------------------
   Content stops growing at 1680px and centres beyond it. Unbounded, a 2560px
   monitor stretched every panel to the glass, and two things broke at once:
   captions and detail lines ran to 200+ characters, well past the ~75 a reader
   can track back from; and the dashboard's line chart — an SVG with a fixed
   viewBox and height:auto, so its height follows its width — grew to roughly
   500px tall, turning six data points into a full screen of chart and pushing
   the company table below the fold on the largest displays it should serve
   best. A ceiling fixes both without touching a single panel's own rules. */
@media (min-width: 1681px) {
    .main-content { margin-inline: auto; max-width: 1680px; width: 100%; }
}

.topbar {
    align-items: center;
    background:
        linear-gradient(120deg, rgba(13, 77, 77, 0.97), rgba(12, 110, 98, 0.93)),
        #0d4d4d;
    border-radius: 18px;
    box-shadow: var(--shadow);
    color: #fff;
    display: grid;
    gap: 18px;
    grid-template-columns: minmax(0, 1fr) auto;
    margin-bottom: 20px;
    overflow: hidden;
    padding: 18px 22px;
    position: relative;
}

/* Collapse the visual column entirely when a page provides no illustration */
.topbar-visual:empty {
    display: none;
}

.finance-signal strong {
    font-size: 23px;
}

.topbar::after {
    background: radial-gradient(circle, rgba(23, 195, 178, 0.28), transparent 15rem);
    content: "";
    height: 260px;
    position: absolute;
    right: -110px;
    top: -120px;
    width: 260px;
}

.topbar-copy {
    position: relative;
    z-index: 1;
}

.section-kicker {
    align-items: center;
    color: #a7f3d0;
    display: inline-flex;
    font-size: 13px;
    font-weight: 700;
    gap: 8px;
    margin-bottom: 10px;
    text-transform: uppercase;
}

.topbar h1 {
    font-size: clamp(22px, 2.6vw, 30px);
    font-weight: 800;
    line-height: 1.1;
    margin: 0 0 5px;
}

/* The greeting line under the page title. Quiet against the banner — it is
   context, not the subject — but at #d6f0ea on the teal gradient it clears 7:1,
   so it is a line anyone can actually read rather than decoration. */
.topbar-sub {
    color: #d6f0ea;
    font-size: 14.5px;
    font-weight: 550;
    line-height: 1.45;
    margin: 0;
    max-width: 62ch;
}

.topbar-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
    margin-top: 14px;
}

.topbar-visual {
    align-items: center;
    display: flex;
    justify-content: center;
    position: relative;
    z-index: 1;
}

.topbar-visual img {
    animation: floatIn 4.6s ease-in-out infinite;
    border-radius: 20px;
    box-shadow: 0 18px 40px rgba(0, 0, 0, 0.2);
    max-height: 190px;
    max-width: 100%;
    object-fit: cover;
}

.dashboard-signal {
    align-self: stretch;
    background:
        linear-gradient(180deg, rgba(255, 255, 255, 0.18), rgba(255, 255, 255, 0.08));
    border: 1px solid rgba(255, 255, 255, 0.3);
    border-radius: 18px;
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2);
    display: flex;
    flex-direction: column;
    justify-content: center;
    min-height: 170px;
    padding: 22px;
    width: 100%;
}

.dashboard-signal .signal-label {
    color: #a7f3d0;
    font-size: 12px;
    font-weight: 800;
    margin-bottom: 8px;
    text-transform: uppercase;
}

.dashboard-signal strong {
    font-size: 28px;
    line-height: 1.1;
}

.dashboard-signal small {
    color: #d1fae5;
    font-size: 14px;
    margin-top: 8px;
}

.content-enter {
    animation: contentFade 200ms var(--ease-out) both;
}

.stats-grid {
    display: grid;
    gap: 16px;
    grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
}

.dashboard-controls {
    margin-bottom: 16px;
}

.stat-icon {
    align-items: center;
    background: rgba(15, 118, 110, 0.12);
    border-radius: 12px;
    color: var(--brand);
    display: inline-flex;
    font-size: 20px;
    height: 38px;
    justify-content: center;
    margin-bottom: 12px;
    position: relative;
    width: 38px;
    z-index: 1;
}


.panel-header,
.toolbar {
    align-items: center;
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
    justify-content: space-between;
    margin-bottom: 14px;
}

.panel-header h2 {
    align-items: center;
    display: flex;
    font-size: 18px;
    font-weight: 750;
    gap: 8px;
    margin: 0;
}

.panel-header h2 i {
    color: var(--brand);
}

.toolbar {
    margin-bottom: 16px;
}

.form-panel {
    max-width: 980px;
}

.table {
    --bs-table-bg: transparent;
}

.table th {
    color: #475569;
    font-size: 12px;
    text-transform: uppercase;
}

.table tbody tr {
    transition: background 160ms ease, transform 160ms ease;
}

.table tbody tr:hover {
    background: rgba(204, 251, 241, 0.36);
    transform: scale(1.002);
}

.sparkbar {
    background: #e2e8f0;
    border-radius: 999px;
    height: 11px;
    min-width: 120px;
    overflow: hidden;
}

.sparkbar span {
    animation: barGrow 620ms ease both;
    background: linear-gradient(90deg, var(--brand), var(--gold));
    border-radius: inherit;
    display: block;
    height: 100%;
    min-width: 4px;
    transform-origin: left center;
}

.sparkbar span[style*="0%"] {
    min-width: 0;
}

/* --- Cost Signal cell (Payroll Cost Per Client) -------------------------
   Two stacked signals answering different questions — share of the largest
   client (.sparkbar) and direction over recent months (sparkline + delta pill,
   from macros/charts.html). Stacked rather than split into a new column so the
   table keeps its existing six columns and width. */

.cost-signal {
    display: flex;
    flex-direction: column;
    gap: 6px;
    min-width: 120px;
}

.cost-signal-trend {
    align-items: center;
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
}

/* --- Executive overview (operator dashboard) -----------------------------
   `.analytics-grid`, `.chart-card` and `.chart-head` used to live here, on the
   assumption that card layout was per-shell. They were only ever USED by
   client/expenses.html — a tenant-shell template, and the tenant shell does not
   load this stylesheet. So the tenant's only analytics page rendered them
   completely unstyled: two full-width stacked cards with no padding, against
   18px-padded .stat-cards directly above. They are now in components.css, the
   shared layer both shells load. Nothing in the operator shell used them.

   Payroll activity timeline. One list, two sources (domain events + curated
   audit entries) — see app/events.py:platform_activity. */
.activity-feed {
    display: flex;
    flex-direction: column;
    gap: 14px;
    list-style: none;
    margin: 0;
    padding: 0;
}

.activity-item {
    display: flex;
    gap: 12px;
}

.activity-icon {
    align-items: center;
    border-radius: var(--radius-pill);
    display: inline-flex;
    flex: none;
    font-size: 13px;
    height: 30px;
    justify-content: center;
    width: 30px;
}

.activity-body {
    min-width: 0;   /* long summaries wrap instead of widening the card */
}

.activity-body strong {
    display: block;
    font-size: 14px;
    font-weight: 700;
}

.activity-detail {
    color: var(--ink-soft);
    font-size: 13px;
    line-height: 1.45;
    margin: 2px 0 3px;
    overflow-wrap: anywhere;
}

/* Trend + "Highest" badge grouped on the card header's right-hand side. */
.panel-header-signal {
    align-items: center;
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
}

.action-required {
    background:
        linear-gradient(135deg, rgba(254, 243, 199, 0.78), rgba(255, 255, 255, 0.95));
}

.action-row {
    align-items: center;
    border-top: 1px solid rgba(15, 118, 110, 0.13);
    color: var(--ink);
    display: flex;
    justify-content: space-between;
    padding: 12px 0;
    text-decoration: none;
}

.action-row:first-of-type {
    border-top: 0;
}

.action-row span {
    align-items: center;
    display: flex;
    gap: 9px;
}

.action-row i {
    color: var(--brand);
}

.action-row strong {
    background: var(--brand);
    border-radius: 999px;
    color: #fff;
    min-width: 34px;
    padding: 5px 10px;
    text-align: center;
}

.action-row:hover {
    color: var(--brand-dark);
}

.excel-grid-wrap {
    border: 1px solid #9fb6c2;
    border-radius: 10px;
    max-height: 560px;
    overflow: auto;
}

.excel-grid {
    border-collapse: separate;
    border-spacing: 0;
    margin-bottom: 0;
    min-width: 1120px;
}

.excel-grid th,
.excel-grid td {
    background: #fff;
    border-bottom: 1px solid #cbd5e1;
    border-right: 1px solid #cbd5e1;
    font-size: 13px;
    padding: 9px 10px;
    white-space: nowrap;
}

.excel-grid th {
    background: linear-gradient(180deg, #e8f3f1, #d9e8e6);
    color: #134e4a;
    font-weight: 800;
    position: sticky;
    text-transform: none;
    top: 0;
    z-index: 2;
}

.excel-grid td:first-child,
.excel-grid th:first-child {
    border-left: 0;
}

.excel-grid .money-cell {
    font-variant-numeric: tabular-nums;
    text-align: right;
}

.excel-grid tbody tr:nth-child(even) td {
    background: #f8fbfb;
}

.excel-grid tbody tr:hover td {
    background: #ecfeff;
}

.btn {
    border-radius: 10px;
    font-weight: 700;
    /* Press feedback. Asymmetric on purpose: the press snaps (60ms), the release
       settles (160ms). Bootstrap's own .btn transition covers colour only. */
    transition: transform 160ms var(--ease-out);
}

.btn:active {
    transform: scale(0.97);
    transition-duration: 60ms;
}

.btn i {
    margin-right: 6px;
}

.login-page {
    align-items: center;
    background:
        radial-gradient(circle at 20% 10%, rgba(23, 195, 178, 0.20), transparent 24rem),
        linear-gradient(135deg, #e7faf4, #f7f9fc);
    display: flex;
    min-height: 100vh;
    justify-content: center;
    padding: 24px;
}

.login-card {
    animation: riseFade 540ms ease both;
    background: rgba(255, 255, 255, 0.95);
    border: 1px solid var(--line);
    border-radius: 18px;
    box-shadow: 0 22px 70px rgba(15, 23, 42, 0.14);
    max-width: 470px;
    padding: 36px;
    width: 100%;
}

.login-intro {
    margin-bottom: 24px;
    text-align: center;
}

.login-intro h1 {
    font-size: 28px;
    font-weight: 850;
    line-height: 1.15;
    margin-bottom: 10px;
}

.login-intro p {
    color: var(--muted);
    line-height: 1.6;
    margin: 0;
}

.btn-primary {
    --bs-btn-bg: var(--brand-deep);
    --bs-btn-border-color: var(--brand-deep);
    --bs-btn-hover-bg: #0a3d3d;
    --bs-btn-hover-border-color: #0a3d3d;
    --bs-btn-active-bg: #0a3d3d;
    --bs-btn-active-border-color: #0a3d3d;
    /* The disabled pair, which this rule used to leave to Bootstrap — so a
       primary button rendered teal while enabled and #0d6efd BLUE the moment
       it was disabled. The payroll runs queue showed it on every load: its
       Bulk Distribute button starts disabled until rows are selected, so the
       page opened with a blue button on it and turned teal when you ticked a
       row. Bootstrap dims these with opacity, so the hue is all that is set. */
    --bs-btn-disabled-bg: var(--brand-deep);
    --bs-btn-disabled-border-color: var(--brand-deep);
}

/* The outline half of the same idea, which had no override at all: every
   `btn-outline-primary` in the operator shell — Open on a runs row, Approval
   Queue, the roster and payslip actions, ten templates in all — drew in
   Bootstrap's blue, a colour that appears nowhere else in this product.
   Themed here rather than respelled at each call site: the class means
   "primary-coloured outline button", and the primary colour is teal.

   --brand-deep for the resting border as well as the text: it measures 8.6:1
   on white against --brand's 5.67, and a control's boundary has to clear 3:1
   on its own (WCAG 1.4.11) before the label's contrast is even considered. */
.btn-outline-primary {
    --bs-btn-color: var(--brand-deep);
    --bs-btn-border-color: var(--brand-deep);
    --bs-btn-hover-bg: var(--brand-deep);
    --bs-btn-hover-border-color: var(--brand-deep);
    --bs-btn-hover-color: #fff;
    --bs-btn-active-bg: var(--brand-deep);
    --bs-btn-active-border-color: var(--brand-deep);
    --bs-btn-active-color: #fff;
    --bs-btn-disabled-color: var(--brand-deep);
    --bs-btn-disabled-border-color: var(--brand-deep);
}

/* Bright-teal accent action — the vivid end of the palette for high-emphasis,
   non-primary CTAs (e.g. "Distribute payslips"). Dark ink keeps it AA on the
   bright fill. */
.btn-accent {
    --bs-btn-bg: var(--brand-bright);
    --bs-btn-border-color: var(--brand-bright);
    --bs-btn-color: #06302b;
    --bs-btn-hover-bg: #3ad6c6;
    --bs-btn-hover-border-color: #3ad6c6;
    --bs-btn-hover-color: #06302b;
    --bs-btn-active-bg: #3ad6c6;
    --bs-btn-active-border-color: #3ad6c6;
    --bs-btn-active-color: #06302b;
    font-weight: 600;
}

.btn-success {
    --bs-btn-bg: var(--green);
    --bs-btn-border-color: var(--green);
}

.badge {
    border-radius: 999px;
    padding: 0.45em 0.7em;
}

.mini-note {
    background: var(--brand-soft);
    border: 1px solid #99f6e4;
    border-radius: 12px;
    color: #134e4a;
    padding: 12px 14px;
}

/* Breadcrumb trail above the topbar (macros/breadcrumbs.html) — quiet by
   design, since the sidebar carries primary navigation. */
.payrolla-breadcrumb {
    margin-bottom: 10px;
}

.payrolla-breadcrumb .breadcrumb {
    align-items: center;
    display: flex;
    flex-wrap: wrap;
    font-size: 13px;
    gap: 2px;
    margin: 0;
    padding: 0;
}

.payrolla-breadcrumb .breadcrumb-item + .breadcrumb-item::before {
    color: var(--muted);
    content: "/";
    padding: 0 8px;
}

.payrolla-breadcrumb .breadcrumb-item a {
    color: var(--brand-dark);
    text-decoration: none;
}

.payrolla-breadcrumb .breadcrumb-item a:hover {
    text-decoration: underline;
}

.payrolla-breadcrumb .breadcrumb-item.active {
    color: var(--muted);
}

/* Guidance shown where a list/table would be once data exists */
.empty-state {
    align-items: center;
    display: flex;
    flex-direction: column;
    gap: 4px;
    padding: 28px 16px;
    text-align: center;
}

.empty-state > i {
    color: var(--brand, #0f766e);
    font-size: 30px;
    margin-bottom: 4px;
    opacity: 0.7;
}

/* --- Accessibility: skip link, visible keyboard focus, active nav --- */
.skip-link {
    background: var(--brand);
    border-radius: 10px;
    color: #fff;
    font-weight: 700;
    left: 12px;
    padding: 10px 16px;
    position: absolute;
    top: -56px;
    transition: top 160ms ease;
    z-index: 1000;
}

.skip-link:focus {
    outline: 2px solid #fff;
    outline-offset: 2px;
    top: 12px;
}

:focus-visible {
    outline: 2px solid var(--brand);
    outline-offset: 2px;
    border-radius: 6px;
}

/* Touch-friendly target sizes (keep dense table btn-sm compact) */
.btn:not(.btn-sm) {
    min-height: 44px;
}

.btn:not(.btn-sm) {
    align-items: center;
    display: inline-flex;
    justify-content: center;
}

/* Collapsible client tabs (details/summary) */
.client-tabs summary {
    cursor: pointer;
    list-style: none;
}

.client-tabs summary::-webkit-details-marker {
    display: none;
}

.client-tabs-caret {
    color: var(--gold);
    margin-left: auto;
    transition: transform 180ms ease;
}

.client-tabs[open] .client-tabs-caret {
    transform: rotate(180deg);
}

@keyframes logoIn {
    from {
        opacity: 0;
        transform: scale(0.92);
    }
    to {
        opacity: 1;
        transform: none;
    }
}

@keyframes contentFade {
    from {
        opacity: 0;
    }
    to {
        opacity: 1;
    }
}

@keyframes riseFade {
    from {
        opacity: 0;
        transform: translateY(16px);
    }
    to {
        opacity: 1;
        transform: translateY(0);
    }
}

@keyframes floatIn {
    0%, 100% {
        transform: translateY(0);
    }
    50% {
        transform: translateY(-8px);
    }
}

@keyframes barGrow {
    from {
        transform: scaleX(0);
    }
    to {
        transform: scaleX(1);
    }
}

@media (prefers-reduced-motion: reduce) {
    *,
    *::before,
    *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        scroll-behavior: auto !important;
        transition-duration: 0.01ms !important;
    }
}

/* The drawer, the hamburger and the scrim are handled by components.css at the
   1100px breakpoint both shells share. What is left here is the page itself.

   The top padding dropped from 64px to 18px, and that is a consequence of the
   bar rather than a taste change: the old hamburger was `position: fixed` over
   the content and the page had to reserve a strip for it. The new one sits
   inside the bar, so the strip would now be empty space above every mobile
   page. */
@media (max-width: 1000px) {
    .main-content {
        padding: 18px 16px;
    }

    .topbar {
        grid-template-columns: 1fr;
    }

    .topbar-visual {
        display: none;
    }
}

@media (max-width: 540px) {
    .main-content {
        padding: 14px 12px;
    }

    .topbar {
        border-radius: 14px;
        padding: 18px;
    }

    .stats-grid {
        grid-template-columns: 1fr 1fr;
    }
}

/* --- Raw upload notice (payroll detail + Raw Hour Upload panel) --- */
.raw-notice {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    margin-top: 14px;
    padding: 12px 16px;
    border: 1px solid rgba(23, 195, 178, .4);
    border-left: 4px solid var(--accent);
    border-radius: 8px;
    background: rgba(23, 195, 178, .09);
    color: #0a544b;
    font-size: .9rem;
}
.raw-notice i { color: var(--gold); margin-top: 2px; }

/* --- Upload-type tabs (Standard vs Raw Hour) on the Create Payroll Run panel --- */
.upload-tabs {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin: 4px 0 18px;
}
.upload-tab {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    padding: 9px 18px;
    border: 1px solid var(--line);
    border-radius: 10px;
    background: var(--shell);
    color: var(--muted);
    font-size: 12px;
    font-weight: 700;
    letter-spacing: .04em;
    text-transform: uppercase;
    cursor: pointer;
    transition: background .15s ease, color .15s ease, border-color .15s ease, box-shadow .15s ease;
}
.upload-tab i { font-size: 14px; }
.upload-tab:hover { color: var(--ink); border-color: var(--brand); }
.upload-tab:focus-visible { outline: 3px solid var(--brand-soft); outline-offset: 2px; }
.upload-tab.is-active {
    background: var(--brand);
    border-color: var(--brand);
    color: #fff;
    box-shadow: 0 8px 18px rgba(15, 118, 110, .22);
}
.upload-panel[hidden] { display: none !important; }

/* ============================================================================
   Toasts — the single feedback channel (see app.js). Fixed, top-right, stacked.
   Each type carries a left accent + icon tint drawn from the app's token set.
   ========================================================================== */
.toast-region {
    position: fixed;
    top: 18px;
    right: 18px;
    z-index: 1080;
    display: flex;
    flex-direction: column;
    gap: 10px;
    max-width: min(360px, calc(100vw - 36px));
    pointer-events: none;
}
.toast-item {
    pointer-events: auto;
    display: flex;
    align-items: flex-start;
    gap: 10px;
    padding: 12px 12px 12px 14px;
    background: var(--surface);
    border: 1px solid var(--line);
    border-left: 4px solid var(--muted);
    border-radius: 12px;
    box-shadow: var(--shadow);
    color: var(--ink);
    font-size: 14px;
    line-height: 1.35;
    opacity: 0;
    transform: translateX(14px);
    transition: opacity .22s ease, transform .22s ease;
}
.toast-item.toast-show { opacity: 1; transform: translateX(0); }
.toast-item.toast-hide { opacity: 0; transform: translateX(14px); }
.toast-icon { font-size: 16px; line-height: 1.35; flex: 0 0 auto; }
.toast-body { flex: 1 1 auto; overflow-wrap: anywhere; }
.toast-close {
    flex: 0 0 auto;
    border: 0;
    background: transparent;
    color: var(--muted);
    font-size: 20px;
    line-height: 1;
    padding: 0 2px;
    cursor: pointer;
    border-radius: 6px;
}
.toast-close:hover { color: var(--ink); }
.toast-close:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
.toast-success { border-left-color: var(--green); }
.toast-success .toast-icon { color: var(--green); }
.toast-danger  { border-left-color: var(--danger); }
.toast-danger  .toast-icon { color: var(--danger); }
.toast-info    { border-left-color: var(--brand); }
.toast-info    .toast-icon { color: var(--brand); }
.toast-warning { border-left-color: #b45309; }
.toast-warning .toast-icon { color: #b45309; }
@media (prefers-reduced-motion: reduce) {
    .toast-item { transition: opacity .01ms linear; transform: none; }
    .toast-item.toast-show { transform: none; }
}

/* ============================================================================
   Confirm modal — styled replacement for native confirm() (which froze the
   renderer in testing). Driven by app-level data-confirm wiring / inline usage.
   ========================================================================== */
.cn-modal-backdrop {
    position: fixed;
    inset: 0;
    z-index: 1090;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 20px;
    background: rgba(15, 23, 42, .55);
    opacity: 0;
    transition: opacity .16s ease;
}
.cn-modal-backdrop.cn-open { opacity: 1; }
.cn-modal {
    width: min(440px, 100%);
    background: var(--surface);
    border-radius: 16px;
    box-shadow: 0 30px 80px rgba(0, 0, 0, .35);
    padding: 22px 22px 18px;
    transform: translateY(8px) scale(.98);
    transition: transform .16s ease;
}
.cn-modal-backdrop.cn-open .cn-modal { transform: translateY(0) scale(1); }
.cn-modal-title {
    display: flex;
    align-items: center;
    gap: 10px;
    font-size: 18px;
    font-weight: 700;
    margin: 0 0 8px;
    color: var(--ink);
}
.cn-modal-title .bi { color: var(--danger); font-size: 20px; }
.cn-modal-text { color: var(--muted); font-size: 14px; margin: 0 0 18px; }
/* Typed confirmation (top of the consequence scale) — the user reproduces the
   subject's name before the confirm button enables. */
.cn-modal-input { margin: -8px 0 18px; font-family: var(--font-mono, monospace); }
.cn-modal-actions { display: flex; justify-content: flex-end; gap: 8px; }
@media (prefers-reduced-motion: reduce) {
    .cn-modal-backdrop, .cn-modal { transition: none; }
    .cn-modal { transform: none; }
}

/* ============================================================================
   Inline form-field validation (server-side re-population) + upload loader.
   ========================================================================== */
.field-error { color: var(--danger); font-size: 12.5px; margin-top: 4px; }
.form-control.is-invalid, .form-select.is-invalid { border-color: var(--danger); }
.form-control.is-invalid:focus, .form-select.is-invalid:focus {
    box-shadow: 0 0 0 .2rem rgba(220, 38, 38, .18);
}

/* Staged upload loader: indeterminate bar + cycling copy. Shown via .is-loading
   on the overlay; hidden by default. Cosmetic only — not tied to real progress. */
.upload-overlay {
    position: absolute;
    inset: 0;
    z-index: 5;
    display: none;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 14px;
    padding: 24px;
    text-align: center;
    background: rgba(255, 255, 255, .86);
    backdrop-filter: blur(2px);
    border-radius: 16px;
}
.upload-overlay.is-loading { display: flex; }
.upload-overlay .spinner-border { color: var(--brand); }
.upload-stage-msg { font-weight: 600; color: var(--ink); min-height: 1.4em; }
.upload-progress {
    width: min(320px, 80%);
    height: 8px;
    border-radius: 999px;
    background: var(--brand-soft);
    overflow: hidden;
}
.upload-progress-bar {
    height: 100%;
    width: 40%;
    border-radius: 999px;
    background: linear-gradient(90deg, var(--brand), var(--brand-dark));
    animation: cnIndeterminate 1.4s linear infinite;
    will-change: transform;
}
.upload-note { color: var(--muted); font-size: 13px; }
@keyframes cnIndeterminate {
    0%   { transform: translateX(-100%); }
    50%  { transform: translateX(150%); }
    100% { transform: translateX(250%); }
}
@media (prefers-reduced-motion: reduce) {
    /* No sweep. Keep the bar static but de-emphasised, so it reads as an
       indeterminate busy state rather than a completed 100% bar. */
    .upload-progress-bar {
        animation: none;
        width: 100%;
        opacity: .55;
    }
}

/* ============================================================================
   Branded error pages (A4) — anonymous, self-contained card (no app shell).
   ========================================================================== */
.error-page {
    min-height: 100vh;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 24px;
    background:
        radial-gradient(circle at 18% 12%, rgba(25, 184, 159, .18), transparent 38%),
        radial-gradient(circle at 84% 82%, rgba(23, 195, 178, .14), transparent 42%),
        linear-gradient(160deg, #052420 0%, #073b34 55%, #04201c 100%);
    color: #edf4f1;
    text-align: center;
}
.error-card {
    width: min(460px, 100%);
    background: rgba(255, 255, 255, .05);
    border: 1px solid rgba(255, 255, 255, .09);
    border-radius: 18px;
    box-shadow: 0 30px 80px rgba(0, 0, 0, .45);
    backdrop-filter: blur(8px);
    padding: 32px 28px 26px;
}
.error-card .brand strong { color: #edf4f1; }
.error-card .brand small { color: #86aaa2; letter-spacing: .16em; text-transform: uppercase; font-size: 10.5px; }
.error-card .error-code {
    font-size: 60px;
    font-weight: 800;
    line-height: 1;
    letter-spacing: -.02em;
    color: var(--brand-bright);
    margin-bottom: 6px;
}
.error-card h1 { font-size: 22px; font-weight: 700; margin: 0 0 8px; color: #edf4f1; }
.error-card p { color: #a9c6bf; margin: 0 0 20px; }

/* --- Payroll lifecycle stepper (Phase 2) --------------------------------- */
.lifecycle-stepper {
    list-style: none;
    display: flex;
    flex-wrap: wrap;
    gap: 0;
    margin: 0;
    padding: 0;
    align-items: flex-start;
}
.lifecycle-stepper .lc-step {
    position: relative;
    flex: 1 1 0;
    min-width: 64px;
    text-align: center;
    padding: 0 2px;
    font-size: 11px;
    line-height: 1.2;
}
.lifecycle-stepper .lc-dot {
    display: block;
    width: 12px;
    height: 12px;
    border-radius: 50%;
    margin: 0 auto 4px;
    background: #cdd7d3;
    border: 2px solid #cdd7d3;
    position: relative;
    z-index: 1;
}
/* connector line between dots */
.lifecycle-stepper .lc-step::before {
    content: "";
    position: absolute;
    top: 5px;
    left: -50%;
    width: 100%;
    height: 2px;
    background: #cdd7d3;
    z-index: 0;
}
.lifecycle-stepper .lc-step:first-child::before { display: none; }
.lifecycle-stepper .lc-label { color: #6b7b76; }
/* states */
.lc-step--done .lc-dot { background: var(--brand, #2f8f6b); border-color: var(--brand, #2f8f6b); }
.lc-step--done::before { background: var(--brand, #2f8f6b); }
.lc-step--done .lc-label { color: #2c3a36; }
.lc-step--current .lc-dot { background: #fff; border-color: var(--brand, #2f8f6b); box-shadow: 0 0 0 3px rgba(47,143,107,.2); }
.lc-step--current .lc-label { color: #1f2a27; font-weight: 600; }
.lc-step--skipped .lc-dot { background: #fff; border-style: dashed; border-color: #cdd7d3; }
.lc-step--skipped .lc-label { color: #9aa8a3; font-style: italic; }
.lifecycle-stepper--compact .lc-step { min-width: 0; font-size: 0; }
.lifecycle-stepper--compact .lc-label { display: none; }
.lifecycle-stepper--compact .lc-dot { width: 9px; height: 9px; }
.lifecycle-stepper--compact .lc-step::before { top: 3.5px; }
@media (prefers-color-scheme: dark) {
    .lifecycle-stepper .lc-label { color: #a9c6bf; }
    .lc-step--done .lc-label { color: #d8e7e2; }
    .lc-step--current .lc-label { color: #edf4f1; }
}

/* --- Activity & approval timeline (Phase 2) ------------------------------ */
.activity-timeline {
    list-style: none;
    margin: 0;
    padding: 0 0 0 18px;
    border-left: 2px solid #d7e0dc;
}
.activity-timeline .activity-item {
    position: relative;
    padding: 0 0 14px 6px;
}
.activity-timeline .activity-item::before {
    content: "";
    position: absolute;
    left: -25px;
    top: 3px;
    width: 10px;
    height: 10px;
    border-radius: 50%;
    background: var(--brand, #2f8f6b);
    border: 2px solid #fff;
}
.activity-item--event::before { background: #b8863b; }
.activity-timeline .activity-head {
    display: flex;
    justify-content: space-between;
    gap: 10px;
    flex-wrap: wrap;
    align-items: baseline;
}
.activity-timeline .activity-meta { color: #6b7b76; font-size: 12px; white-space: nowrap; }
.activity-timeline .activity-detail { color: #4a5a55; font-size: 13px; margin-top: 2px; }
@media (prefers-color-scheme: dark) {
    .activity-timeline { border-left-color: #33524a; }
    .activity-timeline .activity-detail { color: #a9c6bf; }
    .activity-timeline .activity-item::before { border-color: #16241f; }
}
