/* ============================================================
   Orindesk — Project & Financial Management
   Design system
   ============================================================ */

:root {
    --bg: #f4f6fa;
    --surface: #ffffff;
    --surface-soft: #f0f4fa;
    --ink: #101828;
    --ink-soft: #344054;
    --muted: #667085;
    --line: #e4e9f1;
    --line-strong: #d0d8e4;
    --accent: #1660d6;
    --accent-strong: #0c47a8;
    --accent-soft: rgba(22, 96, 214, 0.10);
    --chart-wash: rgba(22, 96, 214, 0.045);
    --warning: #b7791f;
    --warning-soft: rgba(217, 158, 46, 0.14);
    --danger: #d0343b;
    --danger-soft: rgba(224, 76, 83, 0.12);
    --positive: #147a4d;
    --positive-soft: rgba(24, 145, 93, 0.12);
    /* The rail follows the theme.
     *
     * It used to be the same near-black in both, which on a light page was a
     * slab of night down one side of a white document — the one part of the
     * app that did not do what the theme switch said. It is a soft blue-grey
     * now: deeper than the page behind the cards so it still reads as a rail
     * rather than as more page, and nowhere near white.
     *
     * Every colour the sidebar uses is a token, including the ones that were
     * written into the rules as rgba(255,255,255,…) — a white wash is only a
     * highlight on something dark, and on a light rail it is invisible. */
    --sidebar-bg: #e7ecf5;
    /* Near enough to the page's own heading ink to read as type rather than as
       chrome. A menu is read at a glance from across a desk, so it went from
       6.5:1 to 8.8 and then to 12.2 — the last step on purpose, because it was
       still the faintest thing on screen at 8.8 and a menu has no business
       being that. */
    --sidebar-ink: #1e2942;
    --sidebar-strong: #101828;
    /* Still a step quieter than the links — a section label is a heading for
       them, not one of them — but nowhere near a grey wash. */
    --sidebar-section: #3d4759;
    --sidebar-line: #d3dbe9;
    --sidebar-hover: rgba(12, 17, 29, 0.055);
    --sidebar-active-bg: rgba(72, 138, 235, 0.20);
    --sidebar-active-ink: #0c47a8;
    --sidebar-active-icon: #1660d6;
    --shadow-sm: 0 1px 2px rgba(16, 24, 40, 0.06);
    --shadow: 0 4px 16px rgba(16, 24, 40, 0.08);
    --shadow-lg: 0 16px 48px rgba(16, 24, 40, 0.18);
    --radius: 10px;
    --radius-sm: 7px;
    --font: "Inter", ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
    --mono: ui-monospace, "SF Mono", "Cascadia Code", Menlo, monospace;
}

html[data-theme="dark"] {
    --bg: #0b1017;
    --surface: #121924;
    --surface-soft: #1a2331;
    --ink: #ecf2fb;
    --ink-soft: #c6d2e2;
    --muted: #8b99ad;
    --line: #223047;
    --line-strong: #2e3f5b;
    --accent: #4c95f5;
    --accent-strong: #83b7fa;
    --accent-soft: rgba(76, 149, 245, 0.16);
    --chart-wash: rgba(76, 149, 245, 0.07);
    --warning: #e4b04a;
    --warning-soft: rgba(228, 176, 74, 0.15);
    --danger: #f0666c;
    --danger-soft: rgba(240, 102, 108, 0.14);
    --positive: #4ecb8d;
    --positive-soft: rgba(78, 203, 141, 0.14);
    --sidebar-bg: #070b12;
    --sidebar-ink: #8d9ab0;
    --sidebar-strong: #e6ecf5;
    /* Was rgba(168, 179, 199, 0.55), which composites to 3.5:1 — quieter than
       the light labels were before build 90 raised them, and the same complaint
       waiting to happen the first time somebody works in the dark. Solid, and
       5.2:1: a step below the links at 6.9 rather than a whisper under them. */
    --sidebar-section: #76839a;
    --sidebar-line: rgba(255, 255, 255, 0.08);
    --sidebar-hover: rgba(255, 255, 255, 0.06);
    /* Stated here rather than inherited: the light values above are no longer
       the dark ones, which is what they used to be for both themes. */
    --sidebar-active-bg: rgba(72, 138, 235, 0.18);
    --sidebar-active-ink: #ffffff;
    --sidebar-active-icon: #7fb0f7;
    --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4);
    --shadow: 0 4px 16px rgba(0, 0, 0, 0.35);
    --shadow-lg: 0 16px 48px rgba(0, 0, 0, 0.5);
}

* { box-sizing: border-box; }

/* No control may push the page sideways, whatever its intrinsic width is
   (date inputs on iOS are the usual offender). */
input, select, textarea { max-width: 100%; }

html { min-height: 100%; }

body {
    margin: 0;
    min-height: 100vh;
    background: var(--bg);
    color: var(--ink);
    font-family: var(--font);
    font-size: 14px;
    line-height: 1.5;
    overflow-x: hidden;
    -webkit-font-smoothing: antialiased;
}

a { color: inherit; }
button, input, select, textarea { font: inherit; color: inherit; }
h1, h2, h3, h4 { margin: 0; font-weight: 650; letter-spacing: -0.01em; }
p { margin: 0.25rem 0; }

.icon { flex-shrink: 0; vertical-align: -3px; }

/* ---------- Shell ---------- */

.app-shell {
    display: grid;
    grid-template-columns: 248px minmax(0, 1fr);
    min-height: 100vh;
}

.sidebar {
    position: sticky;
    top: 0;
    height: 100vh;
    display: flex;
    flex-direction: column;
    background: var(--sidebar-bg);
    color: var(--sidebar-ink);
    /* A light rail against a light page needs an edge; a dark one never did,
       and its token is a white wash faint enough to do no harm. */
    border-right: 1px solid var(--sidebar-line);
    padding: 18px 14px;
    overflow-y: auto;
    scrollbar-width: thin;
}

.brand {
    display: flex;
    align-items: center;
    gap: 10px;
    text-decoration: none;
    padding: 4px 8px 16px;
    border-bottom: 1px solid var(--sidebar-line);
    margin-bottom: 12px;
}

.brand-mark {
    width: 36px;
    height: 36px;
    display: grid;
    place-items: center;
    border-radius: 9px;
    background: linear-gradient(135deg, #2f7bf0, #144ba8);
    color: #fff;
    font-weight: 800;
    font-size: 14px;
    letter-spacing: 0.02em;
}

/* An uploaded logo sits where the initials square would be. It keeps its own
   proportions and is bounded on both axes, so a wide banner and a square badge
   both land tidily without the sidebar having to know which one it got. */
.brand-logo {
    max-width: 100%;
    width: auto;
    height: auto;
    min-width: 0;
    flex: 0 1 auto;
    object-fit: contain;
    display: block;
}

/* A square-ish logo sits beside the name, like the initials square did. */
.brand.has-logo .brand-logo { max-width: 104px; }

/* A banner logo takes the full width with the name underneath — squeezing one
   into the badge slot left it unreadable and wrapped the name over three lines. */
.brand.wide-logo,
.auth-brand.wide-logo { flex-direction: column; align-items: flex-start; gap: 8px; }
.brand.wide-logo .brand-logo { max-width: 100%; max-height: 46px; }
.auth-brand.wide-logo .brand-logo { max-width: 240px; max-height: 56px; }

.brand-logo + .brand-text { min-width: 0; }

.brand-text { display: flex; flex-direction: column; line-height: 1.25; }
.brand-text strong { color: var(--sidebar-strong); font-size: 14.5px; }
.brand-text small { font-size: 11px; color: var(--sidebar-ink); }

.nav-menu { flex: 1; display: flex; flex-direction: column; gap: 2px; }

.nav-section {
    margin: 14px 8px 4px;
    font-size: 10.5px;
    font-weight: 700;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    color: var(--sidebar-section);
}

/* Air above each group but the first, which already has the brand block's rule.
   Twelve links in five groups run together as one column of blue-grey text and
   the headings alone were not breaking it. A hairline broke it and read as five
   panels rather than one menu; the same distance of nothing does the work and
   leaves the rail quiet. */
.nav-section:not(:first-child) { margin-top: 26px; }

.nav-link {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 10px;
    border-radius: 8px;
    text-decoration: none;
    font-size: 13.5px;
    font-weight: 500;
    color: var(--sidebar-ink);
    transition: background 0.15s, color 0.15s;
}

.nav-link:hover { background: var(--sidebar-hover); color: var(--sidebar-strong); }
.nav-link.active { background: var(--sidebar-active-bg); color: var(--sidebar-active-ink); }
.nav-link.active .icon { color: var(--sidebar-active-icon); }

.sidebar-footer {
    padding-top: 12px;
    border-top: 1px solid var(--sidebar-line);
}

.sidebar-footer a {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 8px 10px;
    border-radius: 8px;
    color: var(--sidebar-ink);
    text-decoration: none;
    font-size: 13px;
}

.sidebar-footer a:hover { background: var(--sidebar-hover); color: var(--sidebar-strong); }

.sidebar-backdrop {
    display: none;
    position: fixed;
    inset: 0;
    background: rgba(6, 10, 16, 0.55);
    z-index: 40;
}

/* ---------- Topbar ---------- */

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

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

/* The public demo's strip. Above the sticky topbar and not sticky itself, so
   it introduces the place once and then gets out of the way when you scroll —
   a banner that follows you down every page reads as a warning. */
.demo-bar {
    padding: 9px 26px;
    background: var(--accent-soft);
    border-bottom: 1px solid var(--line);
    color: var(--ink-soft);
    font-size: 13px;
    line-height: 1.5;
}
.demo-bar strong { color: var(--ink); }
@media (max-width: 720px) { .demo-bar { padding: 9px 16px; } }

.topbar-title { flex: 1; min-width: 0; }
.topbar-title h1 { font-size: 18px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.topbar-subtitle { margin: 0; font-size: 12.5px; color: var(--muted); }

/* Breadcrumb — the trail above a detail page's title. Deliberately quiet: it is
   a way back, not a heading, so it sits smaller and dimmer than the subtitle
   below it and only the links carry any weight on hover. */
.breadcrumb {
    display: flex;
    align-items: center;
    gap: 6px;
    margin-bottom: 2px;
    font-size: 11.5px;
    line-height: 1.4;
    min-width: 0;
}
.breadcrumb .crumb {
    color: var(--muted);
    text-decoration: none;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 220px;
}
.breadcrumb a.crumb:hover { color: var(--accent); text-decoration: underline; }
.breadcrumb .crumb-sep { color: var(--muted); opacity: 0.5; }

/* A link that lives inside secondary text — a client's name under a project
   title, say. It has to stay quiet enough not to compete with the heading, so
   it only reveals itself as a link on hover. */
.subtle-link { color: inherit; text-decoration: none; }
.subtle-link:hover { color: var(--accent); text-decoration: underline; }

@media (max-width: 720px) {
    /* Only the immediate parent survives — that is the one a back tap wants. */
    .breadcrumb .crumb:not(:last-child), .breadcrumb .crumb-sep { display: none; }
    .breadcrumb .crumb { max-width: 60vw; }
}

.topbar-actions { display: flex; align-items: center; gap: 10px; flex: none; }
/* wrap: a page with several header actions (Settings has the most) ran them off
   the right edge on a phone and took the whole document into horizontal scroll. */
.topbar-buttons { display: flex; align-items: center; gap: 8px; flex: none; flex-wrap: wrap; }

.sidebar-toggle {
    display: none;
    flex-direction: column;
    gap: 4px;
    background: none;
    border: 1px solid var(--line);
    border-radius: 8px;
    padding: 9px 8px;
    cursor: pointer;
}

.sidebar-toggle span { width: 16px; height: 2px; border-radius: 2px; background: var(--ink-soft); }

.theme-toggle {
    border: 1px solid var(--line);
    background: var(--surface);
    border-radius: 8px;
    width: 34px;
    height: 34px;
    cursor: pointer;
    display: grid;
    place-items: center;
    color: var(--muted);
}

.theme-toggle svg { display: block; }
html[data-theme="light"] .theme-icon.sun { display: none; }
html[data-theme="dark"] .theme-icon.moon { display: none; }

.user-pill { display: flex; align-items: center; gap: 9px; }

.avatar {
    width: 32px;
    height: 32px;
    display: grid;
    place-items: center;
    border-radius: 50%;
    background: var(--accent-soft);
    color: var(--accent);
    font-size: 12px;
    font-weight: 700;
}

.user-meta { display: flex; flex-direction: column; line-height: 1.2; }
.user-meta span { font-size: 13px; font-weight: 600; }
.user-meta small { font-size: 11px; color: var(--muted); }

.page-body { padding: 22px 26px 40px; display: flex; flex-direction: column; gap: 18px; }

/* ---------- Flash ---------- */

.flash {
    margin: 14px 26px -6px;
    padding: 10px 14px;
    border-radius: var(--radius-sm);
    font-size: 13.5px;
    font-weight: 500;
    border: 1px solid transparent;
}

.flash.success { background: var(--positive-soft); color: var(--positive); border-color: color-mix(in srgb, var(--positive) 30%, transparent); }
.flash.error { background: var(--danger-soft); color: var(--danger); border-color: color-mix(in srgb, var(--danger) 30%, transparent); }

/* ---------- Buttons ---------- */

.button {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    padding: 8px 14px;
    border-radius: 8px;
    border: 1px solid transparent;
    background: var(--accent);
    color: #fff;
    font-size: 13px;
    font-weight: 600;
    text-decoration: none;
    cursor: pointer;
    transition: background 0.15s, border-color 0.15s, color 0.15s, box-shadow 0.15s;
    white-space: nowrap;
}

.button:hover { background: var(--accent-strong); box-shadow: 0 4px 14px -4px var(--accent); }

.button.subtle {
    background: var(--surface);
    border-color: var(--line-strong);
    color: var(--ink-soft);
}

.button.subtle:hover { border-color: var(--accent); color: var(--accent); background: var(--surface); }

.button.danger { background: transparent; border-color: color-mix(in srgb, var(--danger) 40%, transparent); color: var(--danger); }
.button.danger:hover { background: var(--danger-soft); }

.button.small { padding: 5px 10px; font-size: 12px; border-radius: 7px; }
.button.ghost { background: transparent; color: var(--accent); border: none; padding: 4px 6px; }
.button.ghost:hover { text-decoration: underline; background: transparent; }

.icon-button {
    display: inline-grid;
    place-items: center;
    width: 28px;
    height: 28px;
    border-radius: 7px;
    border: none;
    background: transparent;
    color: var(--muted);
    cursor: pointer;
}

.icon-button:hover { background: var(--surface-soft); color: var(--ink); }
.icon-button.danger:hover { background: var(--danger-soft); color: var(--danger); }

/* ---------- Metric cards ---------- */

.metric-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(178px, 1fr));
    gap: 12px;
}

.metric-card {
    position: relative;
    overflow: hidden;
    background:
        radial-gradient(120% 150% at 100% 0%, var(--kpi-tint, transparent) 0%, transparent 60%),
        var(--surface);
    border: 1px solid var(--line);
    border-radius: 12px;
    box-shadow: var(--shadow-sm);
    padding: 14px 16px;
    display: flex;
    flex-direction: column;
    gap: 3px;
    min-width: 0;
    transition: transform 0.18s ease, box-shadow 0.18s ease, border-color 0.18s ease;
}

.metric-card::before {
    content: '';
    position: absolute;
    inset: 0 0 auto 0;
    height: 2px;
    background: linear-gradient(90deg, transparent, var(--kpi-accent, var(--accent)), transparent);
    opacity: 0.7;
}

.metric-card:hover {
    transform: translateY(-2px);
    box-shadow: var(--shadow);
    border-color: var(--kpi-accent, var(--accent));
}

.metric-label {
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--muted);
    line-height: 1.35;
}

.metric-value { font-size: 21px; font-weight: 700; letter-spacing: -0.02em; font-variant-numeric: tabular-nums; }
.metric-sub { font-size: 12px; color: var(--muted); }

.metric-card { --kpi-accent: var(--accent); --kpi-tint: var(--accent-soft); }
.metric-card.tone-positive { --kpi-accent: var(--positive); --kpi-tint: var(--positive-soft); }
.metric-card.tone-negative { --kpi-accent: var(--danger); --kpi-tint: var(--danger-soft); }
.metric-card.tone-warning { --kpi-accent: var(--warning); --kpi-tint: var(--warning-soft); }

.metric-card.tone-positive .metric-value { color: var(--positive); }
.metric-card.tone-negative .metric-value { color: var(--danger); }
.metric-card.tone-warning .metric-value { color: var(--warning); }

.positive { color: var(--positive); }
.negative { color: var(--danger); }
.warning-text { color: var(--warning); }
.muted { color: var(--muted); }
.num { font-variant-numeric: tabular-nums; }

/* ---------- Headline KPI cards ---------- */

.kpi-grid {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 14px;
    margin-bottom: 16px;
}

.kpi-card {
    position: relative;
    overflow: hidden;
    background:
        radial-gradient(120% 140% at 100% 0%, var(--kpi-tint, transparent) 0%, transparent 58%),
        var(--surface);
    border: 1px solid var(--line);
    border-radius: 14px;
    box-shadow: var(--shadow-sm);
    padding: 16px 18px 18px;
    display: flex;
    flex-direction: column;
    gap: 6px;
    min-width: 0;
    transition: transform 0.18s ease, box-shadow 0.18s ease, border-color 0.18s ease;
}

/* Hairline of accent across the top — the whole "glow" in one element. */
.kpi-card::before {
    content: '';
    position: absolute;
    inset: 0 0 auto 0;
    height: 2px;
    background: linear-gradient(90deg, transparent, var(--kpi-accent, var(--accent)), transparent);
    opacity: 0.85;
}

.kpi-card:hover {
    transform: translateY(-2px);
    border-color: var(--kpi-accent, var(--accent));
    box-shadow: var(--shadow), 0 0 0 3px var(--kpi-tint, transparent);
}

.kpi-head { display: flex; align-items: center; gap: 9px; min-width: 0; }

.kpi-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 30px;
    height: 30px;
    border-radius: 9px;
    flex: none;
    color: var(--kpi-accent, var(--accent));
    background: var(--kpi-tint, var(--accent-soft));
    box-shadow: 0 0 14px -2px var(--kpi-tint, transparent);
}

.kpi-label {
    font-size: 11.5px;
    font-weight: 700;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--muted);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.kpi-value {
    font-size: 27px;
    font-weight: 700;
    letter-spacing: -0.02em;
    line-height: 1.15;
    font-variant-numeric: tabular-nums;
    color: var(--kpi-value-ink, var(--ink));
}

.kpi-sub { font-size: 12px; color: var(--muted); }

.kpi-card { --kpi-accent: var(--accent); --kpi-tint: var(--accent-soft); }
.kpi-card.tone-positive { --kpi-accent: var(--positive); --kpi-tint: var(--positive-soft); --kpi-value-ink: var(--positive); }
.kpi-card.tone-negative { --kpi-accent: var(--danger); --kpi-tint: var(--danger-soft); --kpi-value-ink: var(--danger); }
.kpi-card.tone-warning { --kpi-accent: var(--warning); --kpi-tint: var(--warning-soft); --kpi-value-ink: var(--warning); }

.trend-tag {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 5px 11px;
    border-radius: 999px;
    font-size: 12px;
    font-weight: 650;
    font-variant-numeric: tabular-nums;
    border: 1px solid transparent;
}

.trend-tag.good { color: var(--positive); background: var(--positive-soft); border-color: var(--positive-soft); }
.trend-tag.bad { color: var(--danger); background: var(--danger-soft); border-color: var(--danger-soft); }

/* ---------- Panels & grids ---------- */

.panel {
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: 14px;
    box-shadow: var(--shadow-sm);
    padding: 17px 19px;
    min-width: 0;
}

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

.panel-header h2 { font-size: 15px; }
.panel-header .header-side { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }

.eyebrow {
    margin: 0 0 2px;
    font-size: 10.5px;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--muted);
}

.grid-2 { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; }
.grid-3 { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 16px; }
.grid-sidebar { display: grid; grid-template-columns: minmax(0, 2fr) minmax(280px, 1fr); gap: 16px; }

/* Chart panels: a faint accent wash behind the plot so the glowing series
   sits on something, rather than floating on flat white. */
.panel.chart-panel {
    position: relative;
    overflow: hidden;
    background:
        radial-gradient(90% 120% at 15% 0%, var(--chart-wash) 0%, transparent 62%),
        var(--surface);
}

.chart-box { position: relative; height: 260px; }
.chart-box.tall { height: 320px; }
.chart-box.short { height: 200px; }

/*
 * The dashboard doughnut sits beside a taller leaderboard, so the grid stretches
 * its panel to match. That matching is wanted — two panels in a row should end
 * on the same line — but the height it hands over used to pile up as a dead band
 * under the chart.
 *
 * The chart box takes that space instead of leaving it at the bottom, up to a
 * ceiling. Without the ceiling the ring inflates to fill whatever the neighbour
 * happens to be; with it, the chart absorbs most of the difference and whatever
 * is left over is split evenly above and below by the auto margins.
 *
 * Opt-in via .chart-fill rather than by position in the grid. The Budget tab's
 * chart panel is also a direct child of a sidebar grid, but its box height is
 * computed from the number of bars — clamping that one squashes the bars.
 */
.panel.chart-fill { display: flex; flex-direction: column; }

.panel.chart-fill > .chart-box {
    flex: 1 1 auto;
    max-height: 400px;
    margin-block: auto;
}

/* ---------- Tables ---------- */

.table-wrap { overflow-x: auto; }

table { width: 100%; border-collapse: collapse; font-size: 13.5px; }

thead th {
    text-align: left;
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--muted);
    padding: 9px 11px;
    background: var(--surface-soft);
    border-bottom: 1px solid var(--line);
    white-space: nowrap;
}

thead th:first-child { border-top-left-radius: 9px; border-bottom-left-radius: 9px; }
thead th:last-child { border-top-right-radius: 9px; border-bottom-right-radius: 9px; }

tbody td {
    padding: 10px 11px;
    border-bottom: 1px solid var(--line);
    vertical-align: middle;
}

tbody tr { transition: background 0.12s ease; }
tbody tr:hover { background: var(--surface-soft); }
tbody tr:last-child td { border-bottom: none; }

td.right, th.right { text-align: right; }
td.num { font-variant-numeric: tabular-nums; }
td.actions { text-align: right; white-space: nowrap; }

tfoot td {
    padding: 9px 10px;
    font-weight: 700;
    border-top: 2px solid var(--line-strong);
    font-variant-numeric: tabular-nums;
}

tr.row-clickable { cursor: pointer; }
tr.row-clickable:hover { background: var(--surface-soft); }

.table-link { font-weight: 600; text-decoration: none; color: var(--ink); display: block; }
.table-link:hover { color: var(--accent); }
.table-subtext { display: block; font-size: 12px; color: var(--muted); font-weight: 400; }

.empty-cell { text-align: center; color: var(--muted); padding: 22px 10px !important; }

.empty-state {
    text-align: center;
    color: var(--muted);
    padding: 32px 16px;
}

.empty-state p { font-weight: 600; color: var(--ink-soft); }

/* ---------- Badges ---------- */

.badge {
    display: inline-flex;
    align-items: center;
    padding: 2.5px 9px;
    border-radius: 99px;
    font-size: 11.5px;
    font-weight: 600;
    white-space: nowrap;
}

.badge-green { background: var(--positive-soft); color: var(--positive); }
.badge-red { background: var(--danger-soft); color: var(--danger); }
.badge-amber { background: var(--warning-soft); color: var(--warning); }
.badge-blue { background: var(--accent-soft); color: var(--accent); }
.badge-gray { background: var(--surface-soft); color: var(--muted); }
/* Purple carries "the customer has opened it" — a real event, but not yet an
   answer, so it needs to read as distinct from both sent and accepted rather
   than as a shade of either. Defined here with its own values because the theme
   had no purple; both themes get one that passes against its own background. */
.badge-purple { background: rgba(139, 92, 246, 0.14); color: #7c4ddb; }
[data-theme="dark"] .badge-purple { background: rgba(167, 139, 250, 0.18); color: #c4b5fd; }

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

.progress {
    height: 7px;
    border-radius: 99px;
    background: var(--surface-soft);
    overflow: hidden;
    min-width: 70px;
}

.progress-fill { height: 100%; border-radius: 99px; }
.progress-fill.tone-ok { background: var(--accent); }
.progress-fill.tone-warn { background: var(--warning); }
.progress-fill.tone-danger { background: var(--danger); }

/* ---------- Forms ---------- */

.form-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
    gap: 12px 14px;
}

.form-grid .full { grid-column: 1 / -1; }

/* Two fields that belong together, side by side whatever the grid around them
   is doing. A first name and a last name are one name; in a narrow modal the
   outer grid collapses to a single column and splitting them across two full
   rows makes them read as two unrelated questions. Half each, always — until
   the screen is genuinely too narrow for two boxes. */
.field-pair {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 12px 14px;
    grid-column: 1 / -1;
}
@media (max-width: 380px) { .field-pair { grid-template-columns: 1fr; } }

/* A pair whose boxes hold long values — an email address next to a phone
   number — needs to stack much earlier than a first/last name does, or the
   address is cut off at "contact@yourcomp" on a phone. */
@media (max-width: 620px) { .field-pair.stack-sm { grid-template-columns: 1fr; } }

/* One wide field with two short ones beside it: half, quarter, quarter.
   A document's title with the two dates that qualify it — the title needs the
   room and a date needs almost none, so giving each a row of its own pushed the
   work itself below the fold for the sake of two boxes with white space beside
   them. Explicit fractions rather than auto-fill, because the proportions are
   the point.

   The title takes the whole row before the dates do: below 780px there is no
   longer space for a readable title and two dates on one line, and a title cut
   in half is worse than a line break. Everything stacks below 480. */
.field-lead {
    grid-column: 1 / -1;
    display: grid;
    grid-template-columns: 2fr 1fr 1fr;
    gap: 12px 14px;
}

@media (max-width: 780px) {
    .field-lead { grid-template-columns: 1fr 1fr; }
    .field-lead > .field:first-child { grid-column: 1 / -1; }
}

@media (max-width: 480px) { .field-lead { grid-template-columns: 1fr; } }

/* Every settings tab is a stack of panels with 16px between them — and until
   now every tab said so itself, with an inline margin on each panel after the
   first. A tab that forgot left its panels welded together (System and Document
   design both did), and nothing on the page said which was the mistake.

   Said once here instead. It is the same 16px the inline styles use, so the
   ones still carrying it are unaffected — a margin does not add to itself — and
   it reaches the case those styles could not: a <form> holding several panels
   is one child of this box, so the gap between the panel above it and the first
   panel inside it was nobody's job. */
.settings-body > * + * { margin-top: 16px; }

/* The Save for a page whose fields span more than one panel. It sits between
   the panels, not inside the last one, so it reads as belonging to all of them. */
.settings-save {
    align-items: center;
    gap: 14px;
    margin-top: 16px;
    padding: 0 2px;
    flex-wrap: wrap;
}

/* What is going to be printed at the head of a document, shown on the page it
   affects while the fields themselves live on Company. Read-only on purpose:
   two places to edit the same address is how the two stop matching. */
.letterhead-preview {
    display: grid;
    grid-template-columns: 110px 1fr;
    gap: 8px 16px;
    padding: 14px 16px;
    border: 1px solid var(--line);
    border-radius: 10px;
    background: var(--surface-soft, var(--bg));
    font-size: 13.5px;
    line-height: 1.5;
}
.letterhead-row { display: contents; }
.letterhead-label { color: var(--muted); font-size: 12px; font-weight: 600; padding-top: 1px; }
.letterhead-value { color: var(--ink); min-width: 0; overflow-wrap: anywhere; }
@media (max-width: 520px) {
    .letterhead-preview { grid-template-columns: 1fr; gap: 2px 0; }
    .letterhead-row + .letterhead-row .letterhead-label { margin-top: 10px; }
}

.field { display: flex; flex-direction: column; gap: 4px; min-width: 0; }

.field label { font-size: 12px; font-weight: 600; color: var(--ink-soft); }
.field label .req { color: var(--danger); }

/* An input sitting in a table cell rather than in a .field — the overhead
   tiers are the only place this happens. Without it the box keeps the browser
   default white while the text inherits the page's light colour, which in dark
   mode is white on white. */
.cell-input,
.field input,
.field select,
.field textarea,
/* The address grid lays its own controls out, so it sits outside .field — but
   they are the same controls and must look it, or they render as unstyled
   browser inputs: white boxes in a dark modal. */
.address-grid input,
.address-grid select {
    width: 100%;
    padding: 8px 10px;
    border-radius: 8px;
    border: 1px solid var(--line-strong);
    background: var(--surface);
    color: var(--ink);
    font-size: 13.5px;
    transition: border-color 0.15s, box-shadow 0.15s;
}

.field textarea { resize: vertical; min-height: 70px; }

.field input:focus,
.field select:focus,
.field textarea:focus,
.address-grid input:focus,
.address-grid select:focus {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 3px var(--accent-soft);
}

/* ---------- Date and time boxes on iOS ----------
   Safari on iPhone does not render <input type=date|time> the way it renders a
   text box. It shrink-wraps the value, centres it, and gives the control its own
   inner height — so in a column of ordinary fields the date sits in the middle
   of a box that is a different size from the ones above and below it. Every rule
   here undoes one part of that.

   Applied to the inputs directly rather than through .field, because dates also
   appear in filter bars, table cells and the to-do list, and the complaint was
   "on all pages". Rules later in this file that target one of those places still
   win, being more specific and further down. */
input[type="date"],
input[type="time"],
input[type="datetime-local"],
input[type="month"] {
    -webkit-appearance: none;
    appearance: none;
    text-align: left;
    /* The same box the text inputs beside them make. Stated outright because an
       empty date field on iOS is shorter than a filled one, and a row that
       changes height the moment you pick a date is the thing that reads as
       broken. */
    min-height: 38px;
    line-height: 1.45;
}

/* The value lives in its own shadow element, which ignores text-align set on
   the input. This is the rule that actually moves it left. */
input[type="date"]::-webkit-date-and-time-value,
input[type="time"]::-webkit-date-and-time-value,
input[type="datetime-local"]::-webkit-date-and-time-value,
input[type="month"]::-webkit-date-and-time-value {
    text-align: left;
    margin: 0;
}

/* Both of these carry padding of their own on top of the input's, which is what
   made a date box two pixels taller than the text box beside it — small on a
   desktop, and the whole difference in a stacked column on a phone. */
input[type="date"]::-webkit-datetime-edit,
input[type="time"]::-webkit-datetime-edit,
input[type="datetime-local"]::-webkit-datetime-edit {
    padding: 0;
    line-height: 1.45;
}

input[type="date"]::-webkit-datetime-edit-fields-wrapper,
input[type="time"]::-webkit-datetime-edit-fields-wrapper,
input[type="datetime-local"]::-webkit-datetime-edit-fields-wrapper {
    padding: 0;
}

/* The little calendar and clock buttons are drawn near-black by the browser,
   which disappears against a dark field. */
input[type="date"]::-webkit-calendar-picker-indicator,
input[type="time"]::-webkit-calendar-picker-indicator,
input[type="datetime-local"]::-webkit-calendar-picker-indicator {
    opacity: 0.55;
    cursor: pointer;
}
input[type="date"]:hover::-webkit-calendar-picker-indicator,
input[type="time"]:hover::-webkit-calendar-picker-indicator { opacity: 0.9; }

html[data-theme="dark"] input[type="date"]::-webkit-calendar-picker-indicator,
html[data-theme="dark"] input[type="time"]::-webkit-calendar-picker-indicator,
html[data-theme="dark"] input[type="datetime-local"]::-webkit-calendar-picker-indicator {
    filter: invert(1);
}

.field .hint { font-size: 11.5px; color: var(--muted); }

/* ---------- The running note log on a document ----------
   A stack of dated entries, each with a rule above it rather than a card of its
   own: fifteen cards down a page reads as fifteen separate things, and this is
   one log. */
.note-log { display: flex; flex-direction: column; }

.note-entry { padding: 12px 0; border-top: 1px solid var(--line); }
.note-entry:first-child { border-top: 0; padding-top: 2px; }

.note-meta {
    display: flex;
    align-items: baseline;
    gap: 8px;
    font-size: 12.5px;
    margin-bottom: 4px;
}
.note-meta strong { font-size: 13px; }
/* The delete button sits at the far right of the line the date is on, so a
   column of notes has one column of buttons rather than one per paragraph. */
.note-meta form { margin-left: auto; line-height: 1; }

.note-body {
    margin: 0;
    font-size: 13.5px;
    line-height: 1.6;
    color: var(--ink-soft);
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}

/* ---------- The company's own list of appointment types ----------
   A column of one-line rows with a remove button on the right. Narrow on
   purpose: these are two or three words, and a box the width of the panel
   invites a sentence. */
.type-list { display: flex; flex-direction: column; gap: 8px; max-width: 460px; }

.type-row { display: flex; gap: 8px; align-items: center; }

.type-row input[name="type_label[]"] {
    flex: 1 1 auto;
    min-width: 0;
    padding: 8px 10px;
    border-radius: 8px;
    border: 1px solid var(--line-strong);
    background: var(--surface);
    color: var(--ink);
    font-size: 13.5px;
}

.type-row input[name="type_label[]"]:focus {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 3px var(--accent-soft);
}

.type-row .icon-button { flex: none; }

.check-field { display: flex; align-items: center; gap: 8px; font-size: 13px; font-weight: 500; color: var(--ink-soft); }
.check-field input { width: 15px; height: 15px; accent-color: var(--accent); }

/* wrap: the Jobber panel carries four buttons in one row, which ran off the
   right edge of a phone and put the whole page into horizontal scroll. */
/* Buttons live on the right, everywhere: the end of a form, the foot of a modal,
   under a set of line items. The eye finishes reading a form at its right edge
   and the thing to press is already there, rather than back at the margin it
   started from. */
.form-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
    margin-top: 14px;
    align-items: center;
    justify-content: flex-end;
}

.filter-bar {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
    align-items: flex-end;
}

.filter-bar .field { min-width: 130px; flex: 0 1 auto; }
.filter-bar .field.grow { flex: 1 1 180px; }

/* ---------- Tabs ---------- */

.tabs {
    display: flex;
    gap: 2px;
    border-bottom: 1px solid var(--line-strong);
    overflow-x: auto;
    scrollbar-width: none;
}

.tabs a {
    padding: 9px 14px;
    font-size: 13.5px;
    font-weight: 600;
    color: var(--muted);
    text-decoration: none;
    border-bottom: 2px solid transparent;
    margin-bottom: -1px;
    white-space: nowrap;
}

/* Quiet control parked at the end of the tab row (restore dismissed alerts).
   Deliberately understated — it should read as a footnote, not a tab. */
.tabs-aside {
    margin-left: auto;
    display: flex;
    align-items: center;
    /* Stays pinned to the right edge when the tab row has to scroll, so it is
       never stranded off-screen on a narrower laptop. */
    position: sticky;
    right: 0;
    padding-left: 22px;
    background: linear-gradient(90deg, transparent 0, var(--bg) 20px);
}

.tabs-aside button {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    background: none;
    border: none;
    padding: 9px 2px;
    font-size: 12px;
    font-weight: 500;
    color: var(--muted);
    opacity: 0.7;
    cursor: pointer;
    white-space: nowrap;
    transition: color 0.15s ease, opacity 0.15s ease;
}

.tabs-aside button:hover,
.tabs-aside button:focus-visible { color: var(--accent); opacity: 1; }

.tabs a { transition: color 0.15s ease, border-color 0.15s ease; }
.tabs a:hover { color: var(--ink); }
.tabs a.active {
    color: var(--accent);
    border-bottom-color: var(--accent);
    text-shadow: 0 0 18px var(--accent-soft);
}
.tabs a .count {
    background: var(--surface-soft);
    color: var(--muted);
    border-radius: 99px;
    font-size: 11px;
    padding: 1px 7px;
    margin-left: 5px;
}

/* ---------- Modals ---------- */

.modal-backdrop {
    position: fixed;
    inset: 0;
    background: rgba(9, 13, 20, 0.6);
    display: none;
    align-items: flex-start;
    justify-content: center;
    padding: 4vh 16px;
    z-index: 60;
    overflow-y: auto;
}

.modal-backdrop.open { display: flex; }

.modal {
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: 14px;
    box-shadow: var(--shadow-lg);
    width: 100%;
    max-width: 640px;
    padding: 20px 22px;
    animation: modal-in 0.16s ease-out;
}

.modal.wide { max-width: 860px; }
.modal.narrow { max-width: 440px; }

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

.modal-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 14px; }
.modal-header h3 { font-size: 16px; }

/* ---------- Alerts / attention list ---------- */

.attention-list { display: flex; flex-direction: column; gap: 8px; }

.attention-item {
    display: flex;
    gap: 10px;
    align-items: flex-start;
    padding: 11px 13px;
    border-radius: 10px;
    border: 1px solid var(--line);
    background:
        linear-gradient(90deg, var(--attn-tint, transparent) 0%, transparent 34%),
        var(--surface);
    font-size: 13px;
    text-decoration: none;
    transition: transform 0.15s ease, border-color 0.15s ease;
}

.attention-item:hover { border-color: var(--line-strong); transform: translateX(2px); }
.attention-item .icon { margin-top: 1px; }
.attention-item.warn { border-left: 3px solid var(--warning); --attn-tint: var(--warning-soft); }
.attention-item.warn .icon { color: var(--warning); }
.attention-item.bad { border-left: 3px solid var(--danger); --attn-tint: var(--danger-soft); }
.attention-item.bad .icon { color: var(--danger); }
.attention-item > div { flex: 1; min-width: 0; }
.attention-dismiss { flex: none; margin: -3px -4px 0 0; }
.attention-dismiss .icon-button { width: 24px; height: 24px; opacity: 0; transition: opacity 0.15s ease; }
.attention-item:hover .attention-dismiss .icon-button,
.attention-dismiss .icon-button:focus-visible { opacity: 1; }

/* Touch devices have no hover, so the control has to stay visible. */
@media (hover: none) {
    .attention-dismiss .icon-button { opacity: 1; }
}

.attention-item strong { font-weight: 650; }
.attention-item small { color: var(--muted); display: block; }

/* ---------- To-do board ---------- */

.todo-board { margin-bottom: 2px; }

.todo-count { font-weight: 650; }
.todo-count.over { color: var(--danger); }
.todo-count.today { color: var(--warning); }
.todo-count.ok   { color: var(--positive); }

/* Collapsible quick-add ---------------------------------------------------- */

.todo-add-wrap { margin-bottom: 14px; }

.todo-add-trigger {
    display: flex;
    align-items: center;
    gap: 8px;
    width: 100%;
    padding: 9px 13px;
    border-radius: 11px;
    border: 1px dashed var(--line-strong);
    background: transparent;
    color: var(--muted);
    font-size: 13px;
    font-weight: 600;
    cursor: pointer;
    transition: color 0.16s ease, border-color 0.16s ease, background 0.16s ease;
}

.todo-add-trigger:hover,
.todo-add-trigger:focus-visible {
    color: var(--accent);
    border-color: var(--accent);
    background: var(--accent-soft);
}

.todo-add-plus {
    display: inline-flex;
    transition: transform 0.22s cubic-bezier(0.4, 0, 0.2, 1);
}

/* Open state: the + becomes an ×, and the dashed outline turns solid. */
.todo-add-wrap.open .todo-add-plus { transform: rotate(135deg); }
.todo-add-wrap.open .todo-add-trigger {
    color: var(--accent);
    border-style: solid;
    border-color: var(--line-strong);
    border-bottom-left-radius: 0;
    border-bottom-right-radius: 0;
    border-bottom-color: transparent;
}

/* 0fr -> 1fr animates to the content's natural height, with no magic numbers
   and no measuring in JS. */
.todo-add-panel {
    display: grid;
    grid-template-rows: 0fr;
    transition: grid-template-rows 0.24s cubic-bezier(0.4, 0, 0.2, 1);
}
.todo-add-wrap.open .todo-add-panel { grid-template-rows: 1fr; }
.todo-add-panel > div { overflow: hidden; }

.todo-add-panel .todo-add {
    opacity: 0;
    transform: translateY(-4px);
    transition: opacity 0.18s ease, transform 0.18s ease;
}
.todo-add-wrap.open .todo-add-panel .todo-add { opacity: 1; transform: none; }

@media (prefers-reduced-motion: reduce) {
    .todo-add-panel, .todo-add-panel .todo-add, .todo-add-plus { transition: none; }
}

.todo-add {
    display: flex;
    gap: 8px;
    align-items: center;
    flex-wrap: wrap;
    padding: 12px;
    border-radius: 0 0 11px 11px;
    border: 1px solid var(--line-strong);
    border-top: none;
    background: var(--surface-soft);
}

.todo-add input[name="title"] { flex: 1 1 260px; }
.todo-add input, .todo-add select {
    padding: 8px 10px;
    border-radius: 8px;
    border: 1px solid var(--line-strong);
    background: var(--surface);
    font-size: 13px;
    min-width: 0;
}
.todo-add input:focus, .todo-add select:focus { outline: 2px solid var(--accent); outline-offset: 1px; }

.todo-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
    gap: 11px;
}

.todo-card {
    position: relative;
    overflow: hidden;
    border-radius: 12px;
    border: 1px solid var(--line);
    border-left: 4px solid var(--todo-accent);
    background:
        linear-gradient(120deg, var(--todo-tint) 0%, transparent 55%),
        var(--surface);
    padding: 11px 12px;
    transition: border-color 0.16s ease, box-shadow 0.16s ease, opacity 0.16s ease;
    --todo-accent: var(--muted);
    --todo-tint: transparent;
}

.todo-card:focus-within { box-shadow: 0 0 0 3px var(--todo-tint); border-color: var(--todo-accent); }

.todo-card.urgency-over  { --todo-accent: var(--danger);  --todo-tint: var(--danger-soft); }
.todo-card.urgency-today { --todo-accent: var(--warning); --todo-tint: var(--warning-soft); }
.todo-card.urgency-ok   { --todo-accent: var(--positive); --todo-tint: var(--positive-soft); }
.todo-card.urgency-none { --todo-accent: var(--line-strong); --todo-tint: transparent; }
.todo-card.urgency-done,
.todo-card.is-done { --todo-accent: var(--line-strong); --todo-tint: transparent; opacity: 0.55; }
.todo-card.is-done .todo-title { text-decoration: line-through; color: var(--muted); }
.todo-card.is-done:hover { opacity: 1; }

.todo-head { display: flex; align-items: flex-start; gap: 8px; }

/* Title and the person it belongs to share a line: "Call the county — Ed Laux".
   The name is deliberately quiet — attribution, not a heading. */
.todo-titlewrap {
    flex: 1;
    min-width: 0;
    display: flex;
    align-items: baseline;
    gap: 4px;
    flex-wrap: wrap;
    padding-top: 2px;
}

.todo-who {
    flex: none;
    display: inline-flex;
    align-items: baseline;
    gap: 3px;
    font-size: 11.5px;
    color: var(--muted);
    opacity: 0.75;
    max-width: 100%;
    transition: opacity 0.14s ease;
}

.todo-card:hover .todo-who, .todo-who:focus-within { opacity: 1; }
.todo-dash { opacity: 0.6; }

/* The assignee stays editable, but reads as plain faded text until touched. */
.todo-who select {
    border: none;
    background: transparent;
    color: inherit;
    font-size: 11.5px;
    font-family: inherit;
    padding: 1px 2px;
    border-radius: 5px;
    max-width: 120px;
    cursor: pointer;
    appearance: none;
    text-overflow: ellipsis;
}
.todo-who select:hover { background: var(--surface-soft); }
.todo-who select:focus { outline: 2px solid var(--accent); background: var(--surface); color: var(--ink); }

.todo-check { position: relative; flex: none; width: 19px; height: 19px; cursor: pointer; }
/* The input sits above its own styled box so the click target is the input
   itself, not the decoration painted over it. */
.todo-check input { position: absolute; inset: 0; z-index: 1; opacity: 0; width: 100%; height: 100%; margin: 0; cursor: pointer; }
.todo-check span {
    display: block;
    width: 19px; height: 19px;
    border-radius: 6px;
    border: 2px solid var(--line-strong);
    transition: all 0.14s ease;
}
.todo-check input:checked + span {
    background: var(--positive);
    border-color: var(--positive);
    box-shadow: 0 0 12px -2px var(--positive);
}
.todo-check input:checked + span::after {
    content: '';
    position: absolute;
    left: 6px; top: 2px;
    width: 4px; height: 9px;
    border: solid #fff;
    border-width: 0 2px 2px 0;
    transform: rotate(45deg);
}

/* A textarea, not an input, so a long to-do wraps instead of being clipped.
   It grows to fit its content and never shows a scrollbar or resize grip. */
.todo-title {
    /* Keep a readable width for the title; the assignee beside it wraps to the
       next line when there is not room for both. Without the floor, a long name
       squeezed the title to ~85px and broke words mid-syllable. */
    flex: 1 1 auto;
    min-width: 130px;
    border: none;
    background: transparent;
    padding: 3px 4px;
    border-radius: 6px;
    font-size: 13.5px;
    font-weight: 600;
    line-height: 1.35;
    color: var(--ink);
    font-family: inherit;
    resize: none;
    overflow: hidden;
}
.todo-title:focus { outline: none; background: var(--surface-soft); }

/* The note button is always there — a card with a note shows a filled dot so
   you can see there is something to read without opening it. */
.todo-note-toggle { position: relative; width: 24px; height: 24px; flex: none; opacity: 0.45; transition: opacity 0.14s ease, color 0.14s ease; }
.todo-card:hover .todo-note-toggle { opacity: 0.8; }
.todo-note-toggle:hover, .todo-note-toggle[aria-expanded="true"] { opacity: 1; color: var(--accent); }

.todo-note-toggle.has-note { opacity: 0.9; color: var(--todo-accent); }
.todo-note-toggle.has-note::after {
    content: '';
    position: absolute;
    top: 2px; right: 2px;
    width: 6px; height: 6px;
    border-radius: 99px;
    background: var(--todo-accent);
    box-shadow: 0 0 6px -1px var(--todo-accent);
}
/* Both row buttons are always present at a low opacity — they used to fade in
   on hover, which left a hole in the layout and made the row jump. */
.todo-head .icon-button.danger {
    width: 24px; height: 24px; flex: none;
    opacity: 0.3;
    transition: opacity 0.16s ease, color 0.16s ease, background 0.16s ease;
}
.todo-card:hover .icon-button.danger { opacity: 0.55; }
.todo-head .icon-button.danger:hover,
.todo-head .icon-button.danger:focus-visible { opacity: 1; }

@media (hover: none) {
    .todo-note-toggle, .todo-head .icon-button.danger { opacity: 0.6; }
}

.todo-meta {
    display: flex;
    align-items: center;
    gap: 7px;
    flex-wrap: wrap;
    margin: 7px 0 0 27px;
    font-size: 12px;
}

/* The date chip is a pill like the project picker beside it. It used to be bare
   text 18px tall, which is far under a finger's width — a tap a few pixels high
   or low hit nothing at all, which is what made changing a date feel random. */
.todo-due {
    position: relative;
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 4px 10px;
    border-radius: 99px;
    background: var(--todo-tint, var(--surface-soft));
    color: var(--todo-accent);
    font-weight: 650;
    cursor: pointer;
    touch-action: manipulation;
}

/* The invisible input covers the pill and must paint above the label text,
   otherwise the tap lands on the text and nothing opens. The negative inset
   pushes the touch area past the pill's edges — about 44px tall in all, the
   size a fingertip actually needs — without making the pill itself any bigger.
   The horizontal reach stays inside the 7px gap to the project picker. */
.todo-due input[type="date"] {
    position: absolute;
    top: -9px;
    left: -3px;
    z-index: 1;
    opacity: 0;
    /* An input is a replaced element, so with width:auto it keeps its own
       intrinsic width and ignores the right offset — leaving a dead strip down
       one edge. The size is spelled out instead. */
    width: calc(100% + 6px);
    height: calc(100% + 18px);
    /* The global "inputs never exceed their box" rule would otherwise clip the
       overhang straight back off and leave a dead strip down the right edge. */
    max-width: none;
    border: none; cursor: pointer;
    -webkit-appearance: none;
    touch-action: manipulation;
}
.todo-due:hover .todo-due-label { text-decoration: underline; text-underline-offset: 2px; }
.todo-due-label { white-space: nowrap; }
.todo-card.urgency-none .todo-due { color: var(--muted); font-weight: 500; background: var(--surface-soft); }

.todo-project {
    border: none;
    background: var(--surface-soft);
    color: var(--muted);
    border-radius: 99px;
    padding: 3px 9px;
    font-size: 11.5px;
    max-width: 150px;
}
.todo-project:focus { outline: 2px solid var(--accent); }

.todo-note { margin: 8px 0 0 27px; }
.todo-note textarea {
    width: 100%;
    border: 1px solid var(--line);
    background: var(--surface-soft);
    border-radius: 8px;
    padding: 7px 9px;
    font-size: 12.5px;
    resize: vertical;
}

.todo-archive { margin-top: 18px; padding-top: 14px; border-top: 1px solid var(--line); }
.todo-archive h3 { font-size: 13px; margin-bottom: 10px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.06em; }
.todo-archive-list { display: flex; flex-direction: column; gap: 6px; }
.todo-archive-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 9px 11px;
    border-radius: 9px;
    background: var(--surface-soft);
    font-size: 13px;
}
.todo-archive-row strong { font-weight: 600; }
.todo-archive-row small { display: block; font-size: 11.5px; margin-top: 1px; }
.todo-archive-actions { flex: none; display: flex; align-items: center; gap: 6px; }

/* ---------- Working-day picker ---------- */

.daypicker { display: flex; gap: 6px; flex-wrap: wrap; }

.daypicker-day { position: relative; cursor: pointer; }
.daypicker-day input { position: absolute; opacity: 0; pointer-events: none; }

.daypicker-day span {
    display: inline-grid;
    place-items: center;
    min-width: 46px;
    padding: 8px 6px;
    border-radius: 9px;
    border: 1px solid var(--line-strong);
    background: var(--surface);
    color: var(--muted);
    font-size: 12px;
    font-weight: 650;
    transition: all 0.14s ease;
}

.daypicker-day input:checked + span {
    border-color: var(--accent);
    background: var(--accent-soft);
    color: var(--accent);
    box-shadow: 0 0 14px -4px var(--accent);
}

.daypicker-day input:focus-visible + span { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ---------- Schedule: month grid ---------- */

.cal-grid {
    display: grid;
    grid-template-columns: repeat(7, minmax(0, 1fr));
    gap: 3px;
}

.cal-dow {
    text-align: center;
    font-size: 10.5px;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--muted);
    padding-bottom: 5px;
}

.cal-day {
    position: relative;
    aspect-ratio: 1 / 1;
    min-height: 38px;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 3px;
    border-radius: 9px;
    border: 1px solid transparent;
    text-decoration: none;
    color: var(--ink-soft);
    font-variant-numeric: tabular-nums;
    transition: background 0.13s ease, border-color 0.13s ease, color 0.13s ease;
}

.cal-day:hover { background: var(--surface-soft); }
.cal-day.muted-day { color: var(--muted); opacity: 0.45; }

/* Non-working day: faint stripe so it reads as "off" without shouting. */
.cal-day.off-day {
    color: var(--muted);
    background: repeating-linear-gradient(
        135deg,
        transparent 0 5px,
        var(--surface-soft) 5px 6px
    );
}

.cal-day.off-day:hover { background: var(--surface-soft); }
.cal-day.off-day.selected { color: var(--accent); }

.mark.off { background: var(--muted); opacity: 0.55; box-shadow: none; }
.cal-day.today { border-color: var(--line-strong); font-weight: 700; }

.cal-day.selected {
    background: var(--accent-soft);
    border-color: var(--accent);
    color: var(--accent);
    font-weight: 700;
    box-shadow: 0 0 16px -4px var(--accent);
}

.cal-num { font-size: 13px; line-height: 1; }

/* A dot per item, so three appointments read as busier than one. */
.cal-marks {
    display: flex;
    gap: 2.5px;
    height: 5px;
    align-items: center;
    justify-content: center;
    max-width: 100%;
    overflow: hidden;
}

.mark { width: 4.5px; height: 4.5px; border-radius: 99px; display: inline-block; flex: none; }

/* Shown when a day has more than three of a kind. */
.mark.more {
    width: auto; height: auto;
    font-size: 9px; font-weight: 800; line-height: 1;
    color: var(--muted);
    font-style: normal;
    margin-left: 1px;
}
.mark.appt { background: var(--accent); box-shadow: 0 0 6px -1px var(--accent); }
.mark.proj { background: var(--warning); box-shadow: 0 0 6px -1px var(--warning); }

.cal-legend {
    display: flex;
    gap: 14px;
    margin-top: 12px;
    padding-top: 11px;
    border-top: 1px solid var(--line);
    font-size: 11.5px;
    color: var(--muted);
}

.cal-legend span { display: inline-flex; align-items: center; gap: 6px; }

/* ---------- Schedule: day agenda ---------- */

.agenda { display: flex; flex-direction: column; gap: 8px; }

.agenda-row {
    display: flex;
    gap: 13px;
    padding: 11px 13px;
    border-radius: 11px;
    border: 1px solid var(--line);
    border-left: 3px solid var(--agenda-accent, var(--accent));
    background:
        linear-gradient(90deg, var(--agenda-tint, transparent) 0%, transparent 38%),
        var(--surface);
    transition: box-shadow 140ms ease, border-color 140ms ease, transform 140ms ease;
}

/* One colour per type, carried by --agenda-accent through the left edge, the
   dot, the tint and the hover halo, so a card reads as its type at a glance. */
.agenda-row.type-appointment { --agenda-accent: #06b6d4; --agenda-tint: rgba(6, 182, 212, 0.09); }
.agenda-row.type-residential { --agenda-accent: #3b82f6; --agenda-tint: rgba(59, 130, 246, 0.09); }
.agenda-row.type-commercial  { --agenda-accent: #f59e0b; --agenda-tint: rgba(245, 158, 11, 0.09); }
.agenda-row.type-inspection  { --agenda-accent: #8b5cf6; --agenda-tint: rgba(139, 92, 246, 0.09); }
.agenda-row.type-estimate    { --agenda-accent: #10b981; --agenda-tint: rgba(16, 185, 129, 0.09); }
.agenda-row.project          { --agenda-accent: var(--warning); --agenda-tint: var(--warning-soft); }

/* A soft halo in the card's own colour. color-mix keeps one rule working for
   every type; browsers without it simply get the border change. */
.agenda-row:hover {
    border-color: var(--agenda-accent, var(--accent));
    box-shadow: 0 0 0 1px var(--agenda-accent, var(--accent)),
                0 4px 18px -6px var(--agenda-accent, var(--accent));
}

@supports (color: color-mix(in srgb, red 50%, transparent)) {
    .agenda-row:hover {
        border-color: color-mix(in srgb, var(--agenda-accent, var(--accent)) 55%, transparent);
        box-shadow: 0 0 0 1px color-mix(in srgb, var(--agenda-accent, var(--accent)) 28%, transparent),
                    0 6px 22px -8px color-mix(in srgb, var(--agenda-accent, var(--accent)) 70%, transparent);
    }
}

/* The quick-view popup wears the same type colour as the card it came from. */
.modal .type-dot {
    width: 8px; height: 8px; border-radius: 99px; flex: none;
    background: var(--agenda-accent, var(--accent));
    box-shadow: 0 0 9px -1px var(--agenda-accent, var(--accent));
}
.modal.type-appointment { --agenda-accent: #06b6d4; }
.modal.type-residential { --agenda-accent: #3b82f6; }
.modal.type-commercial  { --agenda-accent: #f59e0b; }
.modal.type-inspection  { --agenda-accent: #8b5cf6; }
.modal.type-estimate    { --agenda-accent: #10b981; }
.modal-header h3 { display: flex; align-items: center; gap: 8px; }

.agenda-row.clickable { cursor: pointer; }
.agenda-row.clickable:hover { transform: translateY(-1px); }

@media (prefers-reduced-motion: reduce) {
    .agenda-row { transition: none; }
    .agenda-row.clickable:hover { transform: none; }
}

.agenda-time {
    flex: none;
    width: 74px;
    display: flex;
    flex-direction: column;
    gap: 1px;
    font-variant-numeric: tabular-nums;
}

.agenda-time strong { font-size: 13px; }
.agenda-time small { font-size: 11.5px; color: var(--muted); }

.agenda-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.agenda-title { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; font-size: 13.5px; }
.agenda-title .type-dot {
    width: 7px; height: 7px; border-radius: 99px; flex: none;
    background: var(--agenda-accent);
    box-shadow: 0 0 8px -1px var(--agenda-accent);
}
.agenda-meta { font-size: 12px; color: var(--muted); }
.agenda-meta a { color: var(--muted); text-decoration: none; }
.agenda-meta a:hover { color: var(--accent); text-decoration: underline; }
.agenda-note { font-size: 12.5px; color: var(--ink-soft); white-space: pre-wrap; margin-top: 2px; }
.agenda-actions { flex: none; display: flex; align-items: flex-start; gap: 2px; }

/* On a phone the time sits on its own line with the buttons, and the details
   take the full width underneath. Laying the three side by side left the body
   about 90px wide, which wrapped every line to two or three. */
@media (max-width: 560px) {
    .agenda-row { flex-wrap: wrap; row-gap: 7px; }
    .agenda-time {
        order: 1;
        width: auto;
        flex: 0 1 auto;
        flex-direction: row;
        gap: 7px;
        align-items: baseline;
    }
    .agenda-actions { order: 2; margin-left: auto; }
    .agenda-body { order: 3; flex: 1 1 100%; }
}

/* ---------- Leaderboard ---------- */

.leader-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 3px;
}

.leader-row a {
    display: flex;
    align-items: flex-start;
    gap: 11px;
    padding: 9px 10px;
    border-radius: 10px;
    text-decoration: none;
    transition: background 0.15s ease;
}

.leader-row a:hover { background: var(--surface-soft); }

.leader-rank {
    flex: none;
    width: 22px;
    height: 22px;
    margin-top: 1px;
    border-radius: 7px;
    display: grid;
    place-items: center;
    font-size: 11.5px;
    font-weight: 700;
    color: var(--muted);
    background: var(--surface-soft);
}

/* The top three get the accent treatment, gold-ish first. */
.leader-row:nth-child(1) .leader-rank {
    color: #fff;
    background: linear-gradient(135deg, #f0b429, #e08c1a);
    box-shadow: 0 0 14px -2px rgba(240, 180, 41, 0.75);
}
.leader-row:nth-child(2) .leader-rank { color: var(--ink); background: var(--line-strong); }
.leader-row:nth-child(3) .leader-rank { color: var(--ink-soft); background: var(--line); }

.leader-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 5px; }

.leader-top { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; }

.leader-name {
    font-size: 13.5px;
    font-weight: 650;
    color: var(--ink);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.leader-value {
    flex: none;
    font-size: 13.5px;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    color: var(--positive);
}

.leader-row.down .leader-value { color: var(--danger); }

.leader-bar {
    display: block;
    height: 5px;
    border-radius: 99px;
    background: var(--surface-soft);
    overflow: hidden;
}

.leader-bar i {
    display: block;
    height: 100%;
    border-radius: 99px;
    background: linear-gradient(90deg, color-mix(in srgb, var(--positive) 45%, transparent), var(--positive));
    box-shadow: 0 0 12px -1px var(--positive);
}

.leader-row.down .leader-bar i {
    background: linear-gradient(90deg, color-mix(in srgb, var(--danger) 45%, transparent), var(--danger));
    box-shadow: 0 0 12px -1px var(--danger);
}

.leader-meta { font-size: 11.5px; color: var(--muted); }

/* ---------- Documents grid ---------- */

.doc-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(170px, 1fr));
    gap: 10px;
}

.doc-card {
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    padding: 11px 12px;
    background: var(--surface);
    display: flex;
    flex-direction: column;
    gap: 4px;
    text-decoration: none;
    position: relative;
    transition: border-color 0.15s, box-shadow 0.15s;
}

.doc-card:hover { border-color: var(--accent); box-shadow: var(--shadow-sm); }
.doc-card .doc-icon { font-size: 20px; }
.doc-card .doc-name {
    font-size: 12.5px;
    font-weight: 600;
    word-break: break-word;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}
.doc-card small { font-size: 11px; color: var(--muted); }
.doc-card form { position: absolute; top: 6px; right: 6px; }

.doc-thumb {
    width: 100%;
    height: 92px;
    object-fit: cover;
    border-radius: 6px;
    background: var(--surface-soft);
}

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

.pagination {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    margin-top: 12px;
    font-size: 13px;
    color: var(--muted);
    flex-wrap: wrap;
}

.pagination-links { display: flex; align-items: center; gap: 12px; }
.pagination-links a { color: var(--accent); font-weight: 600; text-decoration: none; }

/* ---------- Auth pages ---------- */

.auth-shell {
    min-height: 100vh;
    display: grid;
    place-items: center;
    padding: 24px;
    background:
        radial-gradient(1200px 500px at 80% -10%, var(--accent-soft), transparent),
        var(--bg);
}

.auth-card {
    width: 100%;
    max-width: 400px;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: 16px;
    box-shadow: var(--shadow);
    padding: 30px 28px;
    display: flex;
    flex-direction: column;
    gap: 14px;
}

/* Settings: what the logo and the tab icon look like right now. The
   checkerboard shows transparency, which is what most logos rely on. */
.brand-upload {
    display: flex;
    align-items: center;
    gap: 14px;
    flex-wrap: wrap;
    padding: 12px 0;
    border-bottom: 1px solid var(--line);
}
.brand-upload:last-of-type { border-bottom: 0; }

.brand-preview {
    flex: none;
    width: 118px;
    height: 66px;
    display: grid;
    place-items: center;
    padding: 7px;
    border-radius: 10px;
    border: 1px solid var(--line);
    background-color: var(--surface-2, var(--surface));
    background-image:
        linear-gradient(45deg, rgba(128, 128, 128, 0.14) 25%, transparent 25%, transparent 75%, rgba(128, 128, 128, 0.14) 75%),
        linear-gradient(45deg, rgba(128, 128, 128, 0.14) 25%, transparent 25%, transparent 75%, rgba(128, 128, 128, 0.14) 75%);
    background-size: 14px 14px;
    background-position: 0 0, 7px 7px;
}

.brand-preview.small { width: 66px; }

/* The home screen icon is always opaque and square, so the transparency
   checkerboard would be a lie. Show it the way a phone will: cropped square
   with rounded corners, filling the frame. */
.brand-preview.appicon {
    width: 76px;
    height: 76px;
    padding: 0;
    overflow: hidden;
    background-image: none;
    border-radius: 17px;
}
/* max-height:none, because the cap on the logo preview further down the file
   would otherwise squash this one too — it is meant to fill its frame. */
.brand-preview.appicon img { width: 100%; height: 100%; max-height: none;
                             object-fit: cover; }

/* ---------------------------------------------------------------- Directions
   Tapping an address asks which navigation app should take you there. */
.navchoice { position: relative; display: inline-block; }

.navchoice-address {
    border: 0;
    background: none;
    padding: 0;
    font: inherit;
    color: var(--accent);
    text-align: inherit;
    cursor: pointer;
    text-decoration: underline;
    text-decoration-style: dotted;
    text-underline-offset: 3px;
}
.navchoice-address:hover { text-decoration-style: solid; }

/* A display rule beats the hidden attribute's UA style, so hidden has to be
   spelled out or the menu would sit open permanently. */
.navchoice-menu[hidden] { display: none; }

.navchoice-menu {
    position: absolute;
    z-index: 30;
    top: calc(100% + 6px);
    right: 0;
    min-width: 172px;
    display: flex;
    flex-direction: column;
    padding: 6px;
    border-radius: 11px;
    border: 1px solid var(--line);
    background: var(--surface);
    box-shadow: 0 16px 36px -14px rgba(2, 8, 23, 0.45);
}

.navchoice-heading {
    font-size: 10.5px;
    letter-spacing: 0.07em;
    text-transform: uppercase;
    color: var(--muted);
    padding: 4px 9px 6px;
}

.navchoice-menu a,
.navchoice-menu button {
    /* Comfortably tappable — this gets used one-handed in a truck. */
    display: block;
    width: 100%;
    padding: 10px 9px;
    border: 0;
    border-radius: 8px;
    background: none;
    font: inherit;
    font-size: 13px;
    color: var(--ink);
    text-align: left;
    text-decoration: none;
    cursor: pointer;
}
.navchoice-menu a:hover,
.navchoice-menu button:hover { background: var(--surface-soft); }
.navchoice-menu button { color: var(--muted); }
/*
 * A pixel cap on the height, not a percentage.
 *
 * max-width:100% works — the frame's width is definite. max-height:100% did
 * not: it measured against a grid area that never resolved, so a square logo
 * was drawn at its own size and hung out of the frame and across the row below
 * it, which reads as an upload that went wrong. 52px is the 66px frame less
 * its 7px of padding top and bottom. The home screen icon has its own rule
 * below and is cropped square on purpose.
 */
.brand-preview img { max-width: 100%; max-height: 52px; object-fit: contain; }
.brand-preview .brand-mark { width: 40px; height: 40px; font-size: 15px; }

.auth-brand { display: flex; align-items: center; gap: 12px; margin-bottom: 6px; }
.auth-brand .brand-logo { max-height: 46px; }
.auth-brand .brand-mark { width: 42px; height: 42px; font-size: 16px; }
.auth-brand h1 { font-size: 19px; }
.auth-brand p { margin: 0; color: var(--muted); font-size: 12.5px; }

/* ---------- Definition rows ---------- */

.def-list { display: flex; flex-direction: column; }

.def-row {
    display: flex;
    justify-content: space-between;
    gap: 14px;
    padding: 7.5px 0;
    border-bottom: 1px dashed var(--line);
    font-size: 13.5px;
}

.def-row:last-child { border-bottom: none; }
.def-row dt { color: var(--muted); }
/* A long email or reference has nowhere to wrap on a narrow phone and used to
   push the whole page sideways; it breaks mid-word rather than overflowing. */
.def-row dd { margin: 0; font-weight: 600; font-variant-numeric: tabular-nums;
    text-align: right; min-width: 0; overflow-wrap: anywhere; }
.def-row.total { border-top: 2px solid var(--line-strong); border-bottom: none; font-weight: 700; }

/* ---------- Permission matrix ---------- */

.perm-group { margin-bottom: 14px; }
.perm-group h4 { font-size: 12px; text-transform: uppercase; letter-spacing: 0.06em; color: var(--muted); margin-bottom: 6px; }
.perm-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); gap: 6px 14px; }

/* ---------- Print ---------- */

@media print {
    .sidebar, .topbar, .sidebar-backdrop, .no-print, .form-actions, .filter-bar, .pagination { display: none !important; }
    .app-shell { display: block; }
    .page-body { padding: 0; }
    .panel { border: 1px solid #ccc; box-shadow: none; break-inside: avoid; }
    body { background: #fff; color: #000; font-size: 12px; }
}

/* ---------- Responsive ---------- */

@media (max-width: 1080px) {
    .grid-2, .grid-3, .grid-sidebar { grid-template-columns: 1fr; }
    .kpi-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 900px) {
    .app-shell { grid-template-columns: 1fr; }

    .sidebar {
        position: fixed;
        left: 0;
        top: 0;
        bottom: 0;
        width: 262px;
        z-index: 50;
        transform: translateX(-105%);
        transition: transform 0.2s ease-out;
    }

    .sidebar.open { transform: none; box-shadow: var(--shadow-lg); }
    .sidebar-backdrop.open { display: block; }
    .sidebar-toggle { display: flex; }

    .topbar { padding: 12px 16px; }

    /* Slides out of the way on a downward scroll and returns on the way up.
       transform, not display, so it animates and keeps its sticky slot; the
       shadow only appears once it has come back over content. app.js adds and
       removes .topbar-hidden. */
    .topbar { transition: transform 0.24s ease, box-shadow 0.24s ease; will-change: transform; }
    .topbar-hidden { transform: translateY(-100%); box-shadow: none; }

    @media (prefers-reduced-motion: reduce) {
        .topbar { transition: none; }
    }
    .page-body { padding: 16px 14px 34px; }
    .flash { margin: 12px 16px -4px; }
    .user-meta { display: none; }
}

@media (max-width: 640px) {
    /* Two even columns: compact, and rows keep the fields aligned even when a
       label wraps — which is what made them overlap before. */
    .filter-bar {
        display: grid;
        grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: 10px 8px;
        align-items: end;
    }
    .filter-bar .field,
    .filter-bar .field.grow { min-width: 0; }
    .filter-bar .field label { font-size: 11.5px; }
    .filter-bar .button,
    .filter-bar > span { grid-column: 1 / -1; }
    .filter-bar .button { justify-content: center; }
    .filter-bar > span { margin-left: 0 !important; }

    /* Long ghost links in panel headers were the one thing still able to push
       the page sideways on a narrow phone. */
    .button.ghost { white-space: normal; text-align: left; }
    .panel-header { row-gap: 8px; }

    /* Stacking flips the main axis, so the title field's flex-basis would
       become a 260px HEIGHT. Reset it to size by content. */
    .todo-add { flex-direction: column; align-items: stretch; }
    .todo-add input, .todo-add select, .todo-add .button { width: 100%; }
    .todo-add input[name="title"] { flex: 0 0 auto; }
    .todo-add .button { justify-content: center; }

    /* The title, the theme toggle and the avatar stay on one row — they are
       small. Only real action buttons ("New appointment") drop below, and only
       when the page has any, so the Dashboard keeps a single tidy row.

       The basis has to be 0, not auto. A wrapping flex container breaks lines
       using each item's BASE size, before any shrinking — so with `auto` a long
       page title (a project's name plus its client and address) measured wider
       than the screen and pushed itself, and then the toggle and avatar, onto
       lines of their own. At 0 the title claims no width up front, shares the
       row, and ellipses to whatever is left. */
    .topbar { flex-wrap: wrap; row-gap: 10px; }
    .topbar-title { flex: 1 1 0; min-width: 0; order: 1; }
    .topbar-title h1 { font-size: 17px; }
    .topbar-subtitle { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
    .topbar-actions { order: 2; flex: none; }
    .topbar-buttons { order: 3; flex: 1 1 100%; }
    .topbar-buttons .button { flex: 1 1 auto; justify-content: center; }

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

    /* On a narrow card the name would squeeze the title into a column of one
       or two words — give the title the full width and let the name sit under. */
    .todo-titlewrap .todo-title { flex: 1 1 100%; }
    .todo-who { margin-left: 4px; }
}

@media (max-width: 560px) {
    .kpi-grid { grid-template-columns: 1fr; }
    .kpi-value { font-size: 24px; }
    .metric-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .metric-value { font-size: 18px; }
    .form-grid { grid-template-columns: 1fr; }
    .chart-box { height: 220px; }
}

/* ------------------------------------------------------------ Financial summary
   A statement, not a spreadsheet dump: three labelled groups, figures on one
   right-hand column in tabular numerals so the decimal points line up, and the
   profit set apart at the foot with its margin under it. Colour is spent only
   where it means something — the balance owed, and profit itself. */
.fin-group { padding: 4px 0 14px; border-bottom: 1px solid var(--line); }
.fin-group + .fin-group { padding-top: 14px; }

.fin-label {
    margin: 0 0 9px;
    font-size: 10.5px;
    font-weight: 700;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    color: var(--muted);
}

.fin-row {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 14px;
    padding: 5px 0;
    font-size: 13.5px;
}
.fin-row > span { color: var(--muted); }
.fin-row > span small { font-size: 11.5px; opacity: 0.75; margin-left: 3px; }
.fin-row > b {
    font-weight: 600;
    color: var(--ink);
    /* Same-width digits, so every figure in the column aligns. */
    font-variant-numeric: tabular-nums;
    font-feature-settings: 'tnum' 1;
    white-space: nowrap;
}

/* The row rule sets the colour, so a toned figure has to out-specify it. */
.fin-row > b.warning-text { color: var(--warning); }
.fin-row > b.negative { color: var(--danger); }
.fin-row > b.positive { color: var(--positive); }

.fin-row.fin-total {
    margin-top: 6px;
    padding-top: 10px;
    border-top: 1px solid var(--line);
}
.fin-row.fin-total > span { color: var(--ink); font-weight: 600; }
.fin-row.fin-total > b { font-size: 15px; font-weight: 700; }

.fin-bar {
    height: 4px;
    margin: 10px 0 0;
    border-radius: 99px;
    background: var(--surface-soft);
    overflow: hidden;
}
.fin-bar > i {
    display: block;
    height: 100%;
    border-radius: 99px;
    background: var(--accent);
}
.fin-bar.warn > i { background: var(--warning); }
.fin-bar.over > i { background: var(--danger); }

.fin-note {
    margin: 7px 0 0;
    line-height: 1.45;
    font-size: 11.5px;
    color: var(--muted);
    font-variant-numeric: tabular-nums;
}
.fin-note + .fin-note { margin-top: 4px; }

.fin-profit { padding-top: 16px; }
.fin-figure {
    margin: 0;
    font-size: 29px;
    font-weight: 700;
    letter-spacing: -0.02em;
    line-height: 1.1;
    font-variant-numeric: tabular-nums;
    color: var(--ink);
}
.fin-figure.positive { color: var(--positive); }
.fin-figure.negative { color: var(--danger); }
.fin-figure.muted { color: var(--muted); }
.fin-margin {
    margin: 5px 0 0;
    font-size: 13px;
    color: var(--muted);
    font-variant-numeric: tabular-nums;
}

/* The Zap setup instructions, folded away until wanted. */
.sync-help { margin-top: 14px; font-size: 13px; }
.sync-help summary { cursor: pointer; color: var(--accent); font-weight: 600; }
.sync-help ol { margin: 10px 0 0; padding-left: 20px; line-height: 1.7; }
.sync-help code {
    background: var(--surface-soft);
    padding: 1px 5px;
    border-radius: 4px;
    font-size: 12px;
    /* A cron line or a redirect URI is one long unbroken token, and on a phone
       it pushed the page wider than the screen. It breaks mid-string instead. */
    overflow-wrap: anywhere;
}

/* ------------------------------------------------------------- Project status
   Four states, so they are all shown at once as a segmented control rather than
   hidden behind a dropdown. The current one is filled in its own colour; the
   rest sit quiet until hovered. Each is a real submit button, so changing status
   is a single tap and needs no JavaScript.

   On a phone the row becomes a 2x2 grid: four full-width taps stacked would push
   the tabs off the first screen, and a horizontally scrolling row hides options
   the user does not know are there. */
.project-bar {
    padding: 12px 14px;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    flex-wrap: wrap;
}

.status-picker {
    display: inline-flex;
    padding: 3px;
    gap: 3px;
    border-radius: 11px;
    background: var(--surface-soft);
    border: 1px solid var(--line);
}
.status-picker.readonly { padding: 0; background: none; border: 0; }

.status-option {
    appearance: none;
    border: 0;
    cursor: pointer;
    font: inherit;
    font-size: 12.5px;
    font-weight: 600;
    letter-spacing: 0.01em;
    padding: 7px 14px;
    border-radius: 8px;
    background: transparent;
    color: var(--muted);
    white-space: nowrap;
    transition: background 0.16s ease, color 0.16s ease, box-shadow 0.16s ease;
}
.status-option:hover { background: var(--surface); color: var(--ink); }
.status-option:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

/* The live state, coloured to match the badge that status carries elsewhere. */
.status-option.is-current { color: #fff; cursor: default; }
.status-option.status-scheduled.is-current { background: #8b5cf6; box-shadow: 0 1px 8px rgba(139, 92, 246, 0.35); }
.status-option.status-in_progress.is-current { background: #3b82f6; box-shadow: 0 1px 8px rgba(59, 130, 246, 0.35); }
.status-option.status-on_hold.is-current { background: #f59e0b; box-shadow: 0 1px 8px rgba(245, 158, 11, 0.35); }
.status-option.status-completed.is-current { background: #10b981; box-shadow: 0 1px 8px rgba(16, 185, 129, 0.35); }
.status-option.is-current:hover { color: #fff; }

.project-facts { margin: 0; font-size: 12.5px; }

/*
 * The four working lists — quotes, change orders, jobs, invoices — are laid out
 * the same way on purpose: number, title, client, then what that document is
 * about. The number is its own column rather than a prefix on the title so the
 * eye can run straight down it, which is how anybody looks for one.
 */
.col-number { white-space: nowrap; width: 1%; }
.col-number a { font-variant-numeric: tabular-nums; font-weight: 600; }

/* An address is long and is not the point of the row, so it is quieter than the
   name beside it and wraps rather than stretching the table. */
.cell-address {
    display: inline-block;
    max-width: 240px;
    font-size: 12.5px;
    color: var(--muted);
    line-height: 1.4;
}

@media (max-width: 720px) {
    .project-bar { padding: 12px; gap: 10px; }

    /* One row on a phone too. Four equal columns rather than auto widths, so
       the pills line up regardless of how long each label is, and the type
       steps down just enough for "In Progress" — the longest of the four — to
       sit on one line at 320px. min-height keeps the tap target honest once
       the padding shrinks. */
    .status-picker {
        display: grid;
        grid-template-columns: repeat(4, 1fr);
        width: 100%;
        gap: 3px;
    }
    .status-option {
        padding: 8px 2px;
        min-height: 40px;
        display: flex;
        align-items: center;
        justify-content: center;
        font-size: 11px;
        letter-spacing: 0;
        text-align: center;
    }
    .project-facts { font-size: 12px; }
}

@media (max-width: 360px) {
    /* An SE-sized screen. The labels still fit at 11px, but only just, and the
       pills end up packed against each other with no breathing room. */
    .status-option { font-size: 10px; padding: 8px 1px; }
    .status-picker { gap: 2px; padding: 2px; }
}

/* ------------------------------------------------------------------- Address
   Street lines span the full width because they are long; city, state, ZIP and
   country are short and sit two-up, which is how an address is actually read. */
.address-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.address-grid .span-2 { grid-column: 1 / -1; }
.field-group { margin-bottom: 14px; }
.field-group > label { display: block; font-size: 13px; font-weight: 600; margin-bottom: 6px; }

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

/* ------------------------------------------------------------ Support session
   Somebody from the operator console signed in as this company's owner. Loud
   on purpose: a back door nobody can see is the kind that gets used carelessly,
   and the customer should be able to tell at a glance whose session this is. */
.support-bar{display:flex;align-items:center;gap:12px;flex-wrap:wrap;
    margin: 0 0 14px; padding: 10px 14px; border-radius: 10px;
    background: #fff7ec; border: 1px solid #f5c78a; color: #7a4a08;
    font-size: 13px; line-height: 1.45;}
.support-bar span{display:flex;align-items:center;gap:8px;flex:1;min-width:0}
.support-bar svg{flex:none}
.support-bar a{margin-left:auto;font-weight:700;color:#7a4a08;text-decoration:underline;white-space:nowrap}
[data-theme="dark"] .support-bar{background:#3a2a12;border-color:#7a5a24;color:#fbd9a5}
[data-theme="dark"] .support-bar a{color:#fbd9a5}

/* The last two days of a trial. Red, because by then it is not a note. */
.support-bar.urgent{background:#fef3f2;border-color:#fda29b;color:#912018}
.support-bar.urgent a{color:#912018}
[data-theme="dark"] .support-bar.urgent{background:#42211e;border-color:#8c3a33;color:#fda29b}
[data-theme="dark"] .support-bar.urgent a{color:#fda29b}

/* --------------------------------------------------------------- Button menu
   A single + that opens a short list, instead of a row of buttons competing for
   the same corner. Anchored to its trigger so it opens where the eye already is. */
.menu-wrap { position: relative; display: inline-flex; }

.menu {
    position: absolute;
    top: calc(100% + 6px);
    right: 0;
    z-index: 60;
    min-width: 210px;
    padding: 6px;
    border-radius: 12px;
    border: 1px solid var(--line-strong);
    background: var(--surface);
    box-shadow: var(--shadow-lg);
    display: none;
}
.menu.open { display: block; }

.menu button,
.menu a {
    display: flex;
    align-items: center;
    gap: 9px;
    width: 100%;
    padding: 9px 11px;
    border: 0;
    border-radius: 8px;
    background: none;
    color: var(--ink);
    font: inherit;
    font-size: 13.5px;
    text-align: left;
    text-decoration: none;
    cursor: pointer;
}
.menu button:hover, .menu a:hover,
.menu button:focus-visible, .menu a:focus-visible {
    background: var(--surface-soft);
    outline: none;
}
.menu form { margin: 0; }
.menu .menu-note { display: block; padding: 4px 11px 8px; font-size: 12px; color: var(--muted); }

/* The lead item.
   Every record page now shows exactly two buttons — Edit, and this menu — and
   the thing the record is actually waiting for went in here with everything
   else. That would have buried it, so it sits first and is the only item in
   the list carrying any colour: the eye still lands on it, it just no longer
   competes with four other buttons for the same corner. One per menu. */
.menu .lead {
    background: var(--accent-soft);
    color: var(--accent);
    font-weight: 600;
    box-shadow: inset 2px 0 0 var(--accent);
}
.menu .lead:hover,
.menu .lead:focus-visible {
    background: var(--accent-soft);
    color: var(--accent-strong);
    box-shadow: inset 3px 0 0 var(--accent-strong);
}
.menu .lead svg { color: currentColor; }

/* Separates what this record is waiting for, and what can be done to it, from
   the things that only ever produce a copy — print, download, preview. */
.menu-line { height: 1px; margin: 6px 5px; background: var(--line); }

@media (max-width: 560px) {
    /* Still anchored to the trigger's right edge — the buttons that open these
       menus all sit at the right of their bar, so the old left anchor pushed the
       panel off the screen. Width is capped instead, and app.js nudges whatever
       still does not fit back inside. */
    .menu { min-width: min(240px, 78vw); max-width: calc(100vw - 24px); }
}

/* ----------------------------------------------------------- Quote line items
   A card per line rather than a table row. A quote line is a sentence or
   two of scope, not a cell — in a table the description column either squeezed
   everything else out or wrapped into an unreadable sliver. The card gives the
   numbers a tidy row of their own and hands the description the full width.

   It also survives a phone, which a five-column table of inputs does not. */
.line-items { display: flex; flex-direction: column; gap: 12px; }

.line-card {
    border: 1px solid var(--line);
    border-radius: 12px;
    /* Even all the way round. The old 4px bottom left the description sitting
       tight against the edge while the top had room, which read as a card that
       had been cut off. */
    padding: 14px 16px;
    background: var(--surface-soft);
    display: flex;
    flex-direction: column;
    gap: 10px;
}
.line-card.is-text { border-style: dashed; }

/* Marked for removal: still there, still editable if it was a mistake, but
   plainly on the way out — and its money already out of the total above. */
.line-card.is-removing { opacity: 0.45; }

.line-head { display: flex; align-items: center; justify-content: space-between; margin: 0; }
.line-head h3 { font-size: 13px; margin: 0; letter-spacing: 0.02em; }

.line-remove {
    display: inline-flex; align-items: center; justify-content: center;
    width: 30px; height: 30px; border-radius: 8px; cursor: pointer;
    border: 1px solid transparent; background: none; color: var(--muted);
    font-size: 19px; line-height: 1; padding: 0;
}
.line-remove:hover { background: var(--surface); color: var(--danger, #ef4444); border-color: var(--line); }
/* The checkbox is the state for a saved row; the × is what is clicked. Hidden
   rather than removed, so the form still works with no JavaScript — but it stays
   focusable, and the label shows the ring, or a keyboard user would be tabbing
   onto something with no visible sign of where they are. */
.line-remove input { position: absolute; opacity: 0; width: 1px; height: 1px; margin: 0; }
.line-remove:focus-within { outline: 2px solid var(--accent); outline-offset: 1px; }
.line-remove input:checked + span { color: var(--danger); }

/* Name takes the room; the numbers stay narrow and aligned. */
.line-grid {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 90px 80px 120px 110px;
    gap: 10px;
    align-items: start;
}
.line-grid .field { margin: 0; }

/* The line's own total. It is a result, not a box you type in — but it sits in a
   row of inputs, so it matches them exactly: same padding, same size, same
   border. It was two pixels taller all round and drawn in a lighter border,
   which made one cell of every line look bigger than the rest of the row. */
.line-total {
    display: block;
    width: 100%;
    padding: 8px 10px;
    border: 1px solid var(--line-strong);
    border-radius: 8px;
    background: var(--surface-soft);
    color: var(--ink);
    font-size: 13.5px;
    font-weight: 650;
    font-variant-numeric: tabular-nums;
    text-align: right;
    /* The exact line box an input gets at this size, so the Total lands at the
       same height as the fields beside it rather than half a pixel short. */
    line-height: 1.5;
}

/* Pushed to the right, under the last line's own controls, so the eye leaves a
   line where it left off rather than travelling back to the margin. */
.line-actions { display: flex; gap: 10px; flex-wrap: wrap; margin-top: 14px; justify-content: flex-end; }

.line-sum {
    display: flex; justify-content: space-between; align-items: baseline;
    margin-top: 16px; padding-top: 14px; border-top: 2px solid var(--line-strong);
}
.line-sum strong { font-size: 20px; font-variant-numeric: tabular-nums; }

@media (max-width: 720px) {
    /* Name on its own line, then the three numbers share a row — the numbers
       are short and belong side by side; the name never is. */
    .line-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
    /* Name and Total each take the whole width: the name is never short, and a
       total sitting in a narrow third-of-a-row box reads like another input to
       fill in rather than the answer. */
    .line-grid .field:first-child,
    .line-grid .field:last-child { grid-column: 1 / -1; }
    .line-card { padding: 12px 12px 2px; }
}

/* ------------------------------------------------------- Selecting many rows
   The bar stays out of the way until something is actually ticked. */
.bulk-bar {
    position: sticky;
    top: 8px;
    z-index: 5;
    display: flex;
    align-items: center;
    /* Three or four buttons and a count do not fit across a 320px phone, and a
       row that will not wrap pushes the whole page sideways rather than being
       clipped by itself. It wraps; the count keeps the first line. */
    flex-wrap: wrap;
    gap: 8px;
    margin-bottom: 10px;
    padding: 9px 12px;
    border-radius: 11px;
    border: 1px solid var(--accent);
    background: var(--surface);
    box-shadow: 0 10px 26px -14px rgba(2, 8, 23, 0.4);
    font-size: 13px;
}
.bulk-bar[hidden] { display: none !important; }
.bulk-bar > span:first-child { margin-right: auto; color: var(--muted); }

@media (max-width: 420px) {
    /* Wrapped, the count sitting on its own line with the buttons under it
       reads better than a ragged mix, so it takes the whole first row. */
    .bulk-bar > span:first-child { flex: 1 0 100%; margin-right: 0; }
    .bulk-bar .button.small { flex: 1 1 auto; justify-content: center; }
}
.bulk-bar strong { color: var(--ink); }
.button.small { padding: 5px 11px; font-size: 12.5px; }
.button.small.danger { color: var(--danger); border-color: var(--danger-soft); }
.button.small.danger:hover { background: var(--danger-soft); }

th.pick, td.pick { width: 34px; padding-right: 0; }

/* The box is drawn rather than native, so it matches in both themes; the real
   input sits on top and invisible so taps land on it and nothing else. */
.pickbox { position: relative; display: inline-flex; width: 18px; height: 18px; cursor: pointer; }
.pickbox input {
    position: absolute; inset: -6px;
    width: calc(100% + 12px); height: calc(100% + 12px);
    max-width: none;
    z-index: 1; opacity: 0; cursor: pointer;
}
.pickbox span {
    width: 18px; height: 18px;
    border-radius: 5px;
    border: 1.5px solid var(--line-strong);
    background: var(--surface);
    transition: background 120ms ease, border-color 120ms ease;
}
.pickbox input:checked + span {
    background: var(--accent);
    border-color: var(--accent);
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='white' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3.5 8.5l3 3 6-6'/%3E%3C/svg%3E");
    background-size: 14px;
    background-position: center;
    background-repeat: no-repeat;
}
.pickbox input:indeterminate + span { background: var(--accent-soft); border-color: var(--accent); }
.pickbox input:focus-visible + span { outline: 2px solid var(--accent); outline-offset: 2px; }

tr.is-picked { background: var(--accent-soft); }

.archive-toggle { font-size: 12.5px; margin-top: 10px; }

/* The few seconds a ticked to-do lingers before filing itself away. */
.todo-countdown {
    display: flex;
    align-items: center;
    gap: 10px;
    margin: 8px 0 0 27px;
    padding: 6px 10px;
    border-radius: 8px;
    background: var(--surface-soft);
    font-size: 12px;
    color: var(--muted);
}
.todo-countdown span { flex: 1; font-variant-numeric: tabular-nums; }
.todo-countdown button {
    border: 0;
    background: none;
    padding: 4px 8px;
    margin: -4px -4px -4px 0;
    border-radius: 6px;
    font: inherit;
    font-weight: 650;
    color: var(--accent);
    cursor: pointer;
}
.todo-countdown button:hover { background: var(--accent-soft); }

/* ---------- Settings tabs ---------- */

/* The tab row is the page's own navigation, so it sits directly under the
   header rather than floating between panels. */
.settings-tabs {
    margin-bottom: 16px;
}

/* On a phone the row scrolls sideways; a hint of padding at the end stops the
   last tab looking cut off when it is scrolled fully into view. */
@media (max-width: 720px) {
    .settings-tabs { padding-right: 12px; }
}

/* ---------- Property rows ---------- */

/* The address leads, its name and notes sit under it. Two tight lines rather
   than three columns that were mostly dashes. */
.property-cell strong {
    display: block;
    line-height: 1.35;
}

.property-cell small {
    display: block;
    font-size: 12.5px;
    line-height: 1.35;
    margin-top: 2px;
}

/* ---------- Saved products & services ---------- */

/* The save-to-list button shares the line head with the title and the × . It is
   deliberately quiet: it is a convenience, not the thing you came to the line to
   do, and it must never compete with the remove control beside it. */
.line-save {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    margin-left: auto;
    margin-right: 6px;
    padding: 4px 9px;
    border: 1px solid var(--line);
    border-radius: 7px;
    background: none;
    color: var(--muted);
    font: inherit;
    font-size: 12px;
    font-weight: 600;
    cursor: pointer;
    white-space: nowrap;
}

.line-save:hover:not(:disabled) { background: var(--surface-soft); color: var(--ink); }
.line-save:disabled { opacity: 0.7; cursor: default; }

@media (max-width: 560px) {
    /* Three things competing for a phone's width broke "Product / Service"
       across two lines and still left the save button pinched. The heading and
       the × keep the top row; the save button drops to its own line under them,
       still on the right where the other buttons on the page sit. */
    .line-head { flex-wrap: wrap; row-gap: 0; }
    .line-head h3 { order: 1; flex: 1 1 auto; white-space: nowrap; }
    .line-remove { order: 2; }
    /* A zero-height full-width row that forces the wrap. It costs nothing on a
       card with no save button, which is why the break lives here rather than
       in a width on the button — stretching the button itself turned its border
       into a full-width strip that no longer read as a button. */
    .line-head::after { content: ''; order: 3; flex: 1 0 100%; height: 0; }
    .line-save { order: 4; flex: 0 0 auto; margin: 8px 0 0 auto; }
}

/* Lives on <body> so a panel's clipped overflow cannot cut it off. Fixed
   positioning keeps it under its field without any scroll arithmetic. */
.service-menu {
    position: fixed;
    z-index: 60;
    overflow-y: auto;
    /* Wider than the Name box it hangs under: the box is one column of a line
       grid, and squeezing a service name plus its price into it would wrap
       every row onto three lines. */
    max-width: calc(100vw - 24px);
    padding: 4px;
    background: var(--surface);
    border: 1px solid var(--line-strong);
    border-radius: 10px;
    box-shadow: 0 12px 32px rgba(15, 23, 42, 0.18);
}

.service-option {
    display: block;
    width: 100%;
    padding: 8px 10px;
    border: 0;
    border-radius: 7px;
    background: none;
    color: var(--ink);
    font: inherit;
    text-align: left;
    cursor: pointer;
}

.service-option:hover,
.service-option.is-active { background: var(--surface-soft); }

.service-option-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px;
}

.service-option-head strong { font-size: 13.5px; font-weight: 600; }
.service-option-head strong,
.service-option small { min-width: 0; }

.service-option-price {
    flex: 0 0 auto;
    font-size: 12.5px;
    color: var(--muted);
    font-variant-numeric: tabular-nums;
}

/* One line of it. The full wording lands in the description box when picked —
   the menu only has to be enough to tell two similar services apart. */
.service-option small {
    display: block;
    margin-top: 2px;
    font-size: 12px;
    color: var(--muted);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

@media (max-width: 620px) {
    .line-save { font-size: 11.5px; padding: 4px 7px; }
    .service-option-head { flex-wrap: wrap; gap: 2px 10px; }
}

/* ---------- Crew picker on a visit ---------- */

/* Tick who is going. Chips rather than a multi-select: on a phone a native
   multi-select needs a long press to add a second person, which is exactly the
   thing you are trying to do here. */
.crew-picker {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
}

.crew-person {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 6px 10px;
    border: 1px solid var(--line);
    border-radius: 999px;
    background: var(--surface);
    font-size: 13px;
    cursor: pointer;
}

.crew-person:hover { border-color: var(--line-strong); }

.crew-person:has(input:checked) {
    border-color: var(--accent);
    background: var(--accent-soft);
    color: var(--accent-strong);
    font-weight: 600;
}

.crew-person input { margin: 0; }

/* ---------- Tables that become cards on a phone ---------- */

/*
 * A five-column table on a 390px screen either squeezes every word onto its own
 * line or pushes the columns that matter off the right edge. These tables turn
 * into one block per row instead, with each cell labelled by the header it came
 * from, so nothing has to be scrolled sideways to be read.
 */
@media (max-width: 720px) {
    .stack-mobile thead { display: none; }

    .stack-mobile,
    .stack-mobile tbody,
    .stack-mobile tr,
    .stack-mobile td {
        display: block;
        width: 100%;
    }

    .stack-mobile tr {
        padding: 12px 2px;
        border-bottom: 1px solid var(--line);
    }

    .stack-mobile tr:last-child { border-bottom: 0; }

    .stack-mobile td {
        border: 0;
        padding: 1px 0;
    }

    /* The first cell is the row's name — it leads, the rest annotate it. */
    .stack-mobile td:first-child,
    .stack-mobile td.pick + td {
        font-size: 14.5px;
        margin-bottom: 4px;
    }

    /*
     * A selection box on a stacked row.
     *
     * Every cell is a full-width block down here, so left alone the checkbox
     * took a line of its own above the name and stole the "this is the row's
     * title" styling from it. It sits in the corner of the card instead, the
     * way a checkbox does on any list of things you can pick.
     */
    .stack-mobile tr:has(td.pick) { position: relative; padding-right: 34px; }
    .stack-mobile td.pick {
        position: absolute;
        top: 12px;
        /* The box carries an invisible input stretched 6px past it on every side
           so a thumb hits it, and that overhang is what widens the table and
           scrolls the whole card sideways. Cleared here rather than shrinking
           the target: on a stacked row the whole corner is free space, so the
           input can simply sit inside its own cell. */
        right: 10px;
        width: auto;
        margin: 0;
        padding: 0;
    }
    .stack-mobile td.pick + td { font-size: 14.5px; }
    .stack-mobile td.pick .pickbox input { inset: -6px 0 -6px -6px; width: calc(100% + 6px); }

    .stack-mobile td[data-label]::before {
        content: attr(data-label) " ";
        color: var(--muted);
        font-size: 12px;
        font-weight: 600;
    }

    /*
     * Number and title share the card's first line.
     *
     * Stacked, every cell is a block, so a number column of its own turned the
     * strongest line of the card into the word "Job #" and pushed the name of
     * the work down to third place. They belong together here — "#1046 Kitchen
     * strip out" — the way they read on a piece of paper.
     */
    .stack-mobile td.col-number {
        float: left;
        margin: 0 7px 0 0;
        font-size: 14.5px;
    }
    .stack-mobile td.col-number::before { content: none; }
    .stack-mobile td.col-number + td { font-size: 14.5px; margin-bottom: 4px; }
    .stack-mobile td.col-number + td::before { content: none; }
    /* The line after the pair starts a fresh row rather than wrapping round it. */
    .stack-mobile td.col-number + td + td { clear: both; }

    .stack-mobile td.actions {
        display: flex;
        gap: 4px;
        margin-top: 8px;
    }

    .stack-mobile .table-subtext { display: inline; }
    .stack-mobile .empty-cell { text-align: left; }
}

/* The "book the N working days" button says exactly what it will do, which makes
   it long. On a phone it wraps rather than pushing the page sideways. */
.button.wrap-label {
    max-width: 100%;
    white-space: normal;
    text-align: left;
    line-height: 1.3;
}

/* ---------- The visit form ---------- */

/* The worked-out day, sitting under the two times it comes from. Reads as a
   result, not as another field to fill in. */
.visit-hours {
    display: block;
    padding: 9px 12px;
    border-radius: 9px;
    background: var(--surface-soft);
    color: var(--ink-soft);
    font-size: 13px;
    font-weight: 600;
}

.visit-hours.is-wrong {
    background: var(--danger-soft, rgba(239, 68, 68, 0.12));
    color: var(--danger, #ef4444);
}

/* A crew member's name is a name — breaking "Luiz Paulo Souza" across three
   lines inside a chip made the picker look like a wall of rubble. The chips
   wrap; what is written on them does not. */
.crew-person span { white-space: nowrap; }

/* Two fields that describe one thing — a start and an end — stay on one row even
   in a narrow modal, where the auto-fitting form grid would otherwise stack them. */
.field-pair {
    grid-column: 1 / -1;
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 12px 14px;
}

/* A short statement of fact inside a panel — something the reader needs to know
   before acting, with the action beside it. Quieter than an alert; it is not a
   problem, it is context. */
.notice-inline {
    margin-top: 12px;
    padding: 10px 12px;
    border: 1px solid var(--line);
    border-left: 3px solid var(--accent);
    border-radius: 8px;
    background: var(--surface-soft);
    font-size: 13px;
    line-height: 1.5;
}

/* The share link, sitting outside a .field and so missing the input styling that
   makes a box readable in the dark theme — it was rendering as the browser's own
   white. Same shape as every other input, plus the monospace that makes a long
   random token something you can check a character of. */
.link-box {
    width: 100%;
    padding: 9px 11px;
    border: 1px solid var(--line);
    border-radius: 9px;
    background: var(--surface-soft);
    color: var(--ink);
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 12.5px;
}

.link-box:focus {
    outline: 2px solid var(--accent);
    outline-offset: 1px;
}

/* The action group in a record's status bar. margin-left:auto rather than the
   bar's space-between doing the work: when the buttons no longer fit beside the
   status and wrap onto their own row, space-between has nothing left to push
   against and drops them back to the left margin. */
.project-bar .bar-actions {
    margin: 0 0 0 auto;
}

/* ------------------------------------------------------------- Editing state
   A record page opens read-only, so when it is armed it has to say so — armed
   and unarmed look otherwise identical, and "why won't this save?" and "why did
   that change?" are both answered by a line at the top of the form. */
.edit-banner {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    flex-wrap: wrap;
    margin: 0 0 16px;
    padding: 9px 13px;
    border: 1px solid var(--accent);
    border-radius: 10px;
    background: var(--accent-soft);
    color: var(--ink);
    font-size: 13px;
}
.edit-banner span { display: inline-flex; align-items: center; gap: 7px; min-width: 0; }
.edit-banner svg { flex: none; }

/* The same line on an accepted quote, where being armed is a bigger deal. */
.edit-banner.warn {
    border-color: var(--warning);
    background: var(--warning-soft);
}

/* A locked record should read as a record, not as a form that happens not to
   work. Two things were wrong with leaving the boxes as they are: the browser's
   own disabled styling greys the text until it is hard to read, and full input
   chrome on a page that cannot be edited invites the click that does nothing.

   So the values keep their full contrast and lose the box: no border, no fill,
   the same padding so nothing shifts when Edit is pressed. It reads as printed
   text, and the difference between looking and editing is unmistakable. */
.field input[readonly],
.field textarea[readonly],
.field select:disabled,
.line-card input[readonly],
.line-card textarea[readonly] {
    background: transparent;
    color: var(--ink);
    border-color: transparent;
    cursor: default;
    opacity: 1;
    resize: none;
    -webkit-text-fill-color: var(--ink);
}
.field input[readonly]:focus,
.field textarea[readonly]:focus,
.field select:disabled:focus {
    border-color: transparent;
    box-shadow: none;
    outline: none;
}

/* A select disabled while it waits for something else — the invoice picker on a
   payment before a project is chosen, the job list on a change order — keeps a
   faint outline. It is a control that will work in a moment, and flattening it
   would leave an unexplained piece of grey text where a box belongs. */
.field select:disabled { border-color: var(--line); background: var(--surface-soft); }

/* On a record that is locked, it is not waiting for anything: there is nothing
   to choose until Edit is pressed, and a box with an arrow on it is an
   invitation to the click that does nothing — the very thing the block above
   exists to avoid. So inside a locked form a select reads as printed text like
   every other value on it, arrow included. `appearance` is what removes the
   arrow; without it a stray triangle floats over transparent ground.

   Scoped to .is-locked rather than to :disabled generally, because those two
   kinds of disabled mean opposite things to the person looking at them. */
.is-locked .field select:disabled {
    -webkit-appearance: none;
    -moz-appearance: none;
    appearance: none;
    border-color: transparent;
    background: transparent;
    /* The arrow's room given back to the text, and what still will not fit ends
       in an ellipsis. A select cannot wrap, and a site address clipped mid-word
       with no box around it reads as a rendering fault rather than as a value
       too long for the column. */
    padding-right: 10px;
    text-overflow: ellipsis;
}

/* On a locked record the values keep a field of their own.
   -------------------------------------------------------------------------
   Stripping the box entirely read as printed text on a page a customer holds,
   which was right for a Title and wrong for a column of eight — a quote's work
   panel became a wall of labels and values floating with nothing to say where
   one ended and the next began.

   A fill and no border is the difference between a value and an input: the box
   says "there is something in here", the missing border says "not until you
   press Edit". Same padding either way, so nothing moves when it is armed.

   Every kind of control gets it, which is the point — the fault this replaces
   was inputs going flat while selects kept a grey box, so a row of fields read
   as two different things depending on what happened to be behind it. */
.is-locked .field input[readonly],
.is-locked .field textarea[readonly],
.is-locked .field select:disabled {
    background: var(--surface-soft);
    border-color: transparent;
}

/* A line card is already that soft grey, so its values take the panel's white
   instead: the point is a field a shade off its own ground, and which way round
   that runs depends on what the ground is. */
.line-card.is-locked input[readonly],
.line-card.is-locked textarea[readonly],
.line-card.is-locked select:disabled {
    background: var(--surface);
    border-color: transparent;
}

/* A placeholder in a locked field is not a hint, it is a lie: there is nothing
   to type into, so the grey text reads as the record's actual content. */
.field input[readonly]::placeholder,
.field textarea[readonly]::placeholder,
.line-card input[readonly]::placeholder,
.line-card textarea[readonly]::placeholder { color: transparent; }

/* A locked textarea shrinks to what it holds, so an empty Terms box is not six
   rows of nothing. `rows` still governs where the browser does not support it,
   which is the same height the field has while it is being edited. */
.field textarea[readonly] { field-sizing: content; min-height: 0; }

/* The line's Total is an <output>, not an input, so it needs telling too —
   otherwise it is the one boxed thing on an otherwise flat, locked page. */
.line-card.is-locked .line-total {
    border-color: transparent;
    background: transparent;
    padding-left: 0;
    padding-right: 0;
}

/* Client link and the like are readonly by nature, not because the page is
   locked — they keep their box so they still read as something to copy. */
.field input.link-box[readonly] {
    background: var(--surface-soft);
    border-color: var(--line-strong);
}

/* ---------------------------------------------------------------------------
   Picking the brand colour

   Twenty swatches you can point at, because almost nobody has a hex code to
   hand and almost everybody can recognise the colour of their own truck. The
   radio itself is hidden; the coloured tile is the control, and it says which
   one is chosen with a ring drawn INSIDE it rather than a tick, so the colour
   is never covered up by the thing marking it.

   One block, not a row of tiles. They butt together with no gaps and the
   container does the rounding and the clipping, so the twenty read as a single
   band of colour running warm to cool — which is how somebody actually looks
   for a colour. Ten columns, two rows, always full: a fixed count rather than
   auto-fit, because auto-fit leaves a hole at the end of the last row and a
   hole in a band of colour looks like a missing swatch.
   --------------------------------------------------------------------------- */
.swatches {
    display: grid;
    grid-template-columns: repeat(10, 1fr);
    border-radius: 10px;
    overflow: hidden;
    border: 1px solid var(--line);
    max-width: 520px;
    /*
     * A hairline round every tile, drawn by the grid rather than by the tiles.
     *
     * A 1px gap with the block's own background showing through it gives each
     * colour an edge on all four sides — including between the two rows, which
     * a border on the tile itself would double up. Flush colours were a band
     * you had to squint at to find the join between two dark ones; separated,
     * every tile is its own target. The line is the same token as the outer
     * border, so the inside and the outside match in both themes.
     */
    gap: 1px;
    background: var(--line);
}
@media (max-width: 620px) { .swatches { grid-template-columns: repeat(5, 1fr); } }

/* inline-block and positioned, so the hidden radio is sized by its own label.
   A <label> is inline by default, and the app's base rule gives every input
   width:100% — against an inline containing block that resolved to the width of
   the whole page, and eight of them stacked up made the settings page 700px
   wider than the window. */
.swatch {
    position: relative;
    display: inline-block;
    cursor: pointer;
    text-align: center;
}
.swatch input {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    opacity: 0;
    margin: 0;
    padding: 0;
    border: 0;
    cursor: pointer;
}

.swatch span {
    display: block;
    width: 100%;
    height: 34px;
    transition: box-shadow 0.14s ease;
}

/*
 * No name under each tile any more.
 *
 * Twenty of them would be a wall of type under a band of colour, and the one
 * that mattered — the name of the chosen one — could not be shown there
 * either: overflow:hidden is what rounds the block, and it clips anything
 * hanging below a tile. The chosen colour is named beside the hex box instead,
 * where the swatch showing it already lives.
 */
.swatch em { display: none; }

.swatch:hover span { box-shadow: inset 0 0 0 2px rgba(255, 255, 255, 0.6); }

/* Drawn inside the tile. An outline would sit on top of the neighbours it is
   flush against, and a tick would cover the one thing you are looking at. */
.swatch input:checked + span {
    box-shadow: inset 0 0 0 3px var(--surface), inset 0 0 0 5px var(--ink);
}
.swatch input:focus-visible + span {
    box-shadow: inset 0 0 0 3px var(--surface), inset 0 0 0 5px var(--accent);
}

/* The colour currently in the box, beside it. */
.swatch-now {
    display: inline-block;
    width: 34px;
    height: 34px;
    border-radius: 8px;
    border: 1px solid var(--line);
}

/* ---------- Storage (Settings) ----------
   Four rows that have to be comparable at a glance, so the bar is the
   comparison and the number is the detail. On a phone the meter is dropped
   rather than squeezed: a two-centimetre bar compares nothing. */

.storage-rows { display: flex; flex-direction: column; gap: 2px; }

.storage-row {
    display: grid;
    grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr) auto;
    align-items: center;
    gap: 14px;
    padding: 11px 0;
    border-bottom: 1px solid var(--line);
}

.storage-row:last-child { border-bottom: 0; }
.storage-row.total { border-top: 2px solid var(--line-strong); border-bottom: 0; padding-top: 12px; }
.storage-label { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.storage-label strong { font-size: 13.5px; }

.storage-meter {
    height: 8px;
    border-radius: 999px;
    background: var(--surface-soft);
    overflow: hidden;
}

.storage-meter span {
    display: block;
    height: 100%;
    border-radius: 999px;
    background: var(--accent);
    min-width: 2px;
}

.storage-figure { text-align: right; display: flex; flex-direction: column; gap: 2px; white-space: nowrap; }
.storage-figure strong { font-size: 14px; font-variant-numeric: tabular-nums; }

@media (max-width: 680px) {
    .storage-row { grid-template-columns: minmax(0, 1fr) auto; }
    .storage-meter { display: none; }
}

/* ---------- Company Costs ----------
   Two charts side by side on a desktop, stacked on anything narrower: a
   doughnut squeezed into half a phone screen is a coloured circle with no
   labels, which tells nobody anything. */

.company-charts {
    display: grid;
    grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr);
    gap: 16px;
    margin-bottom: 16px;
}

@media (max-width: 980px) {
    .company-charts { grid-template-columns: minmax(0, 1fr); }
}

/* The one sentence that answers the question the page was built for. */
.panel.callout-panel {
    border-left: 3px solid var(--accent);
    background: var(--accent-soft);
    padding: 14px 18px;
}

/* The doughnut's legend, as a list — a ring with eight slices needs the
   numbers spelled out beside it or it is decoration. */
.split-list { list-style: none; margin: 14px 0 0; padding: 0; }

.split-list li {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto 44px;
    gap: 10px;
    align-items: baseline;
    padding: 7px 0;
    border-top: 1px solid var(--line);
    font-size: 13px;
}

.split-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.split-value { font-variant-numeric: tabular-nums; font-weight: 600; }
.split-share { text-align: right; color: var(--muted); font-variant-numeric: tabular-nums; }

/* The month picker in a panel header. It wraps rather than pushing the panel
   wider: at 320px the label, the field and the button do not fit on one line,
   and two pixels of horizontal scroll is still horizontal scroll. */
.month-filter { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.month-filter label { font-size: 12.5px; }
.month-filter input { min-width: 0; flex: 1 1 130px; }
