/* ==========================================================================
   Trihedron ERP — shared components
   --------------------------------------------------------------------------
   Loaded after site.css, which owns the brand tokens and the application shell.
   This file holds the pieces every data screen reuses: toolbars, scrollable
   tables, pagination, form grids, empty states, skeleton loaders and toasts.
   ========================================================================== */

/* --------------------------------------------------------------------------
   Toolbar above a list
   -------------------------------------------------------------------------- */

.toolbar {
    display: flex;
    align-items: center;
    gap: 0.85rem;
    padding: 0.7rem 1.1rem;
    background: var(--t-bg-2);
    border-bottom: 1px solid var(--t-line);
    flex-wrap: wrap;
}

.toolbar-spacer {
    flex: 1 1 auto;
}

/* Search input with the magnifier tucked inside it */
.search-field {
    position: relative;
    flex: 0 1 22rem;
    min-width: 12rem;
}

    .search-field .search-icon {
        position: absolute;
        left: 0.6rem;
        top: 50%;
        transform: translateY(-50%);
        color: var(--t-text-faint);
        pointer-events: none;
        width: 16px;
        height: 16px;
    }

.search-input {
    width: 100%;
    padding-left: 2.1rem;
}

    /* Chrome draws this glyph nearly black. Inverting it makes it visible on a
       dark field — and INVISIBLE on a light one, so the correction is scoped to
       the theme that needs it rather than applied to both. */
    :root:not([data-theme="light"]) .search-input::-webkit-search-cancel-button {
        filter: invert(0.6);
    }

.result-count {
    font-family: var(--t-mono);
    font-size: 0.75rem;
    color: var(--t-text-faint);
    white-space: nowrap;
}

.toggle {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    font-size: 0.82rem;
    color: var(--t-text-dim);
    margin: 0;
    cursor: pointer;
    white-space: nowrap;
}

    .toggle input {
        margin: 0;
    }

/* --------------------------------------------------------------------------
   Tables that scroll rather than pushing the page sideways
   -------------------------------------------------------------------------- */

.table-wrap {
    width: 100%;
    overflow-x: auto;
}

    .table-wrap > .table {
        min-width: 46rem;
    }

.row-link {
    cursor: pointer;
}

    .row-link:focus-visible {
        outline: 2px solid var(--t-cyan);
        outline-offset: -2px;
    }

.cell-center {
    text-align: center;
}

.form-control-sm {
    padding: 0.22rem 0.45rem;
    font-size: 0.82rem;
    border-radius: 3px;
}

/* --------------------------------------------------------------------------
   Quick action cards
   -------------------------------------------------------------------------- */

.action-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
    gap: 0.7rem;
}

.action-card {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    padding: 0.8rem 0.9rem;
    background: var(--t-bg-2);
    border: 1px solid var(--t-line);
    border-radius: var(--t-radius);
    color: var(--t-text);
    transition: transform var(--t-fast), border-color var(--t-fast), background var(--t-fast);
}

a.action-card:hover {
    text-decoration: none;
    transform: translateY(-2px);
    border-color: var(--t-cyan);
    background: var(--t-bg-3);
}

    a.action-card:hover .action-arrow {
        color: var(--t-cyan);
        transform: translateX(3px);
    }

.action-card.is-disabled {
    opacity: 0.45;
    cursor: default;
}

.action-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 38px;
    height: 38px;
    flex: 0 0 38px;
    border-radius: var(--t-radius);
    border: 1px solid transparent;
}

.action-blue {
    color: var(--t-blue);
    background: var(--t-blue-soft);
    border-color: color-mix(in srgb, var(--t-blue) 35%, transparent);
}

.action-green {
    color: var(--t-green);
    background: var(--t-green-soft);
    border-color: color-mix(in srgb, var(--t-green) 35%, transparent);
}

.action-cyan {
    color: var(--t-cyan);
    background: var(--t-cyan-soft);
    border-color: color-mix(in srgb, var(--t-cyan) 35%, transparent);
}

.action-muted {
    color: var(--t-text-faint);
    background: var(--t-bg-3);
    border-color: var(--t-line);
}

.action-text {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-width: 0;
}

    .action-text strong {
        font-size: 0.88rem;
        font-weight: 600;
    }

    .action-text small {
        font-size: 0.73rem;
        color: var(--t-text-faint);
    }

.action-arrow {
    color: var(--t-text-faint);
    flex: 0 0 auto;
    transition: color var(--t-fast), transform var(--t-fast);
}

/* --------------------------------------------------------------------------
   Progress track
   -------------------------------------------------------------------------- */

.progress-track {
    height: 5px;
    border-radius: 3px;
    background: var(--t-bg-3);
    overflow: hidden;
    margin-bottom: 0.9rem;
}

.progress-fill {
    height: 100%;
    border-radius: 3px;
    background: linear-gradient(90deg, var(--t-blue), var(--t-cyan));
    transition: width var(--t-med);
}

/* --------------------------------------------------------------------------
   Empty state
   -------------------------------------------------------------------------- */

.empty-state {
    padding: 2.4rem 1.1rem;
    text-align: center;
    color: var(--t-text-faint);
    font-size: 0.86rem;
}

    .empty-state p {
        margin: 0.6rem 0 0;
    }

.empty-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 60px;
    height: 60px;
    border-radius: 50%;
    background: var(--t-bg-2);
    border: 1px solid var(--t-line);
    color: var(--t-text-faint);
}

/* --------------------------------------------------------------------------
   Skeleton loading
   --------------------------------------------------------------------------
   Shown while a list is fetching. A shimmering outline of the rows that are
   coming reads as "loading"; an empty table that suddenly fills reads as broken.
   -------------------------------------------------------------------------- */

.skeleton {
    display: block;
    height: 0.72rem;
    border-radius: 3px;
    background: linear-gradient(90deg, var(--t-bg-2) 25%, var(--t-bg-3) 50%, var(--t-bg-2) 75%);
    background-size: 200% 100%;
    animation: shimmer 1.3s infinite linear;
}

.skeleton-row td {
    padding-top: 0.55rem;
    padding-bottom: 0.55rem;
}

@keyframes shimmer {
    from {
        background-position: 200% 0;
    }

    to {
        background-position: -200% 0;
    }
}

/* --------------------------------------------------------------------------
   Pagination
   -------------------------------------------------------------------------- */

.pager {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.8rem;
    padding: 0.7rem 1.1rem;
    border-top: 1px solid var(--t-line);
}

    .pager:empty {
        display: none;
    }

.pager-info {
    font-size: 0.78rem;
    color: var(--t-text-dim);
}

/* --------------------------------------------------------------------------
   Toasts
   -------------------------------------------------------------------------- */

.toast-host {
    position: fixed;
    right: 1.25rem;
    bottom: 1.25rem;
    z-index: 1080;
    display: flex;
    flex-direction: column;
    gap: 0.55rem;
    pointer-events: none;
}

.toast {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    min-width: 15rem;
    max-width: 26rem;
    padding: 0.65rem 0.8rem;
    border-radius: var(--t-radius);
    border: 1px solid var(--t-line-strong);
    background: var(--t-bg-2);
    color: var(--t-text);
    font-size: 0.85rem;
    box-shadow: var(--t-shadow-modal);
    pointer-events: auto;
    animation: toast-in 200ms cubic-bezier(0.2, 0, 0.2, 1) both;
}

    .toast.is-leaving {
        animation: toast-out 200ms cubic-bezier(0.4, 0, 1, 1) both;
    }

.toast-success {
    border-color: var(--t-green);
    background: linear-gradient(var(--t-green-soft), var(--t-green-soft)), var(--t-bg-2);
}

    .toast-success .icon {
        color: var(--t-green);
    }

.toast-error {
    border-color: var(--t-red);
    background: linear-gradient(var(--t-red-soft), var(--t-red-soft)), var(--t-bg-2);
}

    .toast-error .icon {
        color: var(--t-red);
    }

.toast-info {
    border-color: var(--t-blue);
    background: linear-gradient(var(--t-blue-soft), var(--t-blue-soft)), var(--t-bg-2);
}

    .toast-info .icon {
        color: var(--t-cyan);
    }

.toast-text {
    flex: 1 1 auto;
}

.toast-close {
    flex: 0 0 auto;
    background: none;
    border: none;
    color: var(--t-text-faint);
    font-size: 1.15rem;
    line-height: 1;
    padding: 0 0.15rem;
    cursor: pointer;
    transition: color var(--t-fast);
}

    .toast-close:hover {
        color: var(--t-text);
    }

@keyframes toast-in {
    from {
        opacity: 0;
        transform: translateX(18px) scale(0.97);
    }

    to {
        opacity: 1;
        transform: none;
    }
}

@keyframes toast-out {
    from {
        opacity: 1;
        transform: none;
    }

    to {
        opacity: 0;
        transform: translateX(18px) scale(0.97);
    }
}

/* --------------------------------------------------------------------------
   Form grids
   -------------------------------------------------------------------------- */

.form-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 0 0.9rem;
}

    .form-grid .field {
        margin-bottom: 0.9rem;
    }

    .form-grid .span-2 {
        grid-column: span 2;
    }

    .form-grid .span-3 {
        grid-column: 1 / -1;
    }

.form-actions {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    padding: 0.4rem 0 2rem;
}

@media (max-width: 1000px) {
    .form-grid {
        grid-template-columns: repeat(2, 1fr);
    }

        .form-grid .span-3 {
            grid-column: 1 / -1;
        }
}

@media (max-width: 700px) {
    .form-grid {
        grid-template-columns: 1fr;
    }

        .form-grid .span-2,
        .form-grid .span-3 {
            grid-column: auto;
        }
}
/* Filter dropdowns in a list toolbar */
.filter-select {
    width: auto;
    min-width: 9.5rem;
    flex: 0 0 auto;
}

/* For a select whose options carry a code, a name, a client and a count. At
   9.5rem the useful half of "ZEISS — Zeiss · Zeiss  (5 people)" is cut off. */
.filter-select.filter-wide {
    min-width: 15rem;
    max-width: 22rem;
}

/* Secondary line inside a table cell, e.g. the service line under a project name */
.cell-sub {
    font-size: 0.72rem;
    color: var(--t-text-faint);
    margin-top: 0.1rem;
}
/* --------------------------------------------------------------------------
   Timesheet grid
   --------------------------------------------------------------------------
   Deliberately laid out like the paper form: the same ten columns in the same
   order, so somebody moving off the spreadsheet is reading a familiar object.
   -------------------------------------------------------------------------- */

.head-actions { display: flex; align-items: center; gap: 0.5rem; }

.ts-grid { min-width: 62rem; }

.ts-grid td { padding: 0.25rem 0.4rem; vertical-align: middle; }

.ts-grid input[type="time"],
.ts-grid input[type="number"] {
    width: 100%;
    text-align: center;
    font-family: var(--t-mono);
    font-variant-numeric: tabular-nums;
}

/* Chrome renders the clock icon in a colour that fights a dark field. Scoped to
   the dark theme: inverted on a light field it disappears entirely. */
:root:not([data-theme="light"]) .ts-grid input[type="time"]::-webkit-calendar-picker-indicator {
    filter: invert(0.65);
}

.ts-day { white-space: nowrap; }

/* A day with hours on it is picked out, so a half-filled week is obvious. */
.ts-grid tr.has-hours .ts-worked { color: var(--t-text); font-weight: 600; }
.ts-grid tr:not(.has-hours) .ts-worked { color: var(--t-text-faint); }

.ts-midnight {
    display: inline-block;
    margin-top: 0.2rem;
    font-size: 0.63rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--t-cyan);
    border: 1px solid var(--t-cyan);
    background: var(--t-cyan-soft);
    border-radius: 2px;
    padding: 0 0.3rem;
}

.ts-total-row td {
    border-top: 2px solid var(--t-line-strong);
    background: var(--t-bg-2);
    font-size: 1rem;
    font-weight: 700;
    padding-top: 0.55rem;
    padding-bottom: 0.55rem;
}

.ts-total-label {
    text-align: right;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    font-size: 0.78rem;
    color: var(--t-text-dim);
}

/* --------------------------------------------------------------------------
   Signatures
   -------------------------------------------------------------------------- */

/* The bundled signature face. Dancing Script, SIL Open Font Licence — the same
   file the PDF writer embeds, so the screen and the printed sheet agree.

   Do NOT swap this for Segoe Script or Brush Script MT. Those ship with Windows
   and are licensed for use on Windows; serving them from this application to a
   browser is redistribution. The fallback chain below is a plain cursive, which
   is a licence-safe thing to ask the visitor's own machine for. */
@font-face {
    font-family: "Dancing Script";
    src: url("/fonts/DancingScript-Regular.ttf") format("truetype");
    font-weight: 400;
    font-style: normal;
    font-display: swap;
}

.sig-list { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 0.7rem; }

.sig-card {
    border: 1px solid var(--t-line);
    border-left: 3px solid var(--t-green);
    border-radius: var(--t-radius);
    background: var(--t-bg-2);
    padding: 0.7rem 0.85rem;
}

/* A signature given to a version that has since been recalled. Kept visible
   rather than deleted — it is a record that an approval was given and then
   superseded, not that it never happened. */
.sig-card.is-stale { border-left-color: var(--t-text-faint); opacity: 0.6; }

.sig-role {
    font-size: 0.66rem;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--t-text-faint);
}

/* The script name. Decoration, not evidence — the facts under it are what
   carry weight, which is why they are set in the normal UI face. */
.sig-name {
    font-family: "Dancing Script", cursive;
    font-size: 1.8rem;
    line-height: 1.2;
    color: var(--t-cyan);
    margin: 0.25rem 0 0.15rem;
    /* A long name must never spill out of the card or push the layout wide. */
    overflow-wrap: anywhere;
}

.sig-facts {
    font-size: 0.72rem;
    line-height: 1.55;
    color: var(--t-text-dim);
}

.sig-label { color: var(--t-text-faint); }

.sig-rule {
    border-top: 1px solid var(--t-line-strong);
    margin: 0.5rem 0 0.4rem;
}

.sig-caps {
    font-size: 0.75rem;
    font-weight: 600;
    letter-spacing: 0.04em;
    color: var(--t-text);
    overflow-wrap: anywhere;
}

.sig-title { font-size: 0.7rem; color: var(--t-text-faint); }

.sig-meta { font-size: 0.68rem; color: var(--t-text-faint); margin-top: 0.35rem; }

/* The sign dialog's preview. Same markup as a real signature card, so what the
   signer approves looks exactly like what gets recorded. */
.sig-preview {
    border: 1px dashed var(--t-line-strong);
    border-radius: var(--t-radius);
    background: var(--t-bg-2);
    padding: 0.75rem 0.9rem;
    margin-bottom: 0.9rem;
}

.sig-preview .sig-name { margin-top: 0; }

/* --------------------------------------------------------------------------
   Modal
   --------------------------------------------------------------------------
   Hand-rolled rather than Bootstrap's, because Bootstrap's needs its JS to
   open and close and this only has to show and hide an element.
   -------------------------------------------------------------------------- */

.modal-backdrop-tri {
    position: fixed;
    inset: 0;
    z-index: 1060;
    background: var(--t-scrim);
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 1.5rem;
    animation: fade-in 140ms ease both;
}

@keyframes fade-in { from { opacity: 0; } to { opacity: 1; } }

.modal-card {
    width: 100%;
    max-width: 560px;
    max-height: 90vh;
    overflow-y: auto;
    background: var(--t-bg-1);
    border: 1px solid var(--t-line);
    border-top: 2px solid var(--t-blue);
    border-radius: var(--t-radius-lg);
    box-shadow: var(--t-shadow-modal);
    animation: modal-in 180ms cubic-bezier(0.2, 0, 0.2, 1) both;
}

@keyframes modal-in {
    from { opacity: 0; transform: translateY(12px) scale(0.98); }
    to   { opacity: 1; transform: none; }
}

.modal-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    padding: 0.8rem 1.1rem;
    border-bottom: 1px solid var(--t-line);
    background: var(--t-bg-2);
}

.modal-head h2 {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin: 0;
    font-size: 0.8rem;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--t-text-dim);
}

.modal-body { padding: 1.1rem; }

.modal-foot {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 0.6rem;
    padding: 0.85rem 1.1rem;
    border-top: 1px solid var(--t-line);
    background: var(--t-bg-2);
}

/* --------------------------------------------------------------------------
   Notification bell
   --------------------------------------------------------------------------
   Lives in the top bar on module pages and in the header on the home screen, so
   everything here is positioned relative to .notif-wrap rather than to either
   shell. One set of rules, two hosts.
   -------------------------------------------------------------------------- */

.notif-wrap { position: relative; display: flex; align-items: center; }

.notif-bell {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 36px;
    height: 36px;
    padding: 0;
    border: 1px solid transparent;
    border-radius: var(--t-radius);
    background: transparent;
    color: var(--t-text-dim);
    cursor: pointer;
    transition: background 0.12s ease, color 0.12s ease, border-color 0.12s ease;
}

.notif-bell:hover,
.notif-bell[aria-expanded="true"] {
    background: var(--t-bg-3);
    border-color: var(--t-line);
    color: var(--t-text);
}

.notif-bell:focus-visible {
    outline: 2px solid var(--t-cyan);
    outline-offset: 2px;
}

/* Unread is signalled by colour as well as by the count, so it reads at a
   glance and does not depend on being able to see a two-digit number. */
.notif-bell.has-unread { color: var(--t-cyan); }

.notif-badge {
    position: absolute;
    top: 1px;
    right: 1px;
    min-width: 17px;
    height: 17px;
    padding: 0 4px;
    border-radius: 9px;
    background: var(--t-red);
    color: var(--t-on-fill);
    font-size: 0.65rem;
    font-weight: 700;
    line-height: 17px;
    text-align: center;
}

.notif-panel {
    position: absolute;
    top: calc(100% + 8px);
    right: 0;
    z-index: 60;
    width: min(380px, calc(100vw - 32px));
    max-height: 70vh;
    display: flex;
    flex-direction: column;
    background: var(--t-bg-1);
    border: 1px solid var(--t-line-strong);
    border-radius: var(--t-radius-lg);
    box-shadow: var(--t-shadow-modal);
    overflow: hidden;
}

.notif-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 0.6rem 0.85rem;
    border-bottom: 1px solid var(--t-line);
    font-size: 0.72rem;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--t-text-faint);
}

.notif-all {
    text-transform: none;
    letter-spacing: 0;
    font-size: 0.78rem;
    color: var(--t-cyan);
    text-decoration: none;
}

.notif-all:hover { text-decoration: underline; }

.notif-list { overflow-y: auto; }

.notif-item {
    display: block;
    padding: 0.7rem 0.85rem;
    border-bottom: 1px solid var(--t-line);
    color: inherit;
    text-decoration: none;

    /* The unread marker is a border that is transparent when read, rather than a
       border that appears. A border appearing shifts the text sideways. */
    border-left: 3px solid transparent;
}

.notif-item:last-child { border-bottom: none; }
.notif-item:hover { background: var(--t-bg-2); }
.notif-item.is-unread { border-left-color: var(--t-cyan); background: var(--t-blue-soft); }

.notif-title {
    font-size: 0.84rem;
    font-weight: 600;
    color: var(--t-text);
    line-height: 1.35;
}

.notif-body {
    margin-top: 0.2rem;
    font-size: 0.78rem;
    line-height: 1.45;
    color: var(--t-text-dim);
}

.notif-age {
    margin-top: 0.3rem;
    font-size: 0.7rem;
    color: var(--t-text-faint);
}

.notif-empty {
    margin: 0;
    padding: 1.4rem 0.85rem;
    text-align: center;
    font-size: 0.82rem;
    color: var(--t-text-faint);
}

/* --------------------------------------------------------------------------
   Notifications page
   -------------------------------------------------------------------------- */

.notif-page-list { display: flex; flex-direction: column; }

.notif-row {
    display: flex;
    align-items: flex-start;
    gap: 1rem;
    padding: 0.85rem 0.9rem;
    border: 1px solid var(--t-line);
    border-left: 3px solid transparent;
    border-radius: var(--t-radius);
    background: var(--t-bg-2);
    margin-bottom: 0.5rem;
}

.notif-row.is-unread { border-left-color: var(--t-cyan); }

.notif-row-main { flex: 1 1 auto; min-width: 0; }

.notif-row-actions {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    gap: 0.4rem;
}

.check-inline {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    font-size: 0.8rem;
    color: var(--t-text-dim);
    cursor: pointer;
}

@media (max-width: 640px) {
    .notif-row { flex-direction: column; gap: 0.6rem; }
    .notif-row-actions { align-self: flex-end; }
}

/* --------------------------------------------------------------------------
   People and roles
   -------------------------------------------------------------------------- */

.pill {
    display: inline-block;
    padding: 0.1rem 0.45rem;
    border-radius: 10px;
    background: var(--t-bg-3);
    border: 1px solid var(--t-line);
    font-size: 0.68rem;
    color: var(--t-text-dim);
    white-space: nowrap;
}

.pill-green { background: var(--t-green-soft); border-color: var(--t-green); color: var(--t-green); }
.pill-amber { background: var(--t-amber-soft); border-color: var(--t-amber); color: var(--t-amber); }
.pill-muted { opacity: 0.7; }

.cell-pills { line-height: 1.9; }

.subhead {
    margin: 1rem 0 0.3rem;
    font-size: 0.72rem;
    letter-spacing: 0.11em;
    text-transform: uppercase;
    color: var(--t-text-faint);
}

.role-list {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
    gap: 0.5rem;
    margin: 0.5rem 0 0.9rem;
}

.role-item {
    display: flex;
    align-items: flex-start;
    gap: 0.6rem;
    padding: 0.6rem 0.7rem;
    border: 1px solid var(--t-line);
    border-radius: var(--t-radius);
    background: var(--t-bg-2);
    cursor: pointer;
}

.role-item:hover { border-color: var(--t-line-strong); }

.role-item input { margin-top: 0.2rem; flex: 0 0 auto; }

.role-text { display: flex; flex-direction: column; gap: 0.15rem; min-width: 0; }
.role-name { font-size: 0.83rem; font-weight: 600; color: var(--t-text); }
.role-desc { font-size: 0.72rem; line-height: 1.4; color: var(--t-text-faint); }

/* The account banner. Colour-coded because "invited but never accepted" is the
   state people misread as working, and it needs to look different at a glance. */
.account-state {
    padding: 0.65rem 0.8rem;
    border-radius: var(--t-radius);
    border-left: 3px solid var(--t-line-strong);
    background: var(--t-bg-2);
    font-size: 0.8rem;
    line-height: 1.5;
    color: var(--t-text-dim);
    margin-bottom: 0.9rem;
}

.account-state.is-active { border-left-color: var(--t-green); }
.account-state.is-pending { border-left-color: var(--t-amber); }
.account-state.is-disabled { border-left-color: var(--t-red); }

tr.is-archived { opacity: 0.55; }

/* The People entry point in the top bar. Sized to match the notification bell so
   the two sit as a pair rather than as two unrelated controls. */
.topbar-link {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    height: 36px;
    padding: 0 0.7rem;
    border: 1px solid transparent;
    border-radius: var(--t-radius);
    color: var(--t-text-dim);
    text-decoration: none;
    font-size: 0.82rem;
    transition: background 0.12s ease, color 0.12s ease, border-color 0.12s ease;
}

.topbar-link:hover {
    background: var(--t-bg-3);
    border-color: var(--t-line);
    color: var(--t-text);
}

.topbar-link:focus-visible { outline: 2px solid var(--t-cyan); outline-offset: 2px; }

@media (max-width: 720px) {
    .topbar-link-label { display: none; }
}

/* --------------------------------------------------------------------------
   Documents and signing
   -------------------------------------------------------------------------- */

.seg { display: inline-flex; border: 1px solid var(--t-line); border-radius: var(--t-radius); overflow: hidden; }

.seg-btn {
    padding: 0.35rem 0.75rem;
    border: 0;
    background: transparent;
    color: var(--t-text-dim);
    font-size: 0.8rem;
    cursor: pointer;
    border-right: 1px solid var(--t-line);
}

.seg-btn:last-child { border-right: 0; }
.seg-btn:hover { background: var(--t-bg-3); color: var(--t-text); }
.seg-btn.is-on { background: var(--t-blue-fill); color: var(--t-on-fill); }

.row-sub { font-size: 0.72rem; color: var(--t-text-faint); margin-top: 0.15rem; }
.waiting-me { color: var(--t-cyan); }
tr.is-waiting-me { background: var(--t-blue-soft); }
.pill-red { background: var(--t-red-soft); border-color: var(--t-red); color: var(--t-red); }

/* Upload dropzone */
.dropzone {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.35rem;
    padding: 1.5rem 1rem;
    border: 2px dashed var(--t-line-strong);
    border-radius: var(--t-radius-lg);
    background: var(--t-bg-2);
    color: var(--t-text-faint);
    text-align: center;
    cursor: pointer;
    transition: border-color 0.12s ease, background 0.12s ease;
}

.dropzone:hover { border-color: var(--t-cyan); }
.dropzone.is-over { border-color: var(--t-cyan); background: var(--t-blue-soft); }
.dropzone.has-file { border-style: solid; border-color: var(--t-green); }
.dropzone-text { margin: 0; font-size: 0.85rem; color: var(--t-text); }

.link-btn {
    border: 0;
    background: none;
    padding: 0;
    color: var(--t-cyan);
    text-decoration: underline;
    cursor: pointer;
    font: inherit;
}

/* Document detail: viewer beside the chain */
.doc-layout {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 340px;
    gap: 1rem;
    align-items: start;
}

@media (max-width: 1100px) {
    .doc-layout { grid-template-columns: minmax(0, 1fr); }
}

.doc-viewer-body { padding: 0; }

.doc-side { display: flex; flex-direction: column; gap: 1rem; }
.head-actions { display: flex; gap: 0.5rem; }

/* The signing chain */
.chain { list-style: none; margin: 0; padding: 0; counter-reset: step; }

.step {
    position: relative;
    padding: 0.6rem 0.7rem 0.6rem 2.1rem;
    border: 1px solid var(--t-line);
    border-left: 3px solid var(--t-line-strong);
    border-radius: var(--t-radius);
    background: var(--t-bg-2);
    margin-bottom: 0.45rem;
    counter-increment: step;
}

/* The number is the step order, and it comes from the list rather than the data
   so it can never disagree with the order the steps are drawn in. */
.step::before {
    content: counter(step);
    position: absolute;
    left: 0.6rem;
    top: 0.6rem;
    width: 1.1rem;
    height: 1.1rem;
    border-radius: 50%;
    background: var(--t-bg-3);
    color: var(--t-text-faint);
    font-size: 0.65rem;
    line-height: 1.1rem;
    text-align: center;
}

.step-pending { border-left-color: var(--t-amber); }
.step-pending::before { background: var(--t-amber); color: var(--t-on-status); }
.step-signed { border-left-color: var(--t-green); }
.step-signed::before { background: var(--t-green); color: var(--t-on-status); }
.step-declined { border-left-color: var(--t-red); }
.step-declined::before { background: var(--t-red); color: var(--t-on-fill); }
.step.is-me { background: var(--t-blue-soft); }

.step-head { display: flex; justify-content: space-between; gap: 0.5rem; align-items: baseline; }
.step-name { font-size: 0.84rem; font-weight: 600; color: var(--t-text); }
.step-status { font-size: 0.68rem; color: var(--t-text-faint); white-space: nowrap; }
.step-role { font-size: 0.72rem; color: var(--t-text-faint); }
.step-signed-block { margin-top: 0.35rem; }
.step-signed-block .sig-name { font-size: 1.25rem; margin: 0; }
.step-meta { font-size: 0.68rem; color: var(--t-text-faint); }
.step-declined-reason { margin-top: 0.3rem; font-size: 0.75rem; color: var(--t-red); }

/* Integrity facts */
.facts { margin: 0; font-size: 0.78rem; }
.facts dt { color: var(--t-text-faint); font-size: 0.68rem; text-transform: uppercase; letter-spacing: 0.08em; }
.facts dd { margin: 0.1rem 0 0.6rem; color: var(--t-text); }
.facts dd.hash { font-size: 0.68rem; line-height: 1.4; overflow-wrap: anywhere; }

/* History */
.trail { list-style: none; margin: 0; padding: 0; }

.trail-item {
    display: grid;
    grid-template-columns: 1fr auto;
    gap: 0 0.5rem;
    padding: 0.4rem 0;
    border-bottom: 1px solid var(--t-line);
    font-size: 0.76rem;
}

.trail-item:last-child { border-bottom: 0; }
.trail-what { color: var(--t-text); }
.trail-who { grid-column: 1; font-size: 0.7rem; color: var(--t-text-faint); }
.trail-when { grid-row: 1; grid-column: 2; font-size: 0.68rem; color: var(--t-text-faint); }
.trail-detail { grid-column: 1 / -1; font-size: 0.7rem; color: var(--t-text-faint); }

/* --------------------------------------------------------------------------
   The signature layout editor
   -------------------------------------------------------------------------- */

.prep-layout {
    display: grid;
    grid-template-columns: 330px minmax(0, 1fr);
    gap: 1rem;
    align-items: start;
}

@media (max-width: 1100px) {
    .prep-layout { grid-template-columns: minmax(0, 1fr); }
}

.prep-side { display: flex; flex-direction: column; gap: 1rem; position: sticky; top: 1rem; }

.prep-pages {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 1.25rem;
    max-height: 76vh;
    overflow: auto;
    background: var(--t-bg-0);
    padding: 1.25rem;
}

.prep-loading { padding: 3rem 1rem; color: var(--t-text-faint); font-size: 0.85rem; }

/* One rendered page. The overlay sits exactly on top of the canvas, so a drop at
   (x, y) in the overlay is at (x, y) on the rendered page — which is what makes
   the coordinate conversion a straight viewport transform and not a chain of
   offset corrections. */
.prep-page {
    position: relative;
    flex: 0 0 auto;
    box-shadow: var(--t-shadow-modal);
    background: var(--t-paper);
}

.prep-page canvas { display: block; }

.prep-overlay { position: absolute; inset: 0; }
.prep-overlay.is-over { outline: 2px dashed var(--t-cyan); outline-offset: -2px; }

.prep-page-num {
    position: absolute;
    bottom: -1.15rem;
    left: 0;
    font-size: 0.68rem;
    color: var(--t-text-faint);
}

.zoom-label { font-size: 0.72rem; color: var(--t-text-faint); min-width: 3rem; text-align: center; }

/* Six signer colours, so a placed box is traceable to a name at a glance. */
/* PER-PARTY COLOURS, FIXED IN BOTH THEMES, and deliberately not tokenised.
   These identify a signer — party one is this cyan on the printed sheet, in the
   preview, and in the audit trail — and they are only ever drawn on the paper
   surface, which is white in both themes. A colour that changed with the theme
   would make "the amber signature" mean two different things. */
.sig-c0 { --sig: #03ace4; }
.sig-c1 { --sig: #76c043; }
.sig-c2 { --sig: #f0a028; }
.sig-c3 { --sig: #c678dd; }
.sig-c4 { --sig: #f04d28; }
.sig-c5 { --sig: #2bb6a0; }

/* The signer list */
.signer-list { list-style: none; margin: 0 0 0.75rem; padding: 0; counter-reset: signer; }

.signer {
    position: relative;
    display: flex;
    align-items: flex-start;
    gap: 0.45rem;
    padding: 0.55rem 0.5rem 0.55rem 1.9rem;
    margin-bottom: 0.45rem;
    border: 1px solid var(--t-line);
    border-left: 3px solid var(--sig, var(--t-line-strong));
    border-radius: var(--t-radius);
    background: var(--t-bg-2);
    counter-increment: signer;
    cursor: grab;
}

.signer:active { cursor: grabbing; }
.signer.is-dragging { opacity: 0.45; }

.signer::before {
    content: counter(signer);
    position: absolute;
    left: 0.5rem;
    top: 0.6rem;
    width: 1.05rem;
    height: 1.05rem;
    border-radius: 50%;
    background: var(--sig, var(--t-bg-3));
    color: var(--t-on-paper);
    font-size: 0.62rem;
    font-weight: 700;
    line-height: 1.05rem;
    text-align: center;
}

.signer-grip { color: var(--t-text-faint); font-size: 0.8rem; line-height: 1.2; user-select: none; }
.signer-body { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 0.2rem; }
.signer-name { font-size: 0.82rem; font-weight: 600; color: var(--t-text); }
.signer-title { font-size: 0.68rem; color: var(--t-text-faint); }
.signer-role { font-size: 0.72rem; padding: 0.15rem 0.35rem; }
.signer-count { font-size: 0.66rem; color: var(--sig); }
.signer-count.is-none { color: var(--t-amber); }

.signer-actions { display: flex; flex-direction: column; gap: 0.15rem; }

.icon-btn {
    width: 1.25rem;
    height: 1.25rem;
    padding: 0;
    border: 1px solid var(--t-line);
    border-radius: 3px;
    background: var(--t-bg-3);
    color: var(--t-text-dim);
    font-size: 0.7rem;
    line-height: 1;
    cursor: pointer;
}

.icon-btn:hover:not(:disabled) { border-color: var(--t-cyan); color: var(--t-text); }
.icon-btn:disabled { opacity: 0.3; cursor: default; }
.icon-btn.danger:hover { border-color: var(--t-red); color: var(--t-red); }

/* A placed box. Deliberately translucent — the sender has to be able to read the
   clause underneath it to know they have put the signature in the right place. */
.prep-field {
    position: absolute;
    border: 2px solid var(--sig, var(--t-cyan));
    border-radius: 3px;
    background: color-mix(in srgb, var(--sig, #03ace4) 18%, transparent);
    cursor: move;
    display: flex;
    flex-direction: column;
    justify-content: center;
    overflow: hidden;
    padding: 2px 4px;
}

.field-kind {
    font-size: 0.6rem;
    text-transform: uppercase;
    letter-spacing: 0.07em;
    color: var(--sig);
    line-height: 1.1;
}

.field-who {
    font-size: 0.72rem;
    font-weight: 600;
    color: var(--t-on-paper);
    background: var(--sig);
    align-self: flex-start;
    padding: 0 3px;
    border-radius: 2px;
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.field-remove {
    position: absolute;
    top: 1px;
    right: 1px;
    width: 14px;
    height: 14px;
    padding: 0;
    border: 0;
    border-radius: 2px;
    background: var(--t-scrim);
    color: var(--t-on-fill);
    font-size: 0.68rem;
    line-height: 14px;
    cursor: pointer;
}

.field-resize {
    position: absolute;
    right: -3px;
    bottom: -3px;
    width: 11px;
    height: 11px;
    border: 2px solid var(--sig);
    border-radius: 2px;
    background: var(--t-bg-0);
    cursor: nwse-resize;
}

.save-state { font-size: 0.75rem; color: var(--t-text-faint); margin-right: 0.6rem; }
.save-state.is-dirty { color: var(--t-amber); }

/* --------------------------------------------------------------------------
   Signature boxes on the read-only document view
   --------------------------------------------------------------------------
   Deliberately not the same look as the editor's boxes. There, a box is a thing
   you move; here it is a thing you read. Yours is emphasised and everybody
   else's is faint, because the question a signer is asking is "where do I sign",
   not "where does everyone sign".
   -------------------------------------------------------------------------- */

.doc-overlay { position: absolute; inset: 0; pointer-events: none; }

.doc-field {
    position: absolute;
    border-radius: 3px;
    display: flex;
    flex-direction: column;
    justify-content: center;
    overflow: hidden;
    padding: 1px 3px;
}

/* Somebody else's, still to be signed: an outline and nothing more. */
.doc-field.is-pending {
    border: 1.5px dashed var(--sig, #9aa7b2);
    background: color-mix(in srgb, var(--sig, #9aa7b2) 8%, transparent);
    opacity: 0.55;
}

/* Yours. Solid, filled, and impossible to miss. */
.doc-field.is-mine.is-pending {
    border: 2px solid var(--t-cyan);
    background: color-mix(in srgb, var(--t-cyan) 22%, transparent);
    opacity: 1;
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--t-cyan) 25%, transparent);
}

/* Signed: the same script face the stamped PDF uses, so what you saw before
   signing and what lands on the paper are recognisably the same block. */
.doc-field.is-signed {
    border: 1px solid transparent;
    border-bottom: 1px solid var(--t-line-strong);
    background: transparent;
    opacity: 1;
}

.doc-field-sig {
    font-family: "Dancing Script", cursive;
    font-size: 1.05rem;
    line-height: 1.1;
    color: var(--t-blue);
    overflow: hidden;
    white-space: nowrap;
}

.doc-field-when { font-size: 0.5rem; color: var(--t-on-paper-dim); line-height: 1.2; }

.doc-field-tag {
    align-self: flex-start;
    max-width: 100%;
    padding: 0 3px;
    border-radius: 2px;
    background: var(--t-cyan);
    color: var(--t-on-paper);
    font-size: 0.62rem;
    font-weight: 600;
    line-height: 1.35;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.doc-field.is-pending:not(.is-mine) .doc-field-tag {
    background: var(--sig, #9aa7b2);
}

/* A short pulse after "Find my signature" scrolls to it. Scrolling alone leaves
   the reader hunting for what moved on a dense page. */
@keyframes doc-field-flash {
    0%, 100% { box-shadow: 0 0 0 3px color-mix(in srgb, var(--t-cyan) 25%, transparent); }
    50%      { box-shadow: 0 0 0 10px color-mix(in srgb, var(--t-cyan) 55%, transparent); }
}

.doc-field.is-flash { animation: doc-field-flash 0.6s ease-in-out 2; }

/* The viewer panel now renders pages rather than hosting an iframe. */
.doc-viewer-panel .prep-pages { max-height: 78vh; }

/* --------------------------------------------------------------------------
   Contracting: rates, placements, compliance
   -------------------------------------------------------------------------- */

.grid-full { grid-column: 1 / -1; }
.modal-wide { max-width: 780px; }
.warn-text { color: var(--t-amber); }

.panel-foot {
    display: flex;
    align-items: center;
    gap: 1rem;
    padding: 0.6rem 0.9rem;
    border-top: 1px solid var(--t-line);
}

.panel-foot .hint { margin: 0; flex: 1 1 auto; }

/* The current rate, stated once and large. Everything else is history. */
.rate-now {
    display: flex;
    align-items: baseline;
    gap: 0.6rem;
    padding: 0.7rem 0.9rem;
    border: 1px solid var(--t-line);
    border-left: 3px solid var(--t-green);
    border-radius: var(--t-radius);
    background: var(--t-bg-2);
    margin-bottom: 0.5rem;
}

.rate-amount { font-size: 1.15rem; font-weight: 600; color: var(--t-text); }
.rate-since { font-size: 0.75rem; color: var(--t-text-faint); }
tr.is-current-rate { background: var(--t-green-soft); }

/* Placements */
.placement {
    position: relative;
    padding: 0.7rem 0.85rem;
    margin-bottom: 0.5rem;
    border: 1px solid var(--t-line);
    border-left: 3px solid var(--t-line-strong);
    border-radius: var(--t-radius);
    background: var(--t-bg-2);
}

.placement.status-active { border-left-color: var(--t-green); }
.placement.status-planned { border-left-color: var(--t-cyan); }
.placement.status-completed { opacity: 0.6; }
.placement.status-cancelled { opacity: 0.45; border-left-color: var(--t-red); }

.placement-head { display: flex; justify-content: space-between; gap: 0.6rem; align-items: baseline; }
.placement-project { font-size: 0.86rem; font-weight: 600; color: var(--t-text); }
.placement-client { font-size: 0.74rem; color: var(--t-text-dim); }
.placement-role { font-size: 0.78rem; color: var(--t-cyan); margin-top: 0.15rem; }

.placement-facts {
    display: flex;
    flex-wrap: wrap;
    gap: 0.35rem 0.9rem;
    margin-top: 0.35rem;
    font-size: 0.72rem;
    color: var(--t-text-faint);
}

.travel-flag { color: var(--t-amber); }

/* "no site — not on the globe". Red rather than amber: it is the reason a
   person is invisible on the deployment dashboard, and amber alongside
   "travel paid" would read as another neutral fact. */
.placement-warn { color: var(--t-red); }

/* A select with a button beside it — "Client [New client]", "Site [Edit]".
   The select takes the slack so the button keeps its own width. */
.picker-row {
    display: flex;
    align-items: center;
    gap: 0.4rem;
}

.picker-row .form-select { flex: 1 1 auto; min-width: 0; }
.picker-row .btn { flex: 0 0 auto; }

/* ==========================================================================
   The crew panel on a project
   --------------------------------------------------------------------------
   One row per placement. Rows rather than a table: the facts underneath a name
   are of uneven length — dates, days remaining, a site, two warnings — and a
   table would give every one of them a column that is empty most of the time.
   ========================================================================== */

.crew-list {
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
}

.crew-row {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.4rem 0.8rem;
    padding: 0.55rem 0.7rem;
    border: 1px solid var(--t-line);
    border-left: 3px solid var(--t-line-strong);
    border-radius: var(--t-radius);
    background: var(--t-bg-2);
}

/* The same four states the timeline colours its bars with, so a status means
   one thing across the application. */
.crew-row.status-active { border-left-color: var(--t-green); }
.crew-row.status-planned { border-left-color: var(--t-blue); }
.crew-row.status-completed { border-left-color: var(--t-line-strong); }
.crew-row.status-cancelled { border-left-color: var(--t-red); opacity: 0.6; }

.crew-main {
    display: flex;
    flex-direction: column;
    gap: 0.1rem;
    min-width: 11rem;
    flex: 1 1 12rem;
}

.crew-name { font-size: 0.85rem; color: var(--t-text); }
.crew-sub { font-size: 0.7rem; color: var(--t-text-faint); }

.crew-facts {
    display: flex;
    flex-wrap: wrap;
    gap: 0.15rem 0.7rem;
    font-size: 0.7rem;
    color: var(--t-text-dim);
    flex: 2 1 18rem;
}

.crew-warn { color: var(--t-red); }

.crew-actions {
    display: flex;
    gap: 0.3rem;
    flex: 0 0 auto;
}

/* The resolved location on the placement dialog. A statement, not a field:
   the site comes from the project, and there is nothing here to fill in. */
.placement-place {
    margin: 0.1rem 0 0;
    font-size: 0.84rem;
    color: var(--t-text);
    line-height: 1.45;
}

.placement-place .muted { color: var(--t-text-faint); }
.placement-place .warn { color: var(--t-red); }

/* A collapsed exception. `details` is used rather than a hand-rolled toggle
   because the browser already does the keyboard and screen-reader work. */
#a-site-override > summary {
    cursor: pointer;
    font-size: 0.72rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--t-text-faint);
    padding: 0.35rem 0;
}

#a-site-override > summary:hover { color: var(--t-cyan); }

#a-site-override[open] > summary { color: var(--t-text); }

.override-grid {
    margin-top: 0.5rem;
    padding: 0.7rem 0.8rem;
    border: 1px solid var(--t-line);
    border-radius: var(--t-radius);
    background: var(--t-bg-2);
}

/* An actions row, NOT a button pinned to the corner.
   It used to be `position: absolute; top: .5rem; right: .5rem`, which put the
   Edit button directly on top of the status pill in the head row — "Active"
   rendered as "A" with "ctive" behind the button. One button hid a word; two
   would have hidden each other. */
.placement-actions,
.cert-actions {
    display: flex;
    gap: 0.4rem;
    margin-top: 0.55rem;
    padding-top: 0.5rem;
    border-top: 1px solid var(--t-line);
}

.placement-actions .btn-danger,
.cert-actions .btn-danger { margin-left: auto; color: var(--t-red); }

.placement-actions .btn-danger:hover,
.cert-actions .btn-danger:hover { border-color: var(--t-red); background: var(--t-red-soft); }

/* Certificates. The border colour is the whole point of the card. */
.cert {
    position: relative;
    padding: 0.7rem 0.85rem;
    margin-bottom: 0.5rem;
    border: 1px solid var(--t-line);
    border-left: 3px solid var(--t-line-strong);
    border-radius: var(--t-radius);
    background: var(--t-bg-2);
}

.cert-expired { border-left-color: var(--t-red); background: var(--t-red-soft); }
.cert-expiring { border-left-color: var(--t-amber); background: var(--t-amber-soft); }
.cert-valid { border-left-color: var(--t-green); }

.cert-head { display: flex; justify-content: space-between; gap: 0.6rem; align-items: baseline; }
.cert-title { font-size: 0.86rem; font-weight: 600; color: var(--t-text); }

.cert-facts {
    display: flex;
    flex-wrap: wrap;
    gap: 0.35rem 0.9rem;
    margin-top: 0.3rem;
    font-size: 0.72rem;
    color: var(--t-text-faint);
}

.cert-verified { margin-top: 0.3rem; font-size: 0.7rem; color: var(--t-green); }

/* The save-state line beside a Save button. Three states, each unmistakable at a
   glance, because the whole point is that a toast in the far corner was not. */
.save-state.is-dirty { color: var(--t-amber); }
.save-state.is-saved { color: var(--t-green); }
.save-state.is-error { color: var(--t-red); }

/* A day with one half of a pair filled in. Amber rather than red: it is not a
   mistake yet, it is a row that is not finished. The chip sits under the date
   in the day column so the eye lands on it while typing, not at signing time. */
.ts-warn {
    display: inline-block;
    margin-top: 0.2rem;
    padding: 0.05rem 0.3rem;
    border: 1px solid var(--t-amber);
    border-radius: 2px;
    background: var(--t-amber-soft);
    color: var(--t-amber);
    font-size: 0.62rem;
    line-height: 1.5;
    white-space: nowrap;
}

.ts-grid tr.is-incomplete td { background: color-mix(in srgb, var(--t-amber) 7%, transparent); }

/* ==========================================================================
   The internal noticeboard
   ========================================================================== */

.q-card,
.n-card {
    padding: 0.8rem 0.9rem;
    margin-bottom: 0.7rem;
    background: var(--t-bg-2);
    border: 1px solid var(--t-line);
    border-left-width: 3px;
    border-radius: var(--t-radius);
}

.q-card:last-child,
.n-card:last-child { margin-bottom: 0; }

/* Open for three days or more. Not a rule anybody agreed — a prompt, because a
   contractor's pay query going stale is what this screen exists to prevent. */
.q-card.is-stale { border-left-color: var(--t-amber); }
.n-card.is-expired { opacity: 0.6; }

.q-head,
.n-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 0.75rem;
    flex-wrap: wrap;
}

.q-subject,
.n-title { font-size: 0.92rem; font-weight: 600; color: var(--t-text); }

.q-meta,
.n-meta { margin: 0.2rem 0 0; font-size: 0.7rem; color: var(--t-text-faint); }

.q-body,
.n-body {
    margin: 0.5rem 0 0;
    font-size: 0.83rem;
    line-height: 1.55;
    color: var(--t-text-dim);
    white-space: pre-wrap;
}

.q-actions,
.n-actions {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin-top: 0.7rem;
    flex-wrap: wrap;
}

.n-actions .link-btn { margin-right: auto; }
.link-btn.is-warn { color: var(--t-amber); }

/* The question being answered, shown above the reply box so nobody answers
   from memory of a list they scrolled past. */
.asked-block {
    padding: 0.7rem 0.85rem;
    margin-bottom: 1rem;
    background: var(--t-bg-2);
    border-left: 3px solid var(--t-cyan);
    border-radius: var(--t-radius);
}

.asked-subject { margin: 0; font-size: 0.9rem; font-weight: 600; color: var(--t-text); }
.asked-meta { margin: 0.2rem 0 0; font-size: 0.7rem; color: var(--t-text-faint); }

.asked-body {
    margin: 0.5rem 0 0;
    font-size: 0.83rem;
    line-height: 1.55;
    color: var(--t-text-dim);
    white-space: pre-wrap;
}

/* Read state. Unread first and marked amber — the un-read half is the half
   somebody has to act on. */
.reads-list { list-style: none; margin: 0; padding: 0; }

.reads-list li {
    display: flex;
    justify-content: space-between;
    gap: 0.75rem;
    padding: 0.45rem 0;
    border-bottom: 1px solid var(--t-line);
    font-size: 0.83rem;
}

.reads-list li:last-child { border-bottom: 0; }
.reads-list li.is-unread { color: var(--t-amber); }
.reads-when { font-size: 0.72rem; color: var(--t-text-faint); white-space: nowrap; }
.reads-list li.is-unread .reads-when { color: var(--t-amber); }

/* ==========================================================================
   Quotes
   ========================================================================== */

.q-grid { min-width: 64rem; }
.q-grid td { padding: 0.25rem 0.4rem; vertical-align: middle; }

.q-grid input[type="number"],
.q-grid input[type="text"],
.q-grid select { min-width: 0; }

/* The totals block sits under the grid, right-aligned, the way a client reads
   the bottom of a quote. */
.q-totals {
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
    align-items: flex-end;
    padding: 0.9rem 1.1rem;
    border-top: 1px solid var(--t-line);
}

.q-total-row {
    display: flex;
    justify-content: space-between;
    gap: 2.5rem;
    min-width: 18rem;
    font-size: 0.85rem;
    color: var(--t-text-dim);
}

.q-total-row.is-grand {
    margin-top: 0.35rem;
    padding-top: 0.35rem;
    border-top: 1px solid var(--t-line-strong);
    font-size: 1.05rem;
    font-weight: 600;
    color: var(--t-text);
}

/* Optional extras are stated as excluded, not merely listed after the total.
   A client must never be able to read them as part of the price. */
.q-total-row.is-optional { margin-top: 0.5rem; font-size: 0.76rem; color: var(--t-amber); }
.q-total-row.is-hours { font-size: 0.76rem; color: var(--t-text-faint); }

.decide-actions { justify-content: flex-end; margin-top: -0.5rem; }

/* Revision history */
.rev-list { list-style: none; margin: 0; padding: 0; }

.rev-list li {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    padding: 0.45rem 0;
    border-bottom: 1px solid var(--t-line);
    font-size: 0.82rem;
}

.rev-list li:last-child { border-bottom: 0; }
.rev-list li.is-current { color: var(--t-text); }
.rev-total { margin-left: auto; }
.rev-when { color: var(--t-text-faint); font-size: 0.72rem; min-width: 7rem; text-align: right; }

/* A wider modal for the accept dialog, which carries a form rather than a
   question. */
.modal-card.modal-wide { max-width: 44rem; }

/* ==========================================================================
   The spreadsheet import result
   --------------------------------------------------------------------------
   Skipped and updated rows sort to the top, because those are the ones somebody
   has to do something about. "19 created" is the least interesting number here.
   ========================================================================== */

.import-counts {
    display: flex;
    flex-wrap: wrap;
    gap: 1.25rem;
    padding-bottom: 0.75rem;
    border-bottom: 1px solid var(--t-line);
    font-size: 0.8rem;
    color: var(--t-text-dim);
}

.import-counts strong {
    font-family: var(--t-mono);
    font-size: 1.05rem;
    color: var(--t-text);
    margin-right: 0.3rem;
}

.import-counts .is-warn strong { color: var(--t-amber); }

.import-warnings {
    margin: 0.75rem 0 0;
    padding: 0.6rem 0.85rem 0.6rem 1.9rem;
    border-left: 3px solid var(--t-amber);
    background: var(--t-amber-soft);
    border-radius: var(--t-radius);
    font-size: 0.78rem;
    line-height: 1.5;
    color: var(--t-text);
}

.import-list { list-style: none; margin: 0.85rem 0 0; padding: 0; }

.import-row {
    display: grid;
    grid-template-columns: 5.5rem minmax(9rem, 14rem) minmax(0, 1fr);
    gap: 0.75rem;
    align-items: baseline;
    padding: 0.4rem 0;
    border-bottom: 1px solid var(--t-line);
    font-size: 0.78rem;
}

.import-row:last-child { border-bottom: 0; }

.import-name { color: var(--t-text); display: flex; flex-direction: column; gap: 0.1rem; }
.import-row-num { font-size: 0.66rem; color: var(--t-text-faint); font-family: var(--t-mono); }
.import-detail { color: var(--t-text-dim); line-height: 1.5; }

@media (max-width: 800px) {
    .import-row { grid-template-columns: minmax(0, 1fr); gap: 0.2rem; }
}


/* ==========================================================================
   The theme toggle
   --------------------------------------------------------------------------
   One button in the topbar. BOTH icons are in the markup and CSS shows one, so
   switching costs no request and there is nothing to load at the moment of the
   press — the page has to repaint instantly or the whole thing feels cheap.

   Which icon shows is driven by data-theme-now, written by the server on first
   render and by theme.js on every press. The icon shown is the one you are
   switching TO, which is the convention every operating system uses: in the
   dark theme you see a sun, and pressing it gives you light.
   ========================================================================== */

.theme-toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2rem;
    height: 2rem;
    flex: 0 0 auto;
    padding: 0;
    border: 1px solid transparent;
    border-radius: var(--t-radius);
    background: transparent;
    color: var(--t-text-dim);
    cursor: pointer;
    transition: color var(--t-fast), background var(--t-fast), border-color var(--t-fast);
}

.theme-toggle:hover {
    color: var(--t-accent);
    border-color: var(--t-line);
    background: var(--t-bg-2);
}

.theme-toggle:focus-visible {
    outline: 2px solid var(--t-accent);
    outline-offset: 1px;
}

.theme-toggle .icon { width: 1.05rem; height: 1.05rem; }

/* Only one of the two is ever displayed. Not opacity: a hidden-but-present icon
   would still take part in the flex layout and offset the visible one. */
.theme-icon { display: none; line-height: 0; }

.theme-toggle[data-theme-now="dark"] .theme-icon-sun { display: inline-flex; }
.theme-toggle[data-theme-now="light"] .theme-icon-moon { display: inline-flex; }

/* On the signed-out shell there is no topbar to sit in, so it goes top-right of
   the page — above the login card, out of the way of the form. */
.anon-theme {
    position: absolute;
    top: 1rem;
    right: 1.15rem;
    z-index: 3;
}

/* =====================================================================
   QUOTES
   =====================================================================

   Every colour here is a token, never a literal, so the light theme is
   already handled: site.css redefines the tokens and nothing below has to
   know which theme is showing. Adding a hex value to this block is how a
   light-theme bug gets shipped.
   ===================================================================== */

/* The import report ------------------------------------------------- */

.report-heading {
    font-size: 0.78rem;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    color: var(--t-text-dim);
    margin: 1.25rem 0 0.5rem;
}

.report-heading:first-child {
    margin-top: 0;
}

.report-list {
    margin: 0 0 0.9rem;
    padding-left: 1.15rem;
    line-height: 1.55;
}

.report-list li {
    margin-bottom: 0.3rem;
}

/* Reconciliation lines are read as columns of figures, so they get the
   monospace face and tabular digits — "126387.00" under "126387.00" is
   checkable at a glance and proportional digits are not. */
.report-mono li {
    font-family: var(--t-mono);
    font-variant-numeric: tabular-nums;
    font-size: 0.86rem;
}

.report-warn li {
    color: var(--t-text-warn);
}

.report-danger li {
    color: var(--t-text-danger);
}

/* Cash phasing ------------------------------------------------------ */

/* Twelve columns of money do not fit a laptop, and squeezing them makes
   every figure unreadable. The table keeps its natural width and scrolls
   inside .table-wrap instead; the two identifying columns stay put so a
   scrolled row still says which sheet it is. */
.phasing-table {
    min-width: 64rem;
}

.phasing-table th:first-child,
.phasing-table td:first-child,
.phasing-table th:nth-child(2),
.phasing-table td:nth-child(2) {
    position: sticky;
    background: var(--t-bg-1);
    z-index: 1;
}

.phasing-table th:first-child,
.phasing-table td:first-child {
    left: 0;
}

.phasing-table th:nth-child(2),
.phasing-table td:nth-child(2) {
    left: 4rem;
    box-shadow: 1px 0 0 var(--t-line);
}

/* Cost sheets ------------------------------------------------------- */

.sheet-block {
    border: 1px solid var(--t-line);
    border-radius: var(--t-radius-lg);
    margin-bottom: 0.6rem;
    background: var(--t-bg-1);
    overflow: hidden;
}

.sheet-block[open] {
    border-color: var(--t-line-strong);
}

.sheet-summary {
    display: grid;
    grid-template-columns: 3.5rem 1fr auto 9rem;
    align-items: center;
    gap: 0.85rem;
    padding: 0.7rem 0.9rem;
    cursor: pointer;
    list-style: none;
    transition: background var(--t-fast);
}

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

.sheet-summary:hover {
    background: var(--t-wash-1);
}

.sheet-summary:focus-visible {
    outline: 2px solid var(--t-accent);
    outline-offset: -2px;
}

.sheet-code {
    font-weight: 700;
    font-size: 1.05rem;
    color: var(--t-text-dim);
}

.sheet-name {
    font-weight: 600;
}

.sheet-window {
    font-size: 0.82rem;
    white-space: nowrap;
}

.sheet-total {
    text-align: right;
    font-variant-numeric: tabular-nums;
    font-weight: 700;
}

.sheet-block .table-wrap {
    border-top: 1px solid var(--t-line);
}

.sheet-block > .hint {
    padding: 0.8rem 0.9rem;
    margin: 0;
    border-top: 1px solid var(--t-line);
}

/* An excluded line stays visible and reads as struck out of the totals —
   the whole reason exclusion is a property of the line rather than a
   hidden row. */
.row-muted td {
    color: var(--t-text-faint);
}

@media (max-width: 720px) {
    .sheet-summary {
        grid-template-columns: 2.75rem 1fr;
        row-gap: 0.35rem;
    }

    .sheet-window,
    .sheet-total {
        grid-column: 2;
        text-align: left;
    }
}

/* =====================================================================
   QUOTE WORKBENCH — editing
   =====================================================================
   Tokens only, never literals, so the light theme is already handled.
   ===================================================================== */

.head-actions,
.panel-head-actions {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    flex-wrap: wrap;
}

/* Per-section actions sit inside the open section, not on its summary row —
   a delete button on a collapsed header is a delete button people press by
   accident while trying to expand it. */
.sheet-toolbar {
    display: flex;
    gap: 0.5rem;
    padding: 0.6rem 0.9rem;
    border-top: 1px solid var(--t-line);
    background: var(--t-wash-1);
    flex-wrap: wrap;
}

.row-actions {
    text-align: right;
    white-space: nowrap;
}

.row-actions .btn {
    opacity: 0;
    transition: opacity var(--t-fast);
}

/* Row actions appear on hover and on keyboard focus. Focus matters as much as
   hover: without it the buttons are unreachable by keyboard entirely. */
tr:hover .row-actions .btn,
.row-actions .btn:focus-visible {
    opacity: 1;
}

@media (hover: none) {
    .row-actions .btn { opacity: 1; }
}

/* The dialog ------------------------------------------------------- */

.tri-dialog {
    border: 1px solid var(--t-line-strong);
    border-radius: var(--t-radius-lg);
    background: var(--t-bg-1);
    color: var(--t-text);
    padding: 0;
    width: min(46rem, calc(100vw - 2rem));
    max-height: calc(100vh - 4rem);
    box-shadow: var(--t-shadow-modal);
}

.tri-dialog::backdrop {
    background: var(--t-scrim);
}

.tri-dialog-form {
    display: flex;
    flex-direction: column;
    max-height: calc(100vh - 4rem);
}

.tri-dialog-head,
.tri-dialog-foot {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    padding: 0.9rem 1.1rem;
}

.tri-dialog-head {
    border-bottom: 1px solid var(--t-line);
}

.tri-dialog-head h2 {
    margin: 0;
    font-size: 1.05rem;
}

.tri-dialog-body {
    padding: 1.1rem;
    overflow-y: auto;
}

.tri-dialog-foot {
    border-top: 1px solid var(--t-line);
    background: var(--t-wash-1);
}

.tri-dialog-buttons {
    display: flex;
    gap: 0.5rem;
    margin-left: auto;
}

.check-row {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    cursor: pointer;
}

/* The line picker -------------------------------------------------- */

.pick-results {
    margin-top: 0.5rem;
    max-height: 15rem;
    overflow-y: auto;
    border: 1px solid var(--t-line);
    border-radius: var(--t-radius-lg);
}

.pick-row {
    display: grid;
    grid-template-columns: 7rem 1fr 9rem;
    gap: 0.75rem;
    align-items: start;
    width: 100%;
    text-align: left;
    padding: 0.5rem 0.7rem;
    background: none;
    border: 0;
    border-bottom: 1px solid var(--t-line);
    color: inherit;
    cursor: pointer;
    font: inherit;
}

.pick-row:last-child { border-bottom: 0; }

.pick-row:hover:not(:disabled) { background: var(--t-wash-2); }

.pick-row:focus-visible {
    outline: 2px solid var(--t-accent);
    outline-offset: -2px;
}

/* Unselectable entries stay visible and say why. Hiding a reference price
   means being asked where the flight prices went. */
.pick-row.is-locked {
    cursor: not-allowed;
    opacity: 0.62;
}

.pick-row.is-picked {
    background: var(--t-wash-2);
    box-shadow: inset 3px 0 0 var(--t-accent);
}

.pick-code { font-weight: 600; }

.pick-desc,
.pick-rate {
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
}

.pick-rate {
    text-align: right;
    font-variant-numeric: tabular-nums;
}

.cell-sub.warn { color: var(--t-text-warn); }

@media (max-width: 720px) {
    .pick-row {
        grid-template-columns: 1fr;
        row-gap: 0.25rem;
    }

    .pick-rate { text-align: left; }
}
