/* Avola Flow — one stylesheet, no framework. Colour/type live as custom
   properties here (see docs/BRAND-GUIDELINES.md) — never hardcode a new hex
   value in a rule below; add or reuse a token instead. */

:root {
    --color-orange: #ff7f32;
    --color-ink: #111110;
    --color-paper: #f8f7f4;
    --color-navy: #000060;
    --color-black: #000000;
    --color-white: #ffffff;
    --color-red: #e5302a;
    --color-green: #1f9d55;
    --color-blue: #1e63e9;
    --color-yellow: #ffcd00;

    --surface-page: var(--color-paper);
    --surface-card: var(--color-white);
    --text-1: var(--color-ink);
    --text-2: rgba(17, 17, 16, 0.62);
    --text-3: rgba(17, 17, 16, 0.42);
    --text-4: rgba(17, 17, 16, 0.3);
    --line: rgba(17, 17, 16, 0.12);

    --glow: rgba(255, 127, 50, 0.26);
    --glow-edge: rgba(255, 127, 50, 0);

    /* Single-series chart bars (Statistics page) — same blue in both themes:
       it clears the 3:1 mark-vs-surface contrast floor against both
       --surface-card values, so no dark-mode override is needed. */
    --chart-bar: var(--color-blue);

    --font-display: "Playfair Display", Georgia, serif;
    --font-body: "DM Sans", -apple-system, BlinkMacSystemFont, sans-serif;

    --sidebar-width: 15rem;
    --radius: 0.5rem;
}

:root[data-theme="dark"] {
    --surface-page: var(--color-ink);
    --surface-card: #1c1c1a;
    --text-1: var(--color-paper);
    --text-2: rgba(248, 247, 244, 0.6);
    --text-3: rgba(248, 247, 244, 0.35);
    --text-4: rgba(248, 247, 244, 0.2);
    --line: rgba(248, 247, 244, 0.08);
    --glow: rgba(0, 0, 96, 0.6);
    --glow-edge: rgba(0, 0, 96, 0);
}

@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) {
        --surface-page: var(--color-ink);
        --surface-card: #1c1c1a;
        --text-1: var(--color-paper);
        --text-2: rgba(248, 247, 244, 0.6);
        --text-3: rgba(248, 247, 244, 0.35);
        --text-4: rgba(248, 247, 244, 0.2);
        --line: rgba(248, 247, 244, 0.08);
        --glow: rgba(0, 0, 96, 0.6);
        --glow-edge: rgba(0, 0, 96, 0);
    }
}

* {
    box-sizing: border-box;
}

html,
body {
    margin: 0;
    padding: 0;
    /* iOS Safari auto-inflates font size on text it judges "hard to read" —
       e.g. a long, unwrapped table cell (cron log's Summary column) wider
       than the viewport. Disable that so text always renders at the size
       this stylesheet actually specifies. */
    -webkit-text-size-adjust: 100%;
    text-size-adjust: 100%;
}

body {
    background: var(--surface-page);
    color: var(--text-1);
    font-family: var(--font-body);
    font-weight: 400;
    font-size: 16px;
    line-height: 1.5;
}

h1,
h2,
h3 {
    font-family: var(--font-display);
    font-weight: 700;
    margin: 0 0 0.5em;
}

.page-title {
    font-size: 1.75rem;
    margin: 0;
    /* Truncates with an ellipsis instead of wrapping/overflowing when there
       isn't room (e.g. a long project name + code on a narrow screen).
       min-width: 0 is required for this to work at all — .page-header is a
       flex row, and flex items default to min-width: auto, which blocks
       shrinking below content size regardless of overflow/text-overflow. */
    min-width: 0;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
}

a {
    color: inherit;
}

::selection {
    background: var(--color-orange);
    color: var(--color-paper);
}

/* Soft glow anchored to the top-right of the viewport — sits above it so
   the vertical drift never exposes a hard edge (the bright spot lives
   inside the element, not on its top edge), leaving fade in every direction. */
body::before {
    content: "";
    position: fixed;
    top: -13vh;
    right: 0;
    width: 65vw;
    max-width: 820px;
    height: 80vh;
    pointer-events: none;
    z-index: 0;
    transform-origin: 100% 16%;
    background: radial-gradient(120% 120% at 100% 16%, var(--glow), var(--glow-edge) 62%);
    /* Breathing (scale/opacity) and drift (translate) run as two independent
       animations at different durations so they desync and feel alive. */
    animation: glow 8s ease-in-out infinite alternate,
               glow-drift 11s ease-in-out infinite alternate;
}

@keyframes glow {
    from { opacity: 0.35; scale: 0.85; }
    to   { opacity: 1;    scale: 1.45; }
}

@keyframes glow-drift {
    from { translate: 0 -4vh; }
    to   { translate: 0 10vh; }
}

/* --- App layout: left sidebar (desktop) / top bar (mobile) --- */

body.has-nav {
    display: flex;
    min-height: 100vh;
}

.sidebar {
    width: var(--sidebar-width);
    flex-shrink: 0;
    background: var(--surface-card);
    border-right: 1px solid var(--line);
    display: flex;
    flex-direction: column;
    padding: 1.5rem 1rem;
    position: sticky;
    top: 0;
    height: 100vh;
    overflow-y: auto;
}

/* Logo swaps ink/paper wordmark per theme (see .logo-ink/.logo-paper below),
   so no backing plate is needed for legibility in either theme. */
.sidebar-brand {
    margin-bottom: 1rem;
    padding: 0 0.75rem;
}

.sidebar-brand img {
    height: 1.5rem;
    width: auto;
}

/* --- Logo: theme-aware ink/paper wordmark swap --- */
/* Every logo location (sidebar, topbar, auth card) renders both wordmark
   variants; only one is visible at a time, based on the active theme. */
.logo-ink {
    display: block;
}

.logo-paper {
    display: none;
}

:root[data-theme="dark"] .logo-ink {
    display: none;
}

:root[data-theme="dark"] .logo-paper {
    display: block;
}

@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) .logo-ink {
        display: none;
    }

    :root:not([data-theme="light"]) .logo-paper {
        display: block;
    }
}

.env-banner {
    background: var(--color-yellow);
    color: var(--color-ink);
    font-family: var(--font-body);
    font-weight: 500;
    font-size: 0.75rem;
    text-align: center;
    border-radius: var(--radius);
    padding: 0.35rem;
    margin: 0 0 1rem;
}

.sidebar-nav {
    list-style: none;
    margin: 0;
    padding: 0;
    flex: 1;
}

.sidebar-nav li + li {
    margin-top: 0.25rem;
}

.sidebar-nav a {
    display: block;
    padding: 0.5rem 0.75rem;
    border-radius: var(--radius);
    text-decoration: none;
    font-weight: 500;
    color: var(--text-1);
}

.sidebar-nav a:hover {
    background: var(--line);
}

.sidebar-footer {
    border-top: 1px solid var(--line);
    padding-top: 1rem;
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}

.sidebar-account {
    font-weight: 500;
    text-decoration: none;
}

.sidebar-version {
    color: var(--text-4);
    font-size: 0.75rem;
}

.topbar {
    display: none;
}

.nav-backdrop {
    display: none;
}

.main {
    flex: 1;
    min-width: 0;
    padding: 2rem;
    position: relative;
}

/* --- Responsive: sidebar becomes a slide-in panel under a top bar --- */

@media (max-width: 880px) {
    body.has-nav {
        display: block;
    }

    .topbar {
        display: flex;
        align-items: center;
        gap: 1rem;
        padding: 0.75rem 1rem;
        background: var(--surface-card);
        border-bottom: 1px solid var(--line);
        position: sticky;
        top: 0;
        z-index: 40;
    }

    .topbar-logo {
        height: 1.25rem;
    }

    .nav-toggle {
        appearance: none;
        background: none;
        border: none;
        padding: 0.5rem;
        display: flex;
        flex-direction: column;
        gap: 0.25rem;
        cursor: pointer;
    }

    .nav-toggle span {
        display: block;
        width: 1.25rem;
        height: 2px;
        background: var(--text-1);
    }

    .sidebar {
        position: fixed;
        inset: 0 25% 0 0;
        z-index: 30;
        transform: translateX(-100%);
        transition: transform 0.2s ease;
    }

    .sidebar.is-open {
        transform: translateX(0);
    }

    .nav-backdrop {
        position: fixed;
        inset: 0;
        background: rgba(0, 0, 0, 0.4);
        z-index: 25;
    }

    .nav-backdrop.is-open {
        display: block;
    }

    .main {
        padding: 1.25rem;
    }
}

/* --- Auth screens (login / verify): no sidebar, centered card --- */

.auth-main {
    min-height: 100vh;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    padding: 1.5rem;
    position: relative;
}

@media (prefers-reduced-motion: reduce) {
    body::before {
        animation: none;
    }
}

.auth-card {
    position: relative;
    background: var(--surface-card);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    padding: 2rem;
    width: 100%;
    max-width: 24rem;
}

.auth-logo {
    height: 1.5rem;
    margin-bottom: 1.5rem;
}

.auth-intro {
    color: var(--text-2);
    margin-top: -0.25em;
}

.auth-secondary {
    margin-top: 1rem;
    text-align: center;
}

.auth-copyright {
    margin-top: 1.5rem;
    color: var(--text-4);
    font-size: 0.75rem;
    text-align: center;
}

/* --- Forms --- */

.form-card {
    background: var(--surface-card);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
    padding: 2rem;
    max-width: 34rem;
    margin-bottom: 1.5rem;
}

.form,
.form-inline {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
    max-width: 28rem;
}

/* Modifier for a .form that holds nothing but hidden fields + a submit
   button (e.g. the invoice attach preview's "Confirm attach") — the 28rem
   cap above exists for readable input widths, which doesn't apply here,
   and left in place makes the button visibly narrower than the read-only
   summary/secondary-actions around it, which both fill the card's full
   width. */
.form-fill {
    max-width: none;
}

/* Modifier for a .form-card holding a long read-only value (the calendar
   feed URL) that needs more room than the default 34rem — same idea as
   .form-fill, one level up. */
.form-card-wide {
    max-width: 44rem;
}

/* The calendar feed URL is long; without an explicit width it renders at
   the browser's intrinsic default (~20 characters) even inside a wider
   .form-fill container. */
#calendar_feed_url {
    width: 100%;
}

.form button[type="submit"] {
    margin-top: 0.5rem;
}

.form-inline {
    display: inline-flex;
    flex-direction: row;
}

.form-secondary-actions {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
    margin-top: 1.5rem;
    padding-top: 1.5rem;
    border-top: 1px solid var(--line);
}

.form-secondary-actions .form-inline {
    display: flex;
}

.form-secondary-actions .button-secondary,
.form-secondary-actions .button-danger {
    width: 100%;
}

label {
    font-weight: 500;
    font-size: 0.9rem;
}

input[type="text"],
input[type="email"],
input[type="datetime-local"],
input[type="date"],
input[type="search"],
select,
textarea {
    font: inherit;
    padding: 0.5rem 0.65rem;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    background: var(--surface-page);
    color: var(--text-1);
}

textarea {
    min-height: 5rem;
    resize: vertical;
}

input[type="text"]:focus,
input[type="email"]:focus,
input[type="datetime-local"]:focus,
input[type="date"]:focus,
input[type="search"]:focus,
select:focus,
textarea:focus {
    outline: 2px solid var(--color-orange);
    outline-offset: 1px;
}

input[type="text"]:disabled,
input[type="email"]:disabled {
    background: var(--line);
    color: var(--text-2);
    cursor: not-allowed;
}

/* Tightens a field-note to read as bound to the field right above it,
   rather than reading as just another uniformly .form-gap-spaced child —
   .form's flex gap otherwise puts as much space between a field and its
   own note as between that note and the next field's label. */
.form input + .field-note,
.form select + .field-note,
.form textarea + .field-note {
    margin-top: -0.4rem;
}

.checkbox,
.checkbox-group label {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    font-weight: 400;
}

fieldset.checkbox-group {
    border: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}

fieldset.checkbox-group legend {
    font-weight: 500;
    font-size: 0.9rem;
    padding: 0;
    margin-bottom: 0.25rem;
}

.field-note {
    color: var(--text-3);
    font-size: 0.85rem;
}

/* Same weight/size as a bare <label> (e.g. "API token" on
   views/api_docs_authentication.php) — for a small section heading (e.g.
   "Request"/"Response" on views/api_docs_endpoints.php) that isn't
   actually bound to a form control, so a real <label> would be a semantic
   mismatch. */
.field-label {
    font-weight: 500;
    font-size: 0.9rem;
}

/* Custom-styled file input (Documents "Add document" form) — the native
   control's OS-drawn button + filename text can't be restyled directly,
   so it's visually hidden (not display:none, so it stays keyboard/
   screen-reader operable and clicking its <label for="..."> still opens
   the native file picker with no JS required for that part) behind a
   field styled to read as one control, matching input[type="text"]/
   select's own border/radius/background rather than floating a bare
   button next to loose text. Only the filename text (app.js reflects
   input.files[0].name into .file-field-name on change) needs JS, since
   hiding the input also hides its own built-in filename display. */
.file-field {
    display: flex;
    align-items: stretch;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    background: var(--surface-page);
    overflow: hidden;
}

.file-field .button-secondary {
    flex-shrink: 0;
    border: none;
    border-right: 1px solid var(--line);
    border-radius: 0;
    background: var(--surface-card);
    padding: 0.5rem 1rem;
}

.file-field-input {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

/* Shows a focus ring on the visible label when the hidden input itself
   receives keyboard focus (e.g. Tab) — same outline treatment as every
   other focusable control, just relocated since the real input has none
   of its own visible box to put it on. */
.file-field-input:focus-visible + .button-secondary {
    outline: 2px solid var(--color-orange);
    outline-offset: -2px;
}

.file-field-name {
    display: flex;
    align-items: center;
    padding: 0 0.75rem;
    color: var(--text-3);
    font-size: 0.9rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* --- Buttons --- */

button,
.button,
.button-secondary,
.button-danger,
.link-button {
    font: inherit;
    font-weight: 500;
    border-radius: var(--radius);
    cursor: pointer;
    text-decoration: none;
    display: inline-block;
    text-align: center;
    transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}

button:not(.link-button):not(.button-secondary):not(.button-danger):not(.nav-toggle):not(.announcement-banner-dismiss),
.button {
    background: var(--color-ink);
    color: var(--color-paper);
    border: 1px solid var(--color-ink);
    padding: 0.55rem 1.1rem;
}

:root[data-theme="dark"] button:not(.link-button):not(.button-secondary):not(.button-danger):not(.nav-toggle):not(.announcement-banner-dismiss),
:root[data-theme="dark"] .button {
    background: var(--color-paper);
    color: var(--color-ink);
    border-color: var(--color-paper);
}

@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) button:not(.link-button):not(.button-secondary):not(.button-danger):not(.nav-toggle):not(.announcement-banner-dismiss),
    :root:not([data-theme="light"]) .button {
        background: var(--color-paper);
        color: var(--color-ink);
        border-color: var(--color-paper);
    }
}

button:hover:not(.link-button):not(.button-secondary):not(.button-danger):not(.nav-toggle):not(.announcement-banner-dismiss),
.button:hover {
    background: var(--color-orange);
    border-color: var(--color-orange);
    color: var(--color-ink);
}

.button-secondary {
    background: transparent;
    color: var(--text-1);
    border: 1px solid var(--line);
    padding: 0.55rem 1.1rem;
}

.button-secondary:hover {
    border-color: var(--color-ink);
}

:root[data-theme="dark"] .button-secondary:hover {
    border-color: var(--color-paper);
}

@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) .button-secondary:hover {
        border-color: var(--color-paper);
    }
}

.button-danger {
    background: transparent;
    color: var(--color-red);
    border: 1px solid var(--color-red);
    padding: 0.55rem 1.1rem;
}

.button-danger:hover:not(:disabled) {
    background: var(--color-red);
    color: var(--color-white);
}

.button-danger:disabled {
    color: var(--text-4);
    border-color: var(--line);
    cursor: not-allowed;
}

button:active,
.button:active,
.button-secondary:active,
.button-danger:active:not(:disabled) {
    transform: translateY(1px);
}

button:focus-visible,
.button:focus-visible,
.button-secondary:focus-visible,
.button-danger:focus-visible,
.link-button:focus-visible {
    outline: 2px solid var(--color-orange);
    outline-offset: 2px;
}

.link-button {
    background: none;
    border: none;
    padding: 0;
    color: var(--text-2);
    text-decoration: underline;
}

.link-button:hover {
    color: var(--text-1);
}

/* --- Tables --- */

.table-card {
    background: var(--surface-card);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
    overflow-x: auto;
    overflow-y: hidden;
    -webkit-overflow-scrolling: touch;
    margin-bottom: 1.5rem;
}

/* Compact controls for interactive cells (e.g. a per-row role <select> or
   Remove button) — a table row is a tighter context than a standalone form,
   so these shrink relative to the default input/button sizing. */
.table-card select,
.table-card .button-secondary,
.table-card .button-danger {
    padding: 0.3rem 0.6rem;
    font-size: 0.85rem;
}

/* A <select>'s rendered width follows its currently selected option's text,
   so per-row role dropdowns with different values (e.g. "Client" vs
   "Collaborator") end up different widths in the same column. Fix it so
   every row lines up regardless of which option is selected. */
.table-card select {
    width: 10rem;
}

.table {
    width: 100%;
    border-collapse: collapse;
}

.table th,
.table td {
    text-align: left;
    padding: 0.75rem 1rem;
    border-bottom: 1px solid var(--line);
    white-space: nowrap;
}

.table th {
    font-weight: 600;
    color: var(--text-1);
    font-size: 0.85rem;
}

.table tbody tr:nth-child(even) {
    background: color-mix(in srgb, var(--surface-card) 96%, var(--text-1) 4%);
}

.table tbody tr:hover {
    background: var(--line);
}

.table tbody tr[data-href] {
    cursor: pointer;
}

.table-card .table tbody tr:last-child td {
    border-bottom: none;
}

/* Totals row (e.g. billing-profile invoices' "Total" row) — bold text plus
   a heavier top border reads as a summary without introducing a new
   color/background convention. tbody's zebra striping and hover
   background don't reach tfoot (both scoped to "tbody tr"), which is the
   right default here anyway — a total isn't a clickable/hoverable row. */
.table tfoot td {
    font-weight: 700;
    border-top: 2px solid var(--line);
}

/* Profitability tab's Revenue/category/vendor/Result breakdown table —
   a vendor row nested under its category, indented and muted to read as
   supporting detail rather than another top-level line. */
.table-subrow td {
    color: var(--text-2);
}

.table-subrow td:first-child {
    padding-left: 2rem;
}

.table-card .table tfoot tr:last-child td {
    border-bottom: none;
}

/* Right-aligned, tabular-nums number column — for any .table's money
   column. Scoped as ".table th.num, .table td.num", not a bare ".num",
   because a bare single-class selector loses the specificity contest
   against ".table th, .table td { text-align: left }" above (one class
   vs. class+element) — the rule was applied correctly in every view but
   silently never took effect anywhere, including the Profitability tab
   it was first added for, until this fix. Doesn't need table-layout:
   fixed's cross-table width-matching (see .statistics-table below, which
   does, for several separate tables) — auto layout's default column
   sizing is exactly what a single free-standing table wants; fixed
   layout on a table whose first header cell is empty (no cross-table
   label to size off) collapses that column instead, skewing everything
   toward the number column. */
.table th.num,
.table td.num {
    text-align: right;
    font-variant-numeric: tabular-nums;
}

/* Statistics page's four breakdown tables — table-layout: fixed (scoped to
   just these tables, not .table-card .table in general, so the Schedule
   tab's per-person tables etc. keep sizing off their own content) so a
   .num column's width is actually the exact width declared below, not just
   a soft hint the default auto layout can override. With every non-.num
   column left unset (auto), the fixed-width .num columns render at the
   identical pixel width and the identical distance from the table's right
   edge in every one of these tables regardless of how many columns (or how
   much label text) comes before them — that's what lines Hours/Share up
   from one breakdown to the next, not the auto-layout width hint alone. */
.statistics-table {
    table-layout: fixed;
}

.statistics-table th.num,
.statistics-table td.num {
    text-align: right;
    font-variant-numeric: tabular-nums;
    width: 7rem;
}

/* Per-project Profitability tab's Vendor costs / Category costs tables —
   same fixed-layout, cross-table-alignment idea as .statistics-table
   above, but with its own column widths rather than that class's uniform
   7rem for every .num column: "Amount (DKK excl. VAT)" is a much longer
   header than .statistics-table's "Hours"/"Share" labels ever needed to
   fit, and .table th/td's own white-space: nowrap means an over-narrow
   fixed column doesn't wrap that header, it visually overflows into the
   next one instead. */
.profitability-costs-table {
    table-layout: fixed;
}

.profitability-costs-table th:nth-child(2),
.profitability-costs-table td:nth-child(2) {
    width: 12rem;
}

.profitability-costs-table th:nth-child(3),
.profitability-costs-table td:nth-child(3) {
    width: 8rem;
}

/* Org-wide Statistics > Profitability tab's four tables (Revenue by
   client, Crew costs, Equipment rental costs, Category costs, Revenue &
   profit per hour by client) — three separate classes below, since each
   table has a different column count, but sharing one consistent set of
   per-role widths (money = 10rem, share = 7rem, hours = 6rem, per-hour
   rate = 8rem) so a column showing the same kind of figure lines up at
   the same position whichever table it's in, scrolling down the page.
   Deliberately NOT .profitability-costs-table above — that class is tuned
   for the per-project tab's "Amount (DKK excl. VAT)" header text, and
   resizing it to fit this page's shorter, page-wide-consistent widths
   would either cramp that header or waste space there; this page's own
   headers are shortened instead ("Amount", not "Amount (DKK excl. VAT)")
   specifically so 10rem is enough — every table on this page already
   establishes DKK excl. VAT as the page's implicit currency. */
.profitability-revenue-table,
.profitability-share-table,
.profitability-hours-table {
    table-layout: fixed;
}

.profitability-revenue-table th:nth-child(2),
.profitability-revenue-table td:nth-child(2),
.profitability-revenue-table th:nth-child(3),
.profitability-revenue-table td:nth-child(3),
.profitability-share-table th:nth-child(2),
.profitability-share-table td:nth-child(2),
.profitability-hours-table th:nth-child(2),
.profitability-hours-table td:nth-child(2),
.profitability-hours-table th:nth-child(3),
.profitability-hours-table td:nth-child(3) {
    width: 10rem;
}

.profitability-revenue-table th:nth-child(4),
.profitability-revenue-table td:nth-child(4),
.profitability-share-table th:nth-child(3),
.profitability-share-table td:nth-child(3) {
    width: 7rem;
}

.profitability-hours-table th:nth-child(4),
.profitability-hours-table td:nth-child(4) {
    width: 6rem;
}

.profitability-hours-table th:nth-child(5),
.profitability-hours-table td:nth-child(5),
.profitability-hours-table th:nth-child(6),
.profitability-hours-table td:nth-child(6) {
    width: 8rem;
}

/* Schedule tab: one .table-card per person, each its own <table> — plain
   auto table-layout sizes each one's columns off its own content, so From/
   To/Type/Status/Location end up different widths per person. table-layout:
   fixed plus explicit percentage widths makes every instance size
   identically regardless of content, since they all share the same
   full-width parent. Location (the only free-text column) is left unset so
   it absorbs whatever's left; Edit (staff-only, blank header) gets a narrow
   fixed width and simply doesn't exist in the non-staff column count. */
.schedule-table {
    table-layout: fixed;
}

.schedule-table th:nth-child(1), .schedule-table td:nth-child(1),
.schedule-table th:nth-child(2), .schedule-table td:nth-child(2) {
    width: 18%;
}

.schedule-table th:nth-child(3), .schedule-table td:nth-child(3) {
    width: 16%;
}

.schedule-table th:nth-child(4), .schedule-table td:nth-child(4) {
    width: 12%;
}

.schedule-table th:nth-child(6), .schedule-table td:nth-child(6) {
    width: 5rem;
}

/* .table already sets white-space: nowrap — pair it with an ellipsis so
   content wider than its fixed-percentage column clips gracefully instead
   of spilling into the next cell at in-between (non-mobile) widths. Only
   matters where a column actually has a fixed width; harmless no-op once
   the mobile override below resets everything to width: auto. */
.schedule-table th,
.schedule-table td {
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Below the same breakpoint hide-mobile uses: Location (the fixed layout's
   only unset/auto column) gets display: none, and browsers don't reliably
   reclaim that freed width for the remaining fixed-percentage columns —
   From/To/Type/Status end up squeezed narrower than their nowrap content,
   which then visibly overlaps across cell boundaries. Reverting to plain
   auto layout here sidesteps that entirely — same behavior mobile had
   before fixed-width columns existed at all. */
@media (max-width: 880px) {
    .schedule-table {
        table-layout: auto;
    }

    .schedule-table th:nth-child(1), .schedule-table td:nth-child(1),
    .schedule-table th:nth-child(2), .schedule-table td:nth-child(2),
    .schedule-table th:nth-child(3), .schedule-table td:nth-child(3),
    .schedule-table th:nth-child(4), .schedule-table td:nth-child(4),
    .schedule-table th:nth-child(6), .schedule-table td:nth-child(6) {
        width: auto;
    }
}

/* Projects overview table — same table-layout: fixed + one-absorbing-column
   technique as .schedule-table above, so the live search box doesn't make
   column widths jitter as rows are hidden/shown (table-layout: auto sizes
   columns off whichever rows currently happen to be visible, which visibly
   reflows as the filtered set changes). Name (column 1) is left unset to
   absorb whatever's left — it already has its own .truncate max-width/
   ellipsis regardless of the column's actual rendered width, so it's the
   safe column to leave flexible. Your role (non-staff only, always last)
   gets a width the same way .schedule-table's staff-only Edit column does. */
.projects-table {
    table-layout: fixed;
}

.projects-table th:nth-child(2), .projects-table td:nth-child(2) {
    width: 12%;
}

.projects-table th:nth-child(3), .projects-table td:nth-child(3) {
    width: 28%;
}

.projects-table th:nth-child(4), .projects-table td:nth-child(4) {
    width: 15%;
}

.projects-table th:nth-child(5), .projects-table td:nth-child(5) {
    width: 15%;
}

@media (max-width: 880px) {
    .projects-table {
        table-layout: auto;
    }

    .projects-table th:nth-child(2), .projects-table td:nth-child(2),
    .projects-table th:nth-child(3), .projects-table td:nth-child(3),
    .projects-table th:nth-child(4), .projects-table td:nth-child(4),
    .projects-table th:nth-child(5), .projects-table td:nth-child(5) {
        width: auto;
    }
}

/* Users overview table — same technique as .projects-table above (Name left
   unset to absorb the remainder, same mobile reset). */
.users-table {
    table-layout: fixed;
}

.users-table th:nth-child(2), .users-table td:nth-child(2) {
    width: 16%;
}

.users-table th:nth-child(3), .users-table td:nth-child(3) {
    width: 22%;
}

.users-table th:nth-child(4), .users-table td:nth-child(4) {
    width: 6%;
}

.users-table th:nth-child(5), .users-table td:nth-child(5) {
    width: 10%;
}

.users-table th:nth-child(6), .users-table td:nth-child(6) {
    width: 12%;
}

@media (max-width: 880px) {
    .users-table {
        table-layout: auto;
    }

    .users-table th:nth-child(2), .users-table td:nth-child(2),
    .users-table th:nth-child(3), .users-table td:nth-child(3),
    .users-table th:nth-child(4), .users-table td:nth-child(4),
    .users-table th:nth-child(5), .users-table td:nth-child(5),
    .users-table th:nth-child(6), .users-table td:nth-child(6) {
        width: auto;
    }
}

/* Companies overview table — same technique again. */
.companies-table {
    table-layout: fixed;
}

.companies-table th:nth-child(2), .companies-table td:nth-child(2) {
    width: 35%;
}

.companies-table th:nth-child(3), .companies-table td:nth-child(3) {
    width: 15%;
}

@media (max-width: 880px) {
    .companies-table {
        table-layout: auto;
    }

    .companies-table th:nth-child(2), .companies-table td:nth-child(2),
    .companies-table th:nth-child(3), .companies-table td:nth-child(3) {
        width: auto;
    }
}

/* Dashboard "Open invoices"/"Recent cron runs" — two structurally different
   tables stacked directly on the same page under plain auto layout, each
   sizing its own columns purely off its own row content, read as visually
   uneven side by side. Same fixed-width + one-absorbing-column technique as
   .schedule-table above (Billing profile / Summary are left unset and
   soak up whatever's left) — not literal alignment between the two tables'
   unrelated columns, just a stable, predictable width per table instead of
   content-driven jitter. Company (open-invoices-table's only staff-only,
   conditionally-present column) is appended last in the markup specifically
   so this CSS's nth-child selectors don't shift between the staff/non-staff
   render — same reasoning as .schedule-table's Edit column. */
.open-invoices-table,
.cron-runs-table {
    table-layout: fixed;
}

.open-invoices-table th:nth-child(1), .open-invoices-table td:nth-child(1) {
    width: 10%;
}

.open-invoices-table th:nth-child(3), .open-invoices-table td:nth-child(3) {
    width: 11%;
}

.open-invoices-table th:nth-child(4), .open-invoices-table td:nth-child(4) {
    width: 17%;
}

.open-invoices-table th:nth-child(5), .open-invoices-table td:nth-child(5) {
    width: 13%;
}

/* Remaining — new column, added between Amount and the staff-only Company
   column (which shifts from position 6 to 7 as a result). */
.open-invoices-table th:nth-child(6), .open-invoices-table td:nth-child(6) {
    width: 13%;
}

.open-invoices-table th:nth-child(7), .open-invoices-table td:nth-child(7) {
    width: 15%;
}

.cron-runs-table th:nth-child(1), .cron-runs-table td:nth-child(1) {
    width: 17%;
}

.cron-runs-table th:nth-child(2), .cron-runs-table td:nth-child(2) {
    width: 12%;
}

.cron-runs-table th:nth-child(3), .cron-runs-table td:nth-child(3) {
    width: 10%;
}

.open-invoices-table th,
.open-invoices-table td,
.cron-runs-table th,
.cron-runs-table td {
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Same reasoning as .schedule-table's mobile reset: below the hide-mobile
   breakpoint, some fixed-width columns vanish entirely and the rest
   wouldn't reliably reclaim the freed space — reverting to auto layout
   sidesteps that rather than fighting it. */
@media (max-width: 880px) {
    .open-invoices-table,
    .cron-runs-table {
        table-layout: auto;
    }

    .open-invoices-table th:nth-child(1), .open-invoices-table td:nth-child(1),
    .open-invoices-table th:nth-child(3), .open-invoices-table td:nth-child(3),
    .open-invoices-table th:nth-child(4), .open-invoices-table td:nth-child(4),
    .open-invoices-table th:nth-child(5), .open-invoices-table td:nth-child(5),
    .open-invoices-table th:nth-child(6), .open-invoices-table td:nth-child(6),
    .open-invoices-table th:nth-child(7), .open-invoices-table td:nth-child(7),
    .cron-runs-table th:nth-child(1), .cron-runs-table td:nth-child(1),
    .cron-runs-table th:nth-child(2), .cron-runs-table td:nth-child(2),
    .cron-runs-table th:nth-child(3), .cron-runs-table td:nth-child(3) {
        width: auto;
    }
}

/* Vendor profile Transactions tab — same fixed-width + one-absorbing-column
   technique as the tables above, so it reads consistently with the rest of
   the app rather than sizing its columns off whatever happens to be synced
   for a given vendor. Entity (the only free-text column — a legal entity's
   Dinero contact name) is left unset to absorb whatever's left; it already
   has its own .truncate max-width/ellipsis in the markup, so it's the safe
   column to leave flexible. */
.vendor-transactions-table {
    table-layout: fixed;
}

.vendor-transactions-table th:nth-child(1), .vendor-transactions-table td:nth-child(1) {
    width: 10%;
}

.vendor-transactions-table th:nth-child(3), .vendor-transactions-table td:nth-child(3) {
    width: 12%;
}

.vendor-transactions-table th:nth-child(4), .vendor-transactions-table td:nth-child(4) {
    width: 10%;
}

.vendor-transactions-table th:nth-child(5), .vendor-transactions-table td:nth-child(5),
.vendor-transactions-table th:nth-child(6), .vendor-transactions-table td:nth-child(6),
.vendor-transactions-table th:nth-child(7), .vendor-transactions-table td:nth-child(7) {
    width: 15%;
}

.vendor-transactions-table th,
.vendor-transactions-table td {
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Same reasoning as .schedule-table's mobile reset above: below the
   hide-mobile breakpoint, Due date and Incl. VAT vanish entirely and the
   rest wouldn't reliably reclaim the freed space — reverting to auto
   layout sidesteps that rather than fighting it. */
@media (max-width: 880px) {
    .vendor-transactions-table {
        table-layout: auto;
    }

    .vendor-transactions-table th:nth-child(1), .vendor-transactions-table td:nth-child(1),
    .vendor-transactions-table th:nth-child(3), .vendor-transactions-table td:nth-child(3),
    .vendor-transactions-table th:nth-child(4), .vendor-transactions-table td:nth-child(4),
    .vendor-transactions-table th:nth-child(5), .vendor-transactions-table td:nth-child(5),
    .vendor-transactions-table th:nth-child(6), .vendor-transactions-table td:nth-child(6),
    .vendor-transactions-table th:nth-child(7), .vendor-transactions-table td:nth-child(7) {
        width: auto;
    }
}

/* Same reasoning again: statistics_profitability.php's Profit/Profit-hr
   columns are .hide-mobile, so below the breakpoint the client-name
   column (never given an explicit width — it's already the fixed
   layout's own "remaining space" column) would otherwise stay squeezed
   into whatever room was left after the still-fixed-width number columns,
   rather than reclaiming the space Profit just vacated. */
@media (max-width: 880px) {
    .profitability-revenue-table,
    .profitability-hours-table {
        table-layout: auto;
    }

    .profitability-revenue-table th:nth-child(2), .profitability-revenue-table td:nth-child(2),
    .profitability-revenue-table th:nth-child(3), .profitability-revenue-table td:nth-child(3),
    .profitability-revenue-table th:nth-child(4), .profitability-revenue-table td:nth-child(4),
    .profitability-hours-table th:nth-child(2), .profitability-hours-table td:nth-child(2),
    .profitability-hours-table th:nth-child(3), .profitability-hours-table td:nth-child(3),
    .profitability-hours-table th:nth-child(4), .profitability-hours-table td:nth-child(4),
    .profitability-hours-table th:nth-child(5), .profitability-hours-table td:nth-child(5),
    .profitability-hours-table th:nth-child(6), .profitability-hours-table td:nth-child(6) {
        width: auto;
    }
}

/* Schedule item status (From/To/Type/Status/Location table) — plain text,
   no pill/background, matching .invoice-status's existing convention: only
   the exceptional state gets color. Planned and Unplanned both render as
   plain neutral text (no rule needed, inherits normal table-cell weight/
   color) — Brand Yellow as plain text was unreadable against Paper, so
   Unplanned no longer carries any color at all. Cancelled is the one state
   that still stands out. */
.schedule-status.is-cancelled {
    color: var(--color-red);
}

/* Utility: cap a cell's width and truncate overflowing text with an ellipsis
   — for columns (e.g. Name) that hold arbitrary-length user data, so one
   long value doesn't force the whole table wider/scrolling. Works at every
   width, on any table cell. */
.truncate {
    max-width: 12rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Utility: drop a table column below the tablet breakpoint (same threshold
   as the sidebar/topbar switch). Add to both the <th> and its matching <td>s. */
@media (max-width: 880px) {
    .hide-mobile {
        display: none;
    }
}

/* --- Page header / cards / flash --- */

.page-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    margin-bottom: 1.5rem;
    padding-bottom: 0.85rem;
    border-bottom: 1px solid var(--line);
}

.page-header .button,
.page-header .button-secondary {
    padding: 0.4rem 0.9rem;
    font-size: 0.9rem;
    /* Never shrink or wrap — .page-title is the flex item that gives way
       (it truncates with an ellipsis) when the row runs out of room. */
    flex-shrink: 0;
    white-space: nowrap;
}

/* --- Advisory notice (e.g. the contractor note on a project's Members tab) --- */

.notice-card {
    background: var(--surface-card);
    border: 1px solid var(--line);
    border-left: 3px solid var(--color-orange);
    border-radius: var(--radius);
    padding: 1.25rem 1.5rem;
    max-width: 40rem;
    margin-bottom: 1.5rem;
}

.notice-card h2 {
    font-size: 1rem;
    margin: 0 0 0.5em;
}

.notice-card p {
    color: var(--text-2);
    font-size: 0.9rem;
    margin: 0 0 0.75em;
}

.notice-card p:last-child {
    margin-bottom: 0;
}

/* --- Project sub-navigation (Overview / Members / ... tabs on a project) --- */

.project-subnav {
    display: flex;
    gap: 1.5rem;
    margin-bottom: 1.5rem;
    border-bottom: 1px solid var(--line);
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
}

.project-subnav a {
    padding: 0.6rem 0;
    text-decoration: none;
    color: var(--text-2);
    font-weight: 500;
    border-bottom: 2px solid transparent;
    margin-bottom: -1px;
    flex-shrink: 0;
    white-space: nowrap;
}

.project-subnav a:hover {
    color: var(--text-1);
}

.project-subnav a.is-active {
    color: var(--text-1);
    border-bottom-color: var(--color-orange);
}

.card-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
    gap: 1.25rem;
    margin-bottom: 2rem;
}

.card {
    display: block;
    background: var(--surface-card);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    padding: 1.25rem;
    text-decoration: none;
    color: var(--text-1);
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
    transition: border-color 0.15s ease, box-shadow 0.15s ease, transform 0.15s ease;
}

.card:hover {
    border-color: var(--color-orange);
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
    transform: translateY(-1px);
}

.card h2 {
    font-size: 1.1rem;
    margin-bottom: 0.25rem;
}

.card p {
    color: var(--text-2);
    margin: 0;
}

/* Schedule tab summary cards (planned/unplanned/cancelled hours) — same
   visual weight as the dashboard's .card/.card-grid, but not links, and the
   label uses --font-body explicitly instead of the global h1,h2,h3 rule so
   it doesn't pick up Playfair Display. */
.stat-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
    gap: 1.25rem;
    margin-bottom: 2rem;
}

/* Projects overview's status cards specifically (not .stat-grid in general
   — the Schedule tab's hours-by-status cards reuse that same base class and
   should stay visible on mobile) take up a lot of vertical space above an
   already-narrow mobile screen, on a page that now also has a search box
   and a table competing for the same room. */
@media (max-width: 880px) {
    .projects-stat-grid {
        display: none;
    }
}

/* Standalone search box above a .table-card (e.g. Projects overview) — same
   surface/border/radius treatment as .form-card/.table-card, so it reads as
   its own prominent full-width control rather than a bare input floating
   on the page. */
.search-card {
    background: var(--surface-card);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    padding: 1rem 1.25rem;
    margin-bottom: 1.5rem;
}

.search-input {
    display: block;
    width: 100%;
    font-size: 1rem;
    padding: 0.65rem 0.9rem;
}

.stat-card {
    background: var(--surface-card);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    padding: 1.25rem;
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
}

.stat-card-label {
    font-family: var(--font-body);
    font-size: 0.9rem;
    font-weight: 600;
    margin: 0 0 0.25rem;
    color: var(--text-2);
}

.stat-card-value {
    font-size: 1.6rem;
    font-weight: 700;
    color: var(--text-1);
    margin: 0;
}

/* Year-over-year delta on the Statistics page's stat cards — reuses the
   same green/red tokens invoice status badges already carry, rather than
   inventing a new up/down convention. */
.stat-delta-up {
    color: var(--color-green);
}

.stat-delta-down {
    color: var(--color-red);
}

/* Statistics page's year <select>, sitting in .page-header next to the
   title — no wrapper styling needed beyond killing the <form>'s default
   margin so it doesn't throw off .page-header's flex alignment. */
.statistics-year-form {
    margin: 0;
}

/* Statistics page charts — same surface/border/radius as .form-card/
   .table-card so a chart reads as one more card in the page, not a
   different kind of element. Chart.js needs an ancestor with a fixed
   height to size its responsive canvas into (maintainAspectRatio: false in
   statistics-charts.js), hence the separate .chart-canvas-wrap. */
.chart-card {
    background: var(--surface-card);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
    padding: 1.25rem;
    margin-bottom: 1.5rem;
}

.chart-canvas-wrap {
    position: relative;
    height: 18rem;
}

.chart-empty {
    color: var(--text-2);
    text-align: center;
    padding: 2rem 0;
    margin: 0;
}

/* Calendar page — a real 7-column month grid on desktop. Below the same
   880px breakpoint used everywhere else, the grid collapses into a plain
   stacked day-by-day agenda list (each .calendar-day already renders its
   own date, so no JS is needed to switch layouts), and empty days drop out
   entirely so the agenda doesn't scroll through blank days. */
.calendar-nav {
    display: flex;
    gap: 0.5rem;
    flex-shrink: 0;
}

.calendar-weekdays {
    display: grid;
    grid-template-columns: repeat(7, 1fr);
    gap: 0.75rem;
    margin-bottom: 0.5rem;
    font-size: 0.85rem;
    font-weight: 600;
    color: var(--text-2);
}

.calendar-grid {
    display: grid;
    grid-template-columns: repeat(7, 1fr);
    gap: 0.75rem;
    margin-bottom: 2rem;
}

.calendar-day {
    background: var(--surface-card);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    padding: 0.6rem;
    min-height: 7rem;
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
}

.calendar-day.is-outside-month {
    opacity: 0.45;
}

.calendar-day.is-today {
    border-color: var(--color-orange);
}

.calendar-day-date {
    margin: 0;
    font-size: 0.8rem;
    font-weight: 600;
    color: var(--text-2);
}

.calendar-item {
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
    background: var(--surface-page);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    padding: 0.4rem 0.5rem;
    text-decoration: none;
    color: var(--text-1);
    font-size: 0.8rem;
}

.calendar-item:hover {
    border-color: var(--color-orange);
}

/* Same accent technique as .notice-card — a card for an item the viewer is
   personally assigned to gets a left-edge stripe so their own commitments
   stand out from a project's other scheduled items at a glance. */
.calendar-item.is-mine {
    border-left: 3px solid var(--color-orange);
}

.calendar-item-project {
    font-weight: 600;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.calendar-item-time {
    color: var(--text-2);
}

/* Desktop always shows the full grid regardless of content, so this note
   (present in the markup only when the whole month has nothing scheduled)
   stays hidden there — it only makes sense once the mobile agenda below
   would otherwise render as a blank page. */
.calendar-empty-note {
    display: none;
}

/* Below the tablet breakpoint the month grid becomes a stacked agenda:
   weekday header hidden (each day already labels itself), grid switches to
   a plain block flow, and days with nothing scheduled disappear so the
   list only shows days that matter. Outside-month days are dropped
   entirely regardless of content — the leading/trailing days that pad the
   desktop grid into full weeks (e.g. late April showing before May 1)
   belong to the adjacent month's own agenda, not this one. */
@media (max-width: 880px) {
    .calendar-weekdays {
        display: none;
    }

    .calendar-grid {
        display: block;
    }

    .calendar-day {
        min-height: auto;
        margin-bottom: 0.75rem;
    }

    .calendar-day.is-empty,
    .calendar-day.is-outside-month {
        display: none;
    }

    .calendar-empty-note {
        display: block;
    }
}

/* --- Deliverable cards (Deliverables tab) — same visual weight as the
   dashboard's own .card (padding/border/shadow, hover-lift), but self-
   contained rather than composed with it: whether a card is clickable at
   all is conditional (a Planned/Cancelled deliverable, or one with no link
   yet, is never navigable), so the hover-lift has to be conditional too,
   which .card's own unconditional :hover can't do. Wider than the
   dashboard grid's tiles (roughly 3 per row on a normal desktop width) so
   a short name like "Video 1 (master)" has room to feel substantial. */

.deliverable-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(22rem, 1fr));
    gap: 1.25rem;
    margin-bottom: 1.5rem;
}

.deliverable-card {
    position: relative;
    background: var(--surface-card);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
    padding: 1.25rem;
    display: flex;
    flex-direction: column;
    gap: 0.6rem;
    transition: border-color 0.15s ease, box-shadow 0.15s ease, transform 0.15s ease;
}

.deliverable-card.is-clickable:hover {
    border-color: var(--color-orange);
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
    transform: translateY(-1px);
}

.deliverable-card.is-cancelled {
    opacity: 0.55;
}

/* Same Playfair Display treatment as every other card/page title, via the
   global h1,h2,h3 rule — just sized and margined to fit this card.
   padding-right clears the corner-positioned Edit link so a longer name
   wraps before reaching it instead of running underneath. */
.deliverable-card-name {
    font-size: 1.1rem;
    margin: 0;
    padding-right: 2.75rem;
}

/* Stretched-link: extends the name link's clickable area to the whole
   card (not just the text), so clicking anywhere on a navigable card
   works — while Edit, a real separate element positioned above it via
   z-index, stays independently clickable. */
.deliverable-card-link {
    color: inherit;
    text-decoration: none;
}

.deliverable-card-link::after {
    content: "";
    position: absolute;
    inset: 0;
}

.deliverable-card-status {
    margin: 0;
    font-size: 0.9rem;
    font-weight: 600;
}

.deliverable-card-status.is-neutral {
    color: var(--text-2);
}

.deliverable-card-status.is-active {
    color: var(--color-orange);
}

.deliverable-card-status.is-done {
    color: var(--color-green);
}

/* Invoice status label — plain/neutral (inherits body text weight/color)
   unless overdue or partially paid, the two states that warrant drawing
   the eye. Partial reuses the same orange as .deliverable-card-status's
   "in-progress, not final" state; red stays reserved for overdue/negative. */
.invoice-status.is-overdue {
    color: var(--color-red);
}

.invoice-status.is-partial {
    color: var(--color-orange);
}

/* Compact read-only key/value summary (e.g. the invoice attach preview) —
   term and value side by side on one row, unlike .form's stacked label/
   input layout, since there's nothing to fill in here. */
.summary-list {
    display: grid;
    grid-template-columns: max-content 1fr;
    gap: 0.6rem 1.5rem;
    margin: 0 0 1.5rem;
}

.summary-list dt {
    color: var(--text-2);
    font-weight: 500;
    font-size: 0.9rem;
}

.summary-list dd {
    margin: 0;
    color: var(--text-1);
    font-variant-numeric: tabular-nums;
}

/* .debug-raw: staging-only raw API response dump (project_invoice_new.php).
   .code-block: same look, reused for permanent product documentation (e.g.
   the sample curl requests on views/api_docs.php) rather than debug output.
   Both read as console/code, distinct from the surrounding .form-card's
   surface. */
.debug-raw,
.code-block {
    background: var(--surface-page);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    padding: 1rem;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 0.85rem;
    white-space: pre-wrap;
    word-break: break-word;
    overflow-x: auto;
}

/* API docs page's Endpoints tab (views/api_docs_endpoints.php) — full-width,
   unlike the narrow .form-card/.form-card-wide settings-form boxes used by
   its Introduction/Authentication tabs, since this reads like a classical
   API reference rather than an account setting. Same surface/border/
   radius/shadow recipe as .table-card, but for a block of prose/code
   rather than a table — no overflow/scroll rules needed. */
.content-card {
    background: var(--surface-card);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
    padding: 1.5rem;
    margin-bottom: 1.5rem;
}

.content-card h2 {
    margin-top: 0;
}

/* Parameters table (views/_api_docs_parameters_table.php) — .table's
   default td { white-space: nowrap } suits short tabular values, but a
   parameter's description is prose; force it to wrap instead of pushing
   .table-card into horizontal scroll for an ordinary sentence. */
.parameters-table td {
    white-space: normal;
}

/* A plain text link, same recipe as .link-button — never a bordered
   button — pinned to the card's top-right corner. Positioned above the
   stretched-link overlay via z-index, so it stays independently clickable
   even on a navigable card. */
.deliverable-card-edit {
    position: absolute;
    top: 1.25rem;
    right: 1.25rem;
    z-index: 1;
    font-family: var(--font-body);
    font-size: 0.8rem;
    font-weight: 400;
    color: var(--text-2);
    text-decoration: underline;
}

.deliverable-card-edit:hover {
    color: var(--text-1);
}

.flash {
    border-radius: var(--radius);
    padding: 0.75rem 1rem;
    margin-bottom: 1.25rem;
    font-weight: 500;
}

.flash-success {
    background: color-mix(in srgb, var(--color-green) 15%, transparent);
    color: var(--color-green);
}

.flash-error {
    background: color-mix(in srgb, var(--color-red) 15%, transparent);
    color: var(--color-red);
}

/* Staff-authored service announcement banners — same tinted-background
   recipe as .flash above, one per active announcement, stacked in <main>
   above the flash message. */
.announcement-banner {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 1rem;
    padding: 0.85rem 1rem;
    border-radius: var(--radius);
    margin-bottom: 0.75rem;
}

.announcement-banner-notice {
    background: color-mix(in srgb, var(--color-blue) 15%, transparent);
    color: var(--color-blue);
}

/* Ink, not yellow — yellow text on a yellow-tinted background fails
   contrast, same reasoning as .env-banner's yellow background with ink
   text. */
.announcement-banner-warning {
    background: color-mix(in srgb, var(--color-yellow) 20%, transparent);
    color: var(--color-ink);
}

.announcement-banner-alert {
    background: color-mix(in srgb, var(--color-red) 15%, transparent);
    color: var(--color-red);
}

.announcement-banner-details {
    flex: 1;
    min-width: 0;
}

/* <summary> — expanded by default ([open] is in the markup); app.js drops
   the [open] attribute at load time when the viewport is under 880px, so
   the description/link collapse behind a tap on mobile. This is real
   native <details> state, not a CSS trick forcing collapsed content to
   render — a closed <details>'s content isn't reliably overridable with a
   plain `display` rule (some browsers hide it via an internal mechanism a
   simple override doesn't defeat), so the toggle is driven by the actual
   [open] attribute instead. Without JS it just stays expanded everywhere,
   same graceful-degradation approach as the rest of app.js. */
.announcement-banner-headline {
    display: list-item;
    cursor: pointer;
    font-weight: 600;
}

.announcement-banner-body {
    margin-top: 0.35rem;
}

.announcement-banner-description {
    white-space: pre-line;
}

/* font-weight/text-decoration matches the app's standard text-link recipe
   (.link-button/.deliverable-card-edit) — color deliberately stays
   inherited from the severity class rather than switching to --text-2, the
   same reasoning .flash-success/.flash-error use their own color for all
   text on a tinted surface rather than the neutral page-text tokens. */
.announcement-banner-link {
    font-weight: 500;
    text-decoration: underline;
    opacity: 0.85;
    transition: opacity 0.15s ease;
}

.announcement-banner-link:hover,
.announcement-banner-link:focus-visible {
    opacity: 1;
}

.announcement-banner-dismiss {
    background: none;
    border: none;
    font-size: 1.15rem;
    line-height: 1;
    cursor: pointer;
    color: inherit;
    opacity: 0.6;
    padding: 0.25rem 0.4rem;
    transition: opacity 0.15s ease;
}

.announcement-banner-dismiss:hover,
.announcement-banner-dismiss:focus-visible {
    opacity: 1;
}

/* Severity label on the staff admin list (announcements_index.php) — a
   small colored dot rather than colored text, since --color-yellow reads
   fine as a background tint (see .announcement-banner-warning above) but
   fails contrast as plain text on the table's light surface. */
.severity-text {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
}

.severity-text::before {
    content: '';
    width: 0.5rem;
    height: 0.5rem;
    border-radius: 50%;
    flex-shrink: 0;
}

.severity-notice::before {
    background: var(--color-blue);
}

.severity-warning::before {
    background: var(--color-yellow);
}

.severity-alert::before {
    background: var(--color-red);
}

@media (max-width: 880px) {
    /* flex-direction: column stacks children top-to-bottom in DOM order —
       align-self: flex-end alone only nudges the dismiss button
       horizontally, and since it's the last child it still rendered below
       the content instead of pinned to the corner. Pulling it out of flow
       with position: absolute guarantees the top-right placement
       regardless of how tall the (possibly-expanded) content above it is. */
    .announcement-banner {
        position: relative;
        flex-direction: column;
    }

    .announcement-banner-details {
        padding-right: 1.75rem; /* clears the absolutely positioned button so the headline text doesn't run under it */
    }

    .announcement-banner-dismiss {
        position: absolute;
        top: 0.75rem;
        right: 0.85rem;
    }
}

/* Above the mobile breakpoint app.js never removes [open], so there's
   nothing to toggle — this just strips the now-pointless affordance (the
   marker, the pointer cursor, click-ability) so the always-expanded
   summary doesn't look interactive when it isn't. */
@media (min-width: 881px) {
    .announcement-banner-headline {
        cursor: default;
        pointer-events: none;
        list-style: none; /* removes the marker in Firefox/Chrome */
    }

    .announcement-banner-headline::-webkit-details-marker {
        display: none; /* Safari/older WebKit use this instead of list-style */
    }
}
