/* ============================================================
   Freelance Hub — app shell styles
   Brand tokens mirror the document identity (brand.css):
   gold small-caps eyebrows, black mark, Poppins throughout.
   ============================================================ */

:root {
    /* ── Fogbank palette (redesign) ──────────────────────────────────
       Muted cool-neutral field; one vibrant ember accent reserved for
       money / attention / "now". Legacy names are kept as aliases so the
       existing rules recolor automatically; new work uses --accent / --c-*. */
    --accent:      #C05F27;   /* ember — primary + money owed / attention */
    --accent-glow: #EE7B3C;   /* vivid ember for marks + glows */
    --glow:        color-mix(in srgb, var(--accent-glow) 32%, transparent);
    --gold:        var(--accent);   /* legacy alias → ember */
    --ink:         #1B1E22;   /* cool near-black */
    --ink-soft:    #535A61;
    --muted:       #889097;
    --line:        #DBDEE2;   /* hairline */
    --line-strong: #C8CCD2;
    --bg:          #EFF3F7;   /* light-mode page ground — soft cool off-white */
    --panel:       #FCFCFD;   /* surface */
    --wash:        #E3E6E9;   /* selection / active wash */
    --danger:      #BF463B;
    --danger-bg:   #F6E7E5;
    --ok:          #3D7A65;   /* muted teal-green — positive */
    --c-in:        #3D7A65; --c-in-fill: #59A184;
    --c-out:       #795070; --c-out-fill: #8A5A80;  /* muted plum — cost */
    /* The tax set-aside on the sheet. Red, because it's money leaving — but a
       duller brick than --danger, which in the same table means late. */
    --sh-tax-ink:  #A15248;
    --radius:      0;
    --radius-sm:   0;
    --shadow:      0 1px 2px rgba(20,24,30,.05);
    --font:        'Space Grotesk', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
    --mono:        'IBM Plex Mono', ui-monospace, 'SF Mono', 'Cascadia Code', Consolas, monospace;
    /* The reading face. Space Grotesk is a DISPLAY face — geometric, single-storey
       'a', tight apertures — which is right for a heading and wrong for thirty
       rows of note text. Data surfaces set their body in this; the page around
       them keeps --font, so the brand is unchanged everywhere you look at it. */
    --font-read:   'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;

    /* ---- Grid tokens: keep every page's columns in lockstep -----------
       Change these once and every matching layout follows.
       --rail-w   : width of the right-hand reference sidebar (detail, docs,
                    sheet, invoices, calendar). One number, so no page drifts.
       --card-min : min width of an auto-fit stat/summary card before it wraps.
       Breakpoints can't use variables inside @media, so the two standard
       stacking widths are hard-coded but kept to a documented pair:
         900px  — content pages (detail, docs, dashboard) stack to 1 column
        1100px  — data-table pages (sheet, invoices, calendar) stack to 1 col */
    --rail-w:      320px;
    --card-min:    150px;
}

* { box-sizing: border-box; }

body {
    margin: 0;
    font-family: var(--font);
    color: var(--ink);
    background: var(--bg);
    line-height: 1.5;
    -webkit-font-smoothing: antialiased;
    /* Turn off the browser's native pull-to-refresh so the pull-to-search
       gesture (pull-search.js) has room to work at the top of the page. */
    overscroll-behavior-y: contain;
}

a { color: var(--ink); text-decoration: none; }
a:hover { color: var(--gold); }

/* ---- Dropdown rows use a system font, on purpose ---------------------
   A <select>'s open list is drawn by the operating system, not by the page,
   and handing that path a downloaded webfont is where things go wrong:
   Firefox on Windows renders the popup rows with mis-mapped glyphs, so a
   project called "Monark&Co — Homepage Video Update" comes out as nonsense
   while the very same text, in the very same font, is perfectly legible in
   the label right above it.

   The closed control keeps the app's typeface — that part is drawn in-page
   and is fine. Only the popup rows, which can't be meaningfully styled
   anyway, drop to fonts the OS is guaranteed to have. */
option, optgroup {
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
}

/* ---- Link affordance -------------------------------------------------
   Base anchors stay undecorated on purpose: the nav shelf, stat tiles,
   whole-row links and button-styled anchors all depend on that. But plain
   *inline text links* inside content were hard to spot, so give those a
   clear, always-on underline. Two targets:
     1. anchors sitting inside running text  (p / li / dd / td / blockquote)
     2. a curated set of named inline content-link components
   Whole-row, card, tile and button anchors are intentionally NOT listed. */
.wrap :is(p, li, dd, td, blockquote) a[href]:not(.btn):not(.em-mini-link),
.wrap a.cl-link,
.wrap a.ci-email,
.wrap a.cl-web,
.wrap a.val-link,
.wrap .wk-inv-num,
.wrap .inv-mini-num,
.wrap .pr-title a[href],
.wrap .sheet-job a[href],
.wrap .jobsum-parts a[href] {
    text-decoration: underline;
    text-decoration-thickness: 1px;
    text-underline-offset: 2px;
    text-decoration-color: color-mix(in srgb, currentColor 40%, transparent);
}
.wrap :is(p, li, dd, td, blockquote) a[href]:not(.btn):not(.em-mini-link):hover,
.wrap a.cl-link:hover, .wrap a.ci-email:hover, .wrap a.cl-web:hover,
.wrap a.val-link:hover,
.wrap .wk-inv-num:hover, .wrap .inv-mini-num:hover {
    text-decoration-color: currentColor;
}

.eyebrow {
    font-family: var(--mono);
    font-size: .64rem;
    letter-spacing: .16em;
    text-transform: uppercase;
    color: var(--gold);
    font-weight: 600;
}
/* Breadcrumb eyebrow (detail pages): back-link + context, both gold. */
.crumb { display: inline-flex; align-items: center; gap: .4rem; flex-wrap: wrap; }
.crumb a { color: var(--gold); }
.crumb a:hover { text-decoration: underline; }
.crumb-sep { color: var(--muted); }

/* ---- Top bar ------------------------------------------------ */
.topbar {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: .6rem 1.25rem;
    padding: .8rem 1.25rem;
    background: var(--ink);
    color: #fff;
}
/* Desktop: keep everything on a single row (the wrap above is only for the
   mobile header, which reflows the nav to its own full-width rows). */
@media (min-width: 769px) {
    .topbar { flex-wrap: nowrap; }
}
.topbar .brand { color: #fff; font-weight: 700; letter-spacing: .02em; white-space: nowrap; }
.topbar .brand:hover { color: var(--gold); }
.topbar .brand.active { color: var(--gold); }
.topnav { display: flex; gap: 1.25rem; font-size: .92rem; }
.topnav a { color: #eee; padding: .25rem .7rem; border-radius: 0; }
.topnav a:hover { color: var(--gold); }
.topnav a.active { background: rgba(238,123,60,.18); color: var(--gold); font-weight: 500; }
.nav-muted { color: #7a7a7a; cursor: default; }
/* Hamburger — hidden on desktop, shown in the mobile header query below. */
.nav-toggle { display: none; background: none; border: 0; cursor: pointer; padding: .5rem; border-radius: 0; }
.nav-toggle:hover { background: rgba(255,255,255,.08); }
.nav-toggle-bars, .nav-toggle-bars::before, .nav-toggle-bars::after {
    display: block; width: 22px; height: 2px; background: #eee; border-radius: 2px; transition: background .15s;
}
.nav-toggle-bars { position: relative; }
.nav-toggle-bars::before, .nav-toggle-bars::after { content: ""; position: absolute; left: 0; }
.nav-toggle-bars::before { top: -6px; }
.nav-toggle-bars::after { top: 6px; }
.nav-toggle[aria-expanded="true"] .nav-toggle-bars { background: var(--gold); }
.nav-toggle[aria-expanded="true"] .nav-toggle-bars::before,
.nav-toggle[aria-expanded="true"] .nav-toggle-bars::after { background: var(--gold); }
.linkbtn {
    background: none; border: 0; color: var(--gold);
    font: inherit; cursor: pointer; padding: 0;
}
.linkbtn:hover { text-decoration: underline; }

/* ============================================================
   LEFT NAV SHELF (desktop ≥769px) — replaces the top bar.
   The .topbar above is retained as the MOBILE header (paired with
   the floating bottom nav) and hidden on desktop. Full-width data
   pages add body.is-wide, collapsing the shelf to an icon rail with
   hover tooltips so the wide tables get the whole canvas.
   ============================================================ */
.shelf { display: none; }               /* mobile: hidden; topbar + bottom nav carry it */
.app-body { min-width: 0; }

@media (min-width: 769px) {
    body.app { display: grid; grid-template-columns: var(--shelf-w, 232px) 1fr; align-items: start; }
    .app-body > .topbar { display: none; }         /* topbar is mobile-only on desktop */
    .shelf {
        display: flex; flex-direction: column;
        position: sticky; top: 0; height: 100vh; overflow: hidden;
        background: var(--panel); border-right: 1px solid var(--line);
    }
}

.shelf-brand { display: flex; align-items: center; gap: .6rem; padding: 1.1rem 1.15rem 1rem; border-bottom: 1px solid var(--line); }
.shelf-mark { width: 30px; height: 30px; flex: none; border-radius: 6px; background: var(--ink); color: var(--panel);
    display: grid; place-items: center; font-family: var(--mono); font-weight: 700; font-size: .8rem; text-decoration: none; }
.shelf-mark:hover { background: var(--accent); color: #fff; }
.shelf-mark.has-logo { background: transparent; padding: 0; border-radius: 0; overflow: hidden; }
.shelf-mark.has-logo:hover { background: transparent; }
.shelf-mark.has-logo img { width: 100%; height: 100%; object-fit: cover; display: block; }
.shelf-name { font-weight: 600; font-size: .92rem; letter-spacing: -.01em; color: var(--ink); }
.shelf-sub { font-family: var(--mono); font-size: .55rem; letter-spacing: .14em; text-transform: none; color: var(--muted); margin-top: .12rem; }

.shelf-nav { flex: 1 1 auto; overflow-y: auto; padding: .35rem 0 1rem; }
.shelf-group { padding: 1rem 0 .2rem; }
.shelf-group + .shelf-group { border-top: 1px solid var(--line); }
.shelf-group-label { font-family: var(--mono); font-size: .57rem; letter-spacing: .18em; text-transform: uppercase; color: var(--muted); padding: 0 1.2rem .5rem; }
.sh-item { display: flex; align-items: center; gap: .72rem; padding: .58rem 1.2rem; position: relative; color: var(--ink-soft); font-family: var(--mono); font-size: .82rem; letter-spacing: -.01em; transition: color .12s, background .12s; }
.sh-item:hover { color: var(--ink); background: var(--wash); }
.sh-ico { width: 17px; height: 17px; flex: none; color: var(--muted); }
.sh-item:hover .sh-ico { color: var(--ink); }
.sh-item.active { color: var(--ink); font-weight: 600; background: var(--wash); }
.sh-item.active::before { content: ""; position: absolute; left: 0; top: .3rem; bottom: .3rem; width: 2.5px; background: var(--accent-glow); box-shadow: 0 0 9px var(--glow); }
.sh-item.active .sh-ico { color: var(--accent); }

.shelf-foot { border-top: 1px solid var(--line); padding: .8rem 1rem .9rem; }
.shelf-payout { margin-bottom: .65rem; font-size: .78rem; color: var(--ink-soft); }
/* The payout pill is built for the horizontal top bar (nowrap). In the narrow
   left shelf that gets clipped by the shelf's overflow:hidden — so here let it
   fill the width and wrap: "Next invoice" on its own line, amount + due below. */
.shelf-payout .next-payout { display: flex; flex-wrap: wrap; align-items: baseline; white-space: normal; width: 100%; row-gap: .1rem; }
.shelf-payout .next-payout .np-label { flex-basis: 100%; display: block; }
.shelf-user { display: flex; align-items: center; gap: .5rem; }
.shelf-ava { width: 24px; height: 24px; border-radius: 50%; flex: none; background: var(--ink-soft); color: var(--panel);
    display: grid; place-items: center; font-family: var(--mono); font-size: .56rem; font-weight: 700; }
.shelf-user-txt { font-size: .8rem; color: var(--ink-soft); }

/* ---- The nav shelf is ALWAYS dark — a pro cockpit rail against the light app.
   Re-point the neutral tokens to a dark scale inside the shelf so every existing
   rule (which reads --panel / --ink / --line / --muted) flips automatically.
   The accent (ember) is left as-is; it reads well on the dark ground. ---- */
.shelf {
    --panel:       #17181C;
    --ink:         #ECEDEF;
    --ink-soft:    #B7BAC0;
    --muted:       #7C808A;
    --line:        rgba(255,255,255,.09);
    --line-strong: rgba(255,255,255,.16);
    --wash:        rgba(238,123,60,.16);
    background:    #17181C;
    color:         var(--ink);
}
.shelf-mark { background: var(--accent-glow); color: #17181C; }   /* ember chip pops on dark */

/* Collapsed icon rail. A page can PREFER the rail (body.rail), but the rail is
   only actually applied in the mid-desktop band (769–1199px) — which is where a
   portrait/vertical monitor lands. At ≥1200px there's room for the full shelf,
   so it always stays out regardless of the page.

   Compressed, the rail is icons only — so hovering it (or tabbing into it)
   FLOATS the full shelf back out over the page. The grid column stays 66px, so
   nothing on the page moves; the shelf lifts above it and the names come back.

   The rule that keeps that from looking cheap: the two states may differ ONLY in
   ways a browser can animate. So no display:none anywhere — every label stays in
   the layout at its full-open size (nowrap, so it never rewraps mid-slide) and
   is simply faded out and clipped by the shelf's own overflow. Nothing reflows
   while the width animates; the text just isn't there yet. The few things that
   do move — nav icons and the avatar, which sit centred in the rail and settle
   into their column when it opens — move by transitioning padding, which glides.
   The brand mark doesn't move at all: at 66px the shelf's normal left padding
   already centres it, so it's left alone. */
@media (min-width: 769px) and (max-width: 1199px) {
    body.app.rail { --shelf-w: 66px; --shelf-full: 232px; --sh-open: .13s; }

    body.app.rail .shelf {
        width: var(--shelf-full); z-index: 100;
        transition: width .2s cubic-bezier(.4, 0, .2, 1), box-shadow .2s ease;
    }
    /* A mouse only passing through shouldn't pop it out, so opening waits out a
       short delay. Closing is immediate — you've already left. */
    body.app.rail .shelf:hover { transition-delay: var(--sh-open); }
    body.app.rail .shelf:hover,
    body.app.rail .shelf:focus-within { box-shadow: 14px 0 34px rgba(0, 0, 0, .28); }
    body.app.rail .shelf:not(:hover):not(:focus-within) { width: var(--shelf-w); }
    body.app.rail .shelf-nav { overflow-x: hidden; }   /* labels overhang while shut */

    /* ---- the words: held in place, faded, clipped ---- */
    body.app.rail .shelf-id,
    body.app.rail .shelf-group-label,
    body.app.rail .sh-label,
    body.app.rail .shelf-payout,
    body.app.rail .shelf-user-txt {
        white-space: nowrap;
        transition: opacity .16s ease calc(var(--sh-open) + .05s),
                    transform .24s cubic-bezier(.4, 0, .2, 1) calc(var(--sh-open) + .05s);
    }
    body.app.rail .sh-label,
    body.app.rail .shelf-user-txt { display: inline-block; }
    /* The payout pill is the one thing that WOULD reflow: at 66px it wraps into
       a tall stack. So it's laid out at the open width and folded shut by height
       instead. The foot is bottom-anchored, so folding it takes the hairline up
       and down — your name and avatar never move. */
    body.app.rail .shelf-payout {
        width: calc(var(--shelf-full) - 2rem); overflow: hidden; max-height: 4rem;
        transition: opacity .16s ease calc(var(--sh-open) + .05s),
                    transform .24s cubic-bezier(.4, 0, .2, 1) calc(var(--sh-open) + .05s),
                    max-height .24s cubic-bezier(.4, 0, .2, 1) var(--sh-open),
                    margin .24s cubic-bezier(.4, 0, .2, 1) var(--sh-open);
    }
    body.app.rail .shelf:not(:hover):not(:focus-within) .shelf-payout { max-height: 0; margin-bottom: 0; }

    body.app.rail .shelf:not(:hover):not(:focus-within) .shelf-id,
    body.app.rail .shelf:not(:hover):not(:focus-within) .shelf-group-label,
    body.app.rail .shelf:not(:hover):not(:focus-within) .sh-label,
    body.app.rail .shelf:not(:hover):not(:focus-within) .shelf-payout,
    body.app.rail .shelf:not(:hover):not(:focus-within) .shelf-user-txt {
        opacity: 0; transform: translateX(-5px);
        transition-duration: .1s, .1s; transition-delay: 0s, 0s;
    }

    /* ---- the two things that move, and they glide ---- */
    body.app.rail .sh-item,
    body.app.rail .shelf-foot { transition: padding .2s cubic-bezier(.4, 0, .2, 1); }
    body.app.rail .shelf:not(:hover):not(:focus-within) .sh-item { padding-left: 24.5px; }
    body.app.rail .shelf:not(:hover):not(:focus-within) .shelf-foot { padding-left: 1.31rem; }

    @media (prefers-reduced-motion: reduce) {
        body.app.rail .shelf,
        body.app.rail .sh-item,
        body.app.rail .shelf-foot,
        body.app.rail .shelf-id,
        body.app.rail .shelf-group-label,
        body.app.rail .sh-label,
        body.app.rail .shelf-payout,
        body.app.rail .shelf-user-txt { transition: none; }
    }
}

/* ---- Layout ------------------------------------------------- */
.wrap { max-width: 960px; margin: 0 auto; padding: 1.5rem 1.5rem; }
.page-head { margin-bottom: 1.25rem; }
.page-head h1 { margin: .25rem 0 .5rem; font-size: 1.9rem; }
.muted { color: var(--muted); }
/* Said aloud by a screen reader, invisible on screen — for the times a mark is
   carried by colour or shape alone (the "new mail" dot, for one). */
.sr-only { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
           overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; border: 0; }

/* ---- Cards -------------------------------------------------- */
.cards {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(var(--card-min), 1fr));
    gap: 1rem;
    margin-bottom: 1.25rem;
}
.card {
    background: var(--panel);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    padding: 1.1rem 1.25rem;
    box-shadow: var(--shadow);
}
.card-label { font-size: .75rem; text-transform: none; letter-spacing: .08em; color: var(--muted); }
.card-value { font-size: 1.8rem; font-weight: 600; margin-top: .3rem; }
.card-value.env-local { color: var(--ok); }
.card-value.env-dev   { color: var(--gold); }
.card-value.env-live  { color: var(--danger); }

/* ---- Panel + lists ------------------------------------------ */
.panel {
    background: var(--panel);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    padding: 1.5rem;
    box-shadow: var(--shadow);
}
.panel h2 { margin-top: 0; font-size: 1.15rem; }
.checklist { list-style: none; padding: 0; margin: 0; }
.checklist li { padding: .4rem 0 .4rem 1.6rem; position: relative; color: var(--ink-soft); }
.checklist li::before {
    content: '✓'; position: absolute; left: 0; color: var(--gold); font-weight: 700;
}
code {
    background: #eef1f5; padding: .1em .4em; border-radius: 4px;
    font-family: ui-monospace, 'SF Mono', Menlo, Consolas, monospace; font-size: .88em;
}

/* ---- Login -------------------------------------------------- */
.login-card {
    max-width: 380px;
    margin: 8vh auto 0;
    background: var(--panel);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    padding: 2.25rem 2rem;
    box-shadow: var(--shadow);
}
.login-card h1 { margin: .3rem 0 1.25rem; font-size: 1.5rem; }
.stack { display: flex; flex-direction: column; gap: 1rem; }
.stack label { display: flex; flex-direction: column; gap: .35rem; font-size: .85rem; color: var(--ink-soft); }
.stack input {
    font: inherit; padding: .6rem .7rem;
    border: 1px solid var(--line); border-radius: 0; background: #fff;
}
.stack input:focus { outline: 2px solid var(--gold); outline-offset: 1px; border-color: var(--gold); }

/* "Trust this device" opt-in on the 2FA code screen: checkbox beside its text,
   not stacked above it like the other .stack labels. */
.stack label.tfa-trust { flex-direction: row; align-items: flex-start; gap: .55rem; line-height: 1.35; }
.stack label.tfa-trust input { width: auto; margin-top: .1rem; flex: 0 0 auto; }

.btn {
    font: inherit; font-weight: 600; cursor: pointer;
    padding: .65rem 1rem; border-radius: 0; border: 1px solid transparent;
}
.btn-primary { background: var(--ink); color: #fff; }
/* A generic primary action lifts to a lighter ink on hover — it must NOT flash
   the ember money accent (that color is reserved for money/attention). */
.btn-primary:hover { background: var(--ink-soft); color: #fff; }

/* ---- Alerts ------------------------------------------------- */
.alert { padding: .7rem .9rem; border-radius: 0; font-size: .9rem; margin-bottom: 1rem; }
.alert-error { background: var(--danger-bg); color: var(--danger); border: 1px solid #eecaca; }
.alert-ok { background: #e7efe9; color: var(--ok); border: 1px solid #c9ddce; }
.alert.has-undo { display: flex; align-items: center; justify-content: space-between; gap: 1rem; }
.undo-form { margin: 0; }
.undo-btn { font-weight: 600; text-decoration: underline; white-space: nowrap; }

/* ---- Wider wrap for the app screens ------------------------- */
.wrap { max-width: 1080px; }
/* The Sheet view opens the canvas up so more columns fit on desktop. */
.wrap-wide { max-width: min(96vw, 1600px); }
/* Wider still, for a page that IS a table. A sheet has no measure to keep —
   there is no column of prose to hold to 68 characters — so the useful width
   is the window, and every pixel not spent sideways is spent scrolling. */
.wrap-full { max-width: none; padding-left: 1.1rem; padding-right: 1.1rem; }

/* ---- Projects header + month nav ---------------------------- */
.projects-head, .detail-head { display: flex; align-items: flex-end; justify-content: space-between; gap: 1rem; }
.detail-head { align-items: center; }   /* keep the status control in line with the title */

/* Project actions: a simple row of flat, square, hairline-outlined buttons.
   No connecting band and no colored ribbon — the semantic accent shows only on
   hover, so the resting state stays quiet. */
.detail-actions { display: flex; flex-wrap: wrap; gap: .5rem; margin: .25rem 0 1.4rem; }
.detail-actions form { margin: 0; }
.detail-actions .btn {
    --act: var(--gold);
    border: 1px solid var(--line); border-radius: 0;
    background: var(--panel); color: var(--ink-soft); font-weight: 500; font-size: .82rem;
    padding: .55rem 1rem; white-space: nowrap; transition: background .12s, color .12s, border-color .12s;
}
.detail-actions .act-ember { --act: #C05F27; }
.detail-actions .act-green { --act: #1E8F73; }
.detail-actions .act-blue  { --act: #2F6FB0; }
.detail-actions .act-plum  { --act: #8E3A7E; }
.detail-actions .btn:hover { border-color: var(--act); color: var(--act); background: color-mix(in srgb, var(--act) 7%, transparent); }
/* Delete keeps its danger affordance even inside the action row. */
.detail-actions .btn-danger { background: var(--danger-bg); color: var(--danger); border-color: #eecaca; }
.detail-actions .btn-danger:hover { background: var(--danger); color: #fff; border-color: var(--danger); }
/* Status control sits at the right end of the action band, sized to match the
   action buttons. */
.detail-status-inline { margin-left: auto; display: inline-flex; align-items: center; }
.detail-status-inline .status-select { font-size: .82rem; font-weight: 500; padding: .55rem 1rem; border-radius: 0; }
/* Client icon in the detail header links through to the client page. */
.detail-logo-link { display: inline-flex; flex: none; text-decoration: none; }
.detail-logo-link:hover .detail-logo { border-color: var(--line-strong); }
.detail-logo-link:hover .detail-logo--mono { opacity: .88; }

/* ============================================================
   TIERED DETAIL PATTERN  (client + project detail)
   Redistributes weight so a page reads in three levels: the money
   summary leads (the one lifted block), sections become labelled
   landmarks on the page ground, and rows stay boxless underneath.
   Scoped to the detail pages so the rest of the app is untouched.
   ============================================================ */

/* TIER 1 — the single lifted block: the money read, up top. Ember ribbon,
   big mono figure. This is the "so what?" answered before the lists. */
.detail-summary {
    background: var(--panel); border: 1px solid var(--line); border-top: 3px solid var(--accent);
    padding: 1.05rem 1.3rem 1.15rem; margin: 0 0 1.6rem;
    display: flex; flex-wrap: wrap; align-items: flex-end; gap: 1rem 2.4rem;
}
.ds-hero { display: flex; flex-direction: column; gap: .15rem; }
.ds-k { font-family: var(--mono); font-size: .62rem; letter-spacing: .13em; text-transform: uppercase; color: var(--muted); }
.ds-v { font-family: var(--mono); font-size: clamp(1.7rem, 5vw, 2.15rem); font-weight: 600; line-height: 1; letter-spacing: -.02em; font-variant-numeric: tabular-nums; color: var(--ink); }
.ds-v.is-money { color: var(--accent); }
.ds-stats { display: flex; flex-wrap: wrap; gap: .9rem 1.9rem; margin-left: auto; }
.ds-stat { display: flex; flex-direction: column; gap: .1rem; }
.ds-sk { font-family: var(--mono); font-size: .6rem; letter-spacing: .11em; text-transform: uppercase; color: var(--muted); }
.ds-sv { font-family: var(--mono); font-size: 1.02rem; font-weight: 600; color: var(--ink); font-variant-numeric: tabular-nums; }
.ds-sv a { color: var(--ink); }
.ds-sv.warn { color: var(--accent); }
/* Under the money block on a job that spans several same-name bookings: says the
   headline figure is the whole job, not the week you clicked in from. Pulled up
   under the block so it reads as its footnote rather than a stray line. */
.ds-jobnote { margin: -1.15rem 0 1.6rem; font-size: .78rem; line-height: 1.5; color: var(--muted); }
.ds-jobnote a { color: var(--muted); text-decoration: underline; text-underline-offset: 2px; }
.ds-jobnote a:hover { color: var(--accent); }

/* TIER 2 — the reference rail's sections sit on the page ground: on desktop the
   rail as a whole IS one sheet (see the @media below), so a card per section
   inside it would be a box in a box.
   The MAIN column goes the other way — every section there is its own white
   sheet. That rule lives further down, with the section-head colours it pairs
   with; search "SHEETS + COLOURED SECTION HEADS". (Files/Config keep the sheet
   but flattened — shadow off — for now.) */
.client-detail .cd-side > .panel,
.project-detail .cd-side > .panel {
    background: transparent; border: 0; box-shadow: none;
    padding: 0;
}
.client-detail .cd-main, .client-detail .cd-side,
.project-detail .cd-main, .project-detail .cd-side { gap: 1.9rem; }
/* Desktop (two-column): the reference rail becomes ONE white sheet so the left
   and right columns read as distinct — main content on the ground, rail beside
   it as a card. On mobile (single column) the rail stays de-boxed on the ground,
   which reads fine stacked. */
@media (min-width: 901px) {
    .client-detail .cd-side,
    .project-detail .cd-side {
        background: var(--panel); border: 1px solid var(--line);
        padding: 1.3rem 1.35rem; gap: 1.45rem; align-self: start;
    }
}
.docs .panel,
.config-page .panel { box-shadow: none; }
/* The section head is now the only thing marking a group, so give its rule a bit
   more presence than the in-card hairline it replaces. */
.client-detail .cd-main > .panel > .panel-head,
.client-detail .cd-side > .panel > .panel-head,
.client-detail .cd-main > .panel > h2,
.client-detail .cd-side > .panel > h2,
.project-detail .cd-main > .panel > .panel-head,
.project-detail .cd-side > .panel > .panel-head,
.project-detail .cd-main > .panel > h2,
.project-detail .cd-side > .panel > h2 { border-bottom-color: var(--ink); }
.client-detail .panel > h2,
.client-detail .panel-head h2,
.project-detail .panel > h2,
.project-detail .panel-head h2,
.docs .panel > h2,
.docs .panel-head h2,
.config-page .panel > h2 { font-size: 1.24rem; letter-spacing: -.01em; }
/* On a phone the section head is the landmark the thumb scans for, so keep it a
   clear step above body (overrides the general mobile shrink via higher
   specificity). */
@media (max-width: 768px) {
    .client-detail .cd-main .panel h2, .client-detail .cd-side .panel h2,
    .project-detail .cd-main .panel h2, .project-detail .cd-side .panel h2,
    .docs .docs-main .panel h2, .docs .docs-side .panel h2,
    .config-page .panel h2 { font-size: 1.08rem; }
}

/* Action hierarchy: one filled primary, ghost secondary, and the rare
   destructive/secondary actions tucked behind a "More" disclosure. */
.detail-actions .btn-primary { background: var(--ink); color: #fff; border-color: var(--ink); font-weight: 600; }
.detail-actions .btn-primary:hover { background: var(--ink-soft); color: #fff; border-color: var(--ink-soft); }
.actions-more { position: relative; margin-left: .1rem; }
.actions-more > summary {
    list-style: none; cursor: pointer; border: 1px solid var(--line); background: var(--panel);
    color: var(--ink-soft); font-size: .82rem; font-weight: 500; padding: .55rem .8rem; white-space: nowrap;
}
.actions-more > summary::-webkit-details-marker { display: none; }
.actions-more > summary:hover { border-color: var(--line-strong); color: var(--ink); }
.actions-more[open] > summary { border-color: var(--line-strong); color: var(--ink); }
.actions-more-menu {
    position: absolute; right: 0; top: calc(100% + 4px); z-index: 30; min-width: 168px;
    background: var(--panel); border: 1px solid var(--line-strong); box-shadow: 0 8px 24px rgba(20,24,30,.14);
    display: flex; flex-direction: column; padding: .3rem;
}
.actions-more-menu form { margin: 0; }
.actions-more-menu .btn, .actions-more-menu button, .actions-more-menu a {
    display: block; width: 100%; text-align: left; border: 0; background: none; color: var(--ink-soft);
    font: inherit; font-size: .84rem; font-weight: 500; padding: .5rem .6rem; cursor: pointer; text-decoration: none;
}
.actions-more-menu .btn:hover, .actions-more-menu button:hover, .actions-more-menu a:hover { background: var(--wash); color: var(--ink); }
.actions-more-menu .danger:hover, .actions-more-menu .btn-danger:hover { background: var(--danger-bg); color: var(--danger); }
.actions-more-menu .menu-sep { border-top: 1px solid var(--line); margin: .3rem 0; }
.actions-more-menu .menu-lbl { font-family: var(--mono); font-size: .6rem; letter-spacing: .1em; text-transform: uppercase; color: var(--muted); padding: .45rem .6rem .2rem; }
.actions-more-menu .menu-hint { font-size: .8rem; color: var(--muted); padding: .5rem .6rem; }

/* Small "+ add" action that sits in a de-boxed section head (New project, etc.). */
.sec-add { font-family: var(--mono); font-size: .68rem; font-weight: 600; letter-spacing: .04em;
    text-transform: uppercase; color: var(--ink-soft); border: 1px solid var(--line-strong);
    padding: .28rem .6rem; white-space: nowrap; text-decoration: none; background: var(--panel); }
.sec-add:hover { border-color: var(--ink); color: var(--ink); }
.panel-head-right { display: inline-flex; align-items: center; gap: .8rem; }
/* The "New document" dropdown sits mid-row in the header, so its menu opens
   left-aligned under the trigger (not right-anchored like the end "More"). */
.actions-new-doc .actions-more-menu { left: 0; right: auto; min-width: 210px; }

/* Header action band on small screens: the five actions (Draft email / New
   project / New document / Edit / More) wrap to neat rows, and the dropdown
   triggers match the app's standard mobile button size so the row reads as one
   set rather than mismatched sizes. */
@media (max-width: 640px) {
    .detail-actions { gap: .4rem; }
    .detail-actions > .actions-more > summary { padding: .5rem .85rem; font-size: .88rem; }
}
@media (max-width: 380px) {
    /* On the narrowest phones let the menu size to the viewport, not overflow it. */
    .detail-actions .actions-more-menu { min-width: min(220px, calc(100vw - 2.4rem)); }
}
.month-nav { display: flex; align-items: center; gap: .6rem; }
.month-nav h1 { margin: 0; font-size: 1.7rem; }
.head-actions { display: flex; gap: .6rem; align-items: center; }
.chip {
    display: inline-flex; align-items: center; justify-content: center;
    width: 2rem; height: 2rem; border: 1px solid var(--line); border-radius: 0;
    background: #fff; font-size: 1.1rem; line-height: 1;
}
.chip:hover { border-color: var(--gold); color: var(--gold); }
.kbd {
    display: inline-block; margin-left: .35rem; padding: 0 .35rem;
    border: 1px solid rgba(255,255,255,.4); border-radius: 4px; font-size: .7rem; opacity: .8;
}

.card-value.paid { color: var(--ok); }
.card-value.outstanding { color: var(--accent); }
.card-booked { border-left: 3px solid var(--accent); }
.card-net { font-size: 1rem; font-weight: 600; color: var(--ok); margin-top: .25rem; }

/* ---- Progress bar ------------------------------------------- */
.progress-wrap { margin: 0 0 1rem; }
.progress-top { display: flex; justify-content: space-between; font-size: .85rem; margin-bottom: .4rem; color: var(--ink-soft); }
.progress-bar { height: 8px; background: #e6e9ee; border-radius: 0; overflow: hidden; }
.progress-fill { height: 100%; background: linear-gradient(90deg, var(--accent), var(--accent-glow)); border-radius: 0; transition: width .3s; }

/* ---- View / span toggles ------------------------------------ */
.view-toggle { display: flex; gap: .75rem; margin-bottom: .75rem; }
.seg { display: inline-flex; border: 1px solid var(--line); border-radius: 0; overflow: hidden; background: #fff; }
.seg a { padding: .4rem .85rem; font-size: .85rem; color: var(--ink-soft); }
.seg a.on { background: var(--ink); color: #fff; }
.seg a:hover:not(.on) { color: var(--gold); }
/* The year the whole Projects screen is counted in. Mono, because it's a figure
   the totals below depend on rather than a place to go — and it sits apart from
   the List/Sheet switch so the two reads don't blur into one control. */
.seg-years { margin-left: auto; font-family: var(--mono); }
.seg-years a { font-size: .78rem; letter-spacing: .03em; padding: .4rem .7rem; }

/* ---- "This month" strip + runway line (List view) ----------- */
/* A <details> wrapper: always expanded on desktop, tap-to-collapse on mobile. */
.strip-collapse { margin: 0 0 .25rem; }
.strip-collapse > summary { list-style: none; }
.strip-collapse > summary::-webkit-details-marker { display: none; }
.strip-head { display: flex; align-items: baseline; gap: .6rem; margin: 0 0 .5rem; }
.strip-title { font-weight: 600; font-size: .95rem; }
.strip-chev { display: none; }
.runway-line {
    display: flex; flex-wrap: wrap; align-items: baseline; gap: .35rem .7rem;
    font-size: .88rem; margin: .6rem 0 1.25rem;
}
/* Desktop: the cards are always shown; the summary is just a label. */
@media (min-width: 769px) {
    .strip-collapse > .strip-body { display: block; }
    .strip-collapse > summary { cursor: default; }
}
/* Mobile: collapsed by default (no [open]); tap the header to expand. */
@media (max-width: 768px) {
    .strip-collapse > summary { cursor: pointer; }
    .strip-chev { display: inline-block; margin-left: auto; color: var(--muted); transition: transform .15s; }
    .strip-collapse[open] .strip-chev { transform: rotate(180deg); }
}

/* ---- Project list, grouped by month ------------------------- */
.project-group { margin-bottom: 1.4rem; }
.pg-head {
    display: flex; align-items: baseline; gap: .55rem;
    margin: 0 0 .55rem; font-size: .95rem; font-weight: 600;
    padding-bottom: .35rem; border-bottom: 1px solid var(--line);
}
.pg-tag {
    font-family: var(--mono); font-size: .56rem; font-weight: 600; text-transform: uppercase; letter-spacing: .08em;
    color: #fff; background: var(--accent); border-radius: 2px; padding: .12rem .4rem;
    align-self: center;
}
.pg-count { margin-left: auto; font-size: .78rem; font-weight: 400; color: var(--muted); }
.project-group.is-current .pg-label { color: var(--gold-ink, var(--ink)); }
.project-group.is-past { opacity: .78; }

.project-list { display: flex; flex-direction: column; gap: 0; }
.project-row {
    display: grid;
    grid-template-columns: auto 1fr auto auto auto;
    grid-template-areas: "logo main meta value status" "logo inv inv inv inv";
    gap: .4rem 1.25rem; align-items: center;
    background: transparent; border: 0; border-bottom: 1px solid var(--line); border-radius: 0;
    padding: .85rem .5rem; box-shadow: none; transition: background .12s;
}
.project-row:hover { background: var(--panel); }
.pr-avatar { grid-area: logo; width: 34px; height: 34px; border-radius: 0; object-fit: cover; border: 1px solid var(--line); background: #fff; align-self: start; }
.pr-avatar--mono { display: inline-flex; align-items: center; justify-content: center; background: var(--ink); color: #fff; font-weight: 600; border: 0; }
.pr-main { grid-area: main; min-width: 0; }
/* Keep the job title + client on ONE line as the window narrows: the title
   takes the room and ellipsizes; the client holds a capped share beside it,
   instead of the client wrapping to an awkward indented second line. */
.pr-title { display: flex; align-items: baseline; gap: .5rem; min-width: 0; }
.pr-title a { font-weight: 600; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pr-client { font-family: var(--mono); color: var(--muted); font-size: .74rem; flex: none;
             max-width: 45%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.pr-meta { grid-area: meta; font-family: var(--mono); font-size: .72rem; color: var(--muted); text-align: right; white-space: nowrap; }
.pr-meta .dot { color: var(--line); margin: 0 .3rem; }
.pr-value { grid-area: value; font-family: var(--mono); font-weight: 600; font-variant-numeric: tabular-nums; }
.pr-status { grid-area: status; }
.pr-invoices { grid-area: inv; display: flex; flex-wrap: wrap; gap: .4rem; align-items: center; padding-top: .3rem; border-top: 1px dashed var(--line); }

/* ---- Project sheet (spreadsheet view) — redesign -----------
   A flat hairline ledger: no zebra, no vertical rules, mono numerals and
   mono uppercase column labels. Ember is held back for the pinned row only. */
.sheet-note { font-size: .82rem; color: var(--muted); margin: 0 0 .8rem; }
.sheet-wrap {
    overflow-x: auto;
    background: var(--panel);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    box-shadow: none;
}
.project-sheet { width: 100%; border-collapse: collapse; font-size: .82rem; }
.project-sheet th, .project-sheet td {
    padding: .55rem .8rem;
    border-bottom: 1px solid var(--line);
    text-align: left; white-space: nowrap; vertical-align: middle;
}
/* Column headers are the ledger's instrument labels: mono, small, all-caps. */
.project-sheet thead th {
    position: sticky; top: 0; z-index: 2;
    background: var(--panel); color: var(--muted);
    font-family: var(--mono);
    font-size: .62rem; text-transform: uppercase; letter-spacing: .12em; font-weight: 600;
    padding-top: .7rem; padding-bottom: .7rem;
    border-bottom: 1px solid var(--line-strong);
}
.project-sheet tbody tr:hover { background: var(--wash); }
.project-sheet .num { text-align: right; font-family: var(--mono); font-variant-numeric: tabular-nums; }
.project-sheet .muted-num { color: var(--muted); }
.project-sheet .sheet-date { color: var(--ink-soft); font-family: var(--mono); font-size: .76rem; }
.project-sheet .sheet-rate { color: var(--ink-soft); font-family: var(--mono); font-size: .78rem; }
.project-sheet .sheet-job a { font-weight: 600; }
.sheet-client { display: inline-flex; align-items: center; gap: .45rem; }
.sheet-client img, .sheet-client .mono {
    width: 22px; height: 22px; border-radius: 0; object-fit: cover;
    border: 1px solid var(--line); background: #fff; flex: none;
}
.sheet-client .mono {
    display: inline-flex; align-items: center; justify-content: center;
    background: var(--ink); color: #fff; font-weight: 600; font-size: .72rem; border: 0;
}
.project-sheet .status-select { font-size: .74rem; padding: .2rem .4rem; }
.sheet-client { color: var(--ink); }
.sheet-client:hover { color: var(--gold); }

/* Narrow window: the mark carries the client on its own. A logo beside a name
   is a belt-and-braces label at full width, but in a squeezed Client column the
   name wraps under its own logo and the row stops reading as a row. Below this
   the type steps out and the mark speaks — still announced to screen readers,
   and the whole cell still links to the client. */
@media (max-width: 900px) {
    .project-sheet .sheet-client-name {
        position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
        overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; border: 0;
    }
    .project-sheet .sheet-client { gap: 0; position: relative; }
}
/* Payment rollup badge (replaces the invoice chips in this dense view): a
   square mono micro-tag, matching the in-card labels/tags elsewhere. */
.pay-badge { display: inline-block; font-family: var(--mono); font-size: .62rem; font-weight: 600; text-transform: uppercase; letter-spacing: .06em; padding: .22rem .5rem; border-radius: 0; border: 1px solid var(--line); color: var(--muted); background: transparent; }
.pay-paid    { background: #e7efe9; color: var(--ok); border-color: #c9ddce; }
.pay-partial { background: #f4e8dd; color: #9a6a3a; border-color: #e4d0bf; }
.pay-sent    { background: #e9ecf1; color: #4a5c74; border-color: #ccd3de; }
.pay-draft   { background: #eef1f5; color: var(--ink-soft); }
.pay-none    { border-style: dashed; color: var(--muted); }
.project-sheet tfoot td {
    position: sticky; bottom: 0; z-index: 1;
    background: var(--panel); font-weight: 600;
    border-top: 2px solid var(--line-strong); border-bottom: 0;
    font-family: var(--mono);
}
.project-sheet tfoot td:first-child { font-family: var(--font); }   /* "Totals · N jobs" is prose */
/* The totals' working: confirmed and pencilled, under the grand total. A size
   down and no top rule, so the three read as one block rather than as three
   competing bottom lines — and the pencilled row goes quiet, because it's the
   part the Booked card deliberately doesn't count. */
.project-sheet tfoot tr.sheet-foot-split td { border-top: 0; font-weight: 400; font-size: .74rem;
    padding-top: .28rem; padding-bottom: .28rem; color: var(--ink-soft); }
.project-sheet tfoot tr.sheet-foot-split td:first-child { padding-left: 1.4rem; }
.project-sheet tfoot tr.sheet-foot-split--soft td { color: var(--muted); }
.project-sheet tfoot tr.sheet-foot-split--soft td:last-child { font-style: italic; }
/* Row lit up from a calendar-day hover (the reverse of row → calendar). */
.project-sheet tbody tr.row-hl td { background: var(--wash); }
/* Row pinned to the calendar highlight — the one place ember appears here. */
.project-sheet tbody tr.row-pinned { background: color-mix(in srgb, var(--accent) 9%, transparent); box-shadow: inset 3px 0 0 var(--accent); }
.project-sheet tbody tr.row-pinned td:first-child { position: relative; }
.project-sheet tbody tr[data-project] { cursor: pointer; }

/* Instalment lines under a job — the invoices it is actually billed in. They are
   the working, not another set of jobs: no bottom rule between them, a size down,
   and only the money columns keep their full weight so the figures still read
   straight down the sheet's own columns. The last one closes with the rule, which
   is what makes the group read as belonging to the row above it. */
.project-sheet tbody tr.sheet-subrow td { border-bottom: 0; padding-top: .28rem; padding-bottom: .28rem;
    font-size: .78rem; background: color-mix(in srgb, var(--wash) 55%, transparent); }
.project-sheet tbody tr.sheet-subrow:last-of-type td,
.project-sheet tbody tr.sheet-subrow:has(+ tr[data-project]) td { border-bottom: 1px solid var(--line); }
.project-sheet tbody tr.sheet-subrow:hover td { background: var(--wash); }
.project-sheet tbody tr.sheet-subrow .pay-badge { font-size: .58rem; padding: .14rem .38rem; }
.sheet-sub-job { padding-left: 1.4rem !important; }
.sheet-sub-tick { color: var(--muted); margin-right: .3rem; }
.project-sheet tbody tr.sheet-subrow .sheet-job a { font-weight: 600; font-family: var(--mono); font-size: .74rem; }
.sheet-sub-when { color: var(--ink-soft); font-family: var(--mono); font-size: .72rem; }
.sheet-sub-when.is-late { color: var(--danger); font-weight: 600; }
/* The unbilled remainder is a gap, not a document — dashed and quiet. */
.project-sheet tbody tr.sheet-subrow--rest td { color: var(--muted); }

/* ---- Sortable column headers ---------------------------------------
   Shared by all three wide ledgers — the Projects sheet, the Invoices
   sheet and the Paperwork hub — because a heading that sorts should look
   and behave the same wherever you meet one. The engine is
   assets/js/sheet-sort.js; these are its clothes.

   Every header is a button. The label keeps its mono all-caps look — what
   changes is that it can be pressed, so the arrow is always there (faint
   until it's the column doing the sorting) rather than appearing on
   hover, which would leave the sheet looking un-sortable at rest. */
/* The button takes over the cell's padding so the whole heading is the hit
   target, not just the words. The cell's own padding therefore has to move onto
   the button — and it differs per table (the dense sheets are tighter), so it
   travels as a variable rather than as three near-identical rules. */
.project-sheet { --sh-th-pad: .7rem .8rem; }
.s-table .project-sheet, .project-sheet.inv-sheet, .project-sheet.est-sheet { --sh-th-pad: .4rem .5rem; }
.project-sheet th.sh-th { padding: 0; }
.project-sheet th.sh-th .sh-sort {
    display: flex; align-items: center; gap: .35rem; width: 100%;
    padding: var(--sh-th-pad);
    background: none; border: 0; cursor: pointer;
    font: inherit; color: inherit; letter-spacing: inherit; text-transform: inherit;
    text-align: left;
}
.project-sheet th.sh-th.num .sh-sort { justify-content: flex-end; }
.project-sheet th.sh-th .sh-sort:hover { color: var(--ink); background: var(--wash); }
.project-sheet th.sh-th .sh-sort:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.project-sheet th.sh-th .sh-arrow::after { content: '\2195'; font-size: .8em; opacity: .3; }
.project-sheet th.sh-th.is-sorted { color: var(--ink); }
.project-sheet th.sh-th.is-sorted .sh-arrow::after { content: '\2191'; opacity: 1; color: var(--accent); }
.project-sheet th.sh-th.is-sorted.is-desc .sh-arrow::after { content: '\2193'; }

/* ---- The money columns get their own colour ------------------------
   Three columns of near-identical mono figures sitting side by side is
   the one place this ledger stops being readable: Gross, Tax and Net all
   look like the same number until you count columns. So each keeps a
   hue of its own — the work's value in the positive teal, the set-aside
   in the rust the app already uses for money going out, and what you
   actually keep in ember, the accent this design reserves for the figure
   that matters. The tint behind them is deliberately at the edge of
   visible: enough to track a column down thirty rows, not enough to turn
   a hairline ledger into a spreadsheet. */
.project-sheet .sh-gross { color: var(--ok); }
.project-sheet .sh-tax   { color: var(--sh-tax-ink); }
.project-sheet .sh-net   { color: var(--accent); font-weight: 600; }
.project-sheet tbody .sh-gross { background: color-mix(in srgb, var(--ok) 4%, transparent); }
.project-sheet tbody .sh-tax   { background: color-mix(in srgb, var(--danger) 4%, transparent); }
.project-sheet tbody .sh-net   { background: color-mix(in srgb, var(--accent) 6%, transparent); }
/* Row hover has to win over the tint, or hovering a row lights up seven
   cells and skips three. Same tint, mixed over the hover wash instead of
   over nothing. */
.project-sheet tbody tr:hover .sh-gross { background: color-mix(in srgb, var(--ok) 7%, var(--wash)); }
.project-sheet tbody tr:hover .sh-tax   { background: color-mix(in srgb, var(--danger) 7%, var(--wash)); }
.project-sheet tbody tr:hover .sh-net   { background: color-mix(in srgb, var(--accent) 10%, var(--wash)); }
/* Head and foot are sticky, so their tint must be opaque — a translucent
   one would let the scrolling rows show through the totals. */
.project-sheet thead .sh-gross, .project-sheet tfoot .sh-gross { background: color-mix(in srgb, var(--ok) 6%, var(--panel)); }
.project-sheet thead .sh-tax,   .project-sheet tfoot .sh-tax   { background: color-mix(in srgb, var(--danger) 6%, var(--panel)); }
.project-sheet thead .sh-net,   .project-sheet tfoot .sh-net   { background: color-mix(in srgb, var(--accent) 9%, var(--panel)); }
.project-sheet thead th.sh-gross { color: var(--ok); }
.project-sheet thead th.sh-tax   { color: var(--sh-tax-ink); }
.project-sheet thead th.sh-net   { color: var(--accent); }
/* The working under the grand total keeps the colours but not the volume. */
.project-sheet tfoot tr.sheet-foot-split .sh-gross,
.project-sheet tfoot tr.sheet-foot-split .sh-tax,
.project-sheet tfoot tr.sheet-foot-split .sh-net { opacity: .72; font-weight: 400; }

/* ---- A finished job goes quiet -------------------------------------
   Complete, or every invoice paid: the row drops out of the colour
   scheme entirely. Dimming alone wasn't enough once the money columns
   were coloured — a half-faded green still reads as green — so the row
   is desaturated as well, which is what makes "done" and "live" tell
   apart at a glance down a long sheet. Hover brings it back in full,
   because a finished job still has to be readable when you go looking. */
.project-sheet tbody tr.sheet-row--done td { opacity: .46; filter: grayscale(1); }
.project-sheet tbody tr.sheet-row--done:hover td { opacity: 1; filter: none; }
.project-sheet tbody tr.sheet-row--done .sheet-job a,
.project-sheet tbody tr.sheet-row--done .sheet-client-name { font-weight: 400; }

/* "Whole job" total on a project's detail page: shown only when the same job
   name is split across 2+ projects for one client (a duplicate, or weekend days
   billed at their own rate). Lists each piece and sums the billing. */
.jobsum-note { font-size: .78rem; color: var(--muted); margin: 0 0 .7rem; }
.jobsum-parts { list-style: none; margin: .6rem 0 0; padding: .6rem 0 0; border-top: 1px solid var(--line); }
.jobsum-parts li { display: flex; align-items: baseline; justify-content: space-between; gap: .6rem; padding: .28rem 0; font-size: .82rem; }
.jobsum-parts a { font-weight: 600; }
.jobsum-here { color: var(--ink-soft); }
.jobsum-amt { font-variant-numeric: tabular-nums; color: var(--ink-soft); white-space: nowrap; }

/* Split-job detail page: each same-name project renders as a collapsible
   "piece" so its own days/rate/invoices stay editable under one job. */
.pieces-intro { font-size: .78rem; color: var(--muted); margin: 0 0 1rem; line-height: 1.5; }
/* A piece is a disclosure, not a card. Same shape as a grouped job on the client
   page: a summary line you tap, and its contents indented behind a rail. The five
   pastel status fills are gone — a status now shows as a colour on the piece's
   left edge, drawn from the same status palette the pill uses, so the page keeps
   one set of status colours instead of two. */
.piece { background: none; border: 0; border-radius: 0; box-shadow: none; margin: 0; overflow: visible; }
/* Nested (inside another piece's body) pieces separate with a hairline; top-level
   ones are sheets in their own right and separate by the column gap. */
.piece + .piece { border-top: 1px solid var(--line); }
.cd-main > .piece + .piece { border-top: 0; }
.piece-head {
    cursor: pointer; list-style: none;
    display: flex; align-items: baseline; gap: .6rem;
    padding: .6rem 0 .6rem .8rem; background: none;
    box-shadow: inset 3px 0 0 var(--st, var(--line-strong));
}
.piece-head::-webkit-details-marker { display: none; }
.piece-head::before { content: '▸'; color: var(--muted); font-size: .8rem; order: 10; margin-left: .5rem; }
.piece[open] .piece-head::before { content: '▾'; }
.piece-rate { font-weight: 600; font-size: .9rem; }
.piece-meta { font-family: var(--mono); color: var(--ink-soft); font-size: .78rem; font-variant-numeric: tabular-nums; }
.piece-head.st-booked   { --st: var(--ok); }
.piece-head.st-hold     { --st: var(--accent); }
.piece-head.st-hold2    { --st: color-mix(in srgb, var(--accent) 55%, var(--line)); }
.piece-head.st-estimate { --st: #8E3A7E; }
.piece-head.st-complete { --st: var(--muted); }
/* A split job carries its status per piece: the control lives on the summary row,
   pushed to the right, and stays clickable without toggling the fold. */
.piece-status { margin-left: auto; align-self: center; }
.piece-status .status-select { font-size: .74rem; font-weight: 600; padding: .2rem .45rem; }
/* The piece's contents sit indented behind a rail — the same "opened group"
   shape a split job uses on the client page — so it's obvious what belongs to
   which piece without drawing a box around it. */
.piece-body { padding: .5rem 0 1rem 1.15rem; margin-left: .15rem; border-left: 2px solid var(--line); }
.piece-body > .panel {
    background: none; border: 0; box-shadow: none; border-radius: 0;
    padding: 1rem 0 0; margin-top: 1rem; border-top: 1px solid var(--line);
}
.piece-body > .panel:first-child, .piece-body > .pd-recon:first-child { border-top: 0; margin-top: 0; padding-top: 0; }

/* ---- Sheet: two-column layout (calendar + table left, cards right) --------- */
.sheet-layout {
    display: grid;
    grid-template-columns: minmax(0, 1fr) var(--rail-w);
    /* Rows: the calendar hugs its content (collapsed = a thin bar); the table row
       flexes to absorb any extra height from the taller summary-cards rail beside
       it — so a short sheet leaves slack BELOW the table, never a gap between the
       collapsed calendar and the sheet. */
    grid-template-rows: auto 1fr;
    grid-template-areas: "cal cards" "table cards";
    gap: 1rem 1.25rem;
    align-items: start;
}
.sheet-layout > div { min-width: 0; }   /* let the table shrink, not overflow */
.s-cal   { grid-area: cal; }
.s-table { grid-area: table; }
.s-cards { grid-area: cards; }
.s-cards > .cards-compact { margin-bottom: 0; }
.s-cards .cards-compact { grid-template-columns: 1fr; }   /* stack the cards in the narrow rail */
/* Summary cards adopt the Dashboard tile language: flat, square, a 3px colored
   top ribbon, no box border/shadow. Booked-ahead = ember (the headline money),
   each month = green (future income). */
.sheet-card { --ribbon: #00A884; border: 0; border-top: 3px solid var(--ribbon); box-shadow: none; background: var(--panel); }
.sheet-card--hero { --ribbon: #EE7B3C; }
.sheet-card .card-value { font-variant-numeric: tabular-nums; }
/* Month/year rollup tiles (Calendar rail + Projects "this month" strip): same
   ribbon language — booked/paid green, gross/net blue data, outstanding ember. */
.mc-card { --ribbon: var(--line-strong); border: 0; border-top: 3px solid var(--ribbon); box-shadow: none; background: var(--panel); }
.mc-booked { --ribbon: #00A884; }
.mc-gross  { --ribbon: #2F6FB0; }
.mc-net    { --ribbon: #2F6FB0; }
.mc-paid   { --ribbon: #00A884; }
.mc-out    { --ribbon: #EE7B3C; }
.mc-be     { --ribbon: #EE7B3C; }   /* still short of break-even — ember */
.mc-be-ok  { --ribbon: #00A884; }   /* break-even covered — green */
.mc-card .card-value { font-variant-numeric: tabular-nums; }
/* Secondary "cost" foot-line: quiet, so it doesn't read as income (green). */
.card-net--muted { color: var(--muted); font-weight: 400; }
/* A card that counts something narrower than the sheet beside it says so on its
   face — the tooltip only helps the person who already suspected a mismatch. */
.card-note { margin-top: .3rem; font-size: .66rem; line-height: 1.35; color: var(--muted); }
/* Break-even foot-line on the Sheet's month cards: short = ember, over = green. */
.card-be { font-size: .78rem; font-weight: 600; margin-top: .05rem; font-variant-numeric: tabular-nums; }
.card-be.short { color: var(--gold); }
.card-be.over  { color: var(--ok); }

/* Foldaway calendars: collapsed by default so the sheet gets the spotlight. */
.sheet-cal-fold { margin: 0; }
.sheet-cal-fold > summary { list-style: none; }
.sheet-cal-fold > summary::-webkit-details-marker { display: none; }
.sheet-cal-toggle {
    display: flex; align-items: baseline; gap: .55rem; cursor: pointer;
    padding: .5rem .7rem; border: 1px solid var(--line); border-radius: var(--radius);
    background: var(--panel); user-select: none;
}
.sheet-cal-toggle:hover { border-color: var(--gold); }
.sheet-cal-toggle-label { font-weight: 600; font-size: .88rem; }
.sheet-cal-chev { margin-left: auto; color: var(--muted); transition: transform .15s; align-self: center; }
.sheet-cal-fold[open] .sheet-cal-chev { transform: rotate(180deg); }
.sheet-cal-fold[open] .sheet-cal-toggle { margin-bottom: .8rem; border-color: var(--gold); }
.sheet-cal-foldbody > .sheet-cal { margin-bottom: 0; }

/* This 10-column sheet needs room; below this, stack (cards on top, then table,
   calendar last) and let it use the page's full width — no side-scroll. */
@media (max-width: 1100px) {
    .sheet-layout { grid-template-columns: 1fr; grid-template-rows: none; grid-template-areas: "cards" "table" "cal"; }
    .s-cards .cards-compact { grid-template-columns: repeat(auto-fit, minmax(var(--card-min), 1fr)); }
}

/* Fit the sheet to its column — the free-text columns (Client, Job) wrap; the
   compact data columns stay on one line. */
.s-table .project-sheet { font-size: .78rem; }
.s-table .project-sheet th, .s-table .project-sheet td { padding: .4rem .5rem; white-space: normal; }
.s-table .project-sheet .sheet-date, .s-table .project-sheet .num,
.s-table .project-sheet .sheet-rate, .s-table .project-sheet .pay-badge,
.s-table .project-sheet .status-select { white-space: nowrap; }
.s-table .sheet-client { flex-wrap: wrap; }   /* let a long client name wrap under its logo */

/* ---- Compact rollup cards (Sheet + Projects month summary) --- */
.cards-compact { grid-template-columns: repeat(auto-fit, minmax(var(--card-min), 1fr)); gap: .8rem; }
.cards-compact .card { padding: .6rem .85rem; }
.cards-compact .card-label { font-size: .68rem; }
.cards-compact .card-value { font-size: 1.25rem; margin-top: .1rem; }
.cards-compact .card-net { font-size: .78rem; margin-top: .05rem; }
.card-sub { text-transform: none; letter-spacing: 0; color: var(--muted); font-weight: 400; }

/* ---- Sheet: 12-month value chart ---------------------------- */
.year-graph {
    background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius);
    padding: 1rem 1.15rem 1.15rem; box-shadow: var(--shadow); margin-bottom: 1.5rem;
}
.year-graph-head { display: flex; justify-content: space-between; flex-wrap: wrap; gap: .4rem; font-size: .82rem; color: var(--ink-soft); margin-bottom: .8rem; }
.year-bars-wrap { position: relative; height: 132px; }
.year-bars { display: grid; grid-template-columns: repeat(12, 1fr); gap: .5rem; align-items: end; height: 100%; }
.year-bar-col { display: flex; align-items: flex-end; justify-content: center; height: 100%; }
.year-bar {
    width: 62%; max-width: 26px; min-height: 2px;
    background: var(--accent);
    border-radius: 0; transition: filter .15s;
}
.year-bar.covered { background: var(--ok); }
.year-bar:hover { filter: brightness(1.08); }
.year-threshold { position: absolute; left: 0; right: 0; border-top: 2px dashed var(--gold); pointer-events: none; }
.year-threshold span {
    position: absolute; right: 0; top: -1.05rem; font-size: .62rem; color: var(--gold);
    text-transform: none; letter-spacing: .06em; font-weight: 600;
}
.year-bar-labels { display: grid; grid-template-columns: repeat(12, 1fr); gap: .5rem; margin-top: .4rem; }
.year-bar-label { text-align: center; font-size: .62rem; color: var(--muted); }

/* ---- Sheet: at-a-glance calendar (1–3 months) --------------- */
.sheet-cal { display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); gap: 1rem; margin-bottom: 1.5rem; }
.sheet-cal-link { text-decoration: none; cursor: pointer; }
.sheet-cal-link:hover .sheet-cal-month { border-color: var(--gold); }
.sheet-cal-month {
    background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius);
    padding: .8rem .85rem; box-shadow: var(--shadow);
}
.sheet-cal-title { font-weight: 600; font-size: .88rem; margin-bottom: .55rem; }
.sheet-cal-dows, .sheet-cal-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 3px; }
.sheet-cal-dows { margin-bottom: 3px; }
.sheet-cal-dow { text-align: center; font-size: .58rem; color: var(--muted); }
.sheet-cal .cell {
    position: relative;
    aspect-ratio: 1 / 1; display: flex; align-items: center; justify-content: center;
    font-size: .68rem; color: var(--ink-soft); border-radius: 5px; border: 1px solid transparent;
}
.sheet-cal .cell.empty { border: 0; }
/* Pay-day marker: a small dot in the day's corner (green once paid). */
.cal-pay-dot { position: absolute; top: 3px; right: 3px; width: 5px; height: 5px; border-radius: 50%; background: var(--gold); }
.cal-pay-dot.paid { background: var(--ok); }
.sheet-cal-legend { grid-column: 1 / -1; display: flex; align-items: center; gap: .35rem; font-size: .68rem; color: var(--muted); margin-top: .1rem; }
.sheet-cal-legend .cal-pay-dot { position: static; }
/* Invoice-focus calendar: pay days are the only marks, tinted so they stand out. */
.sheet-cal .cell.payday-cell { background: #faeee4; border-color: #e6c9b4; font-weight: 600; color: var(--ink); }
.sheet-cal .cell.inv-hl {
    background: var(--gold); border-color: var(--gold); color: var(--ink);
    box-shadow: 0 0 0 2px rgba(238,123,60,.4); transform: scale(1.12); position: relative; z-index: 1;
}
.sheet-cal .cell.inv-hl .cal-pay-dot { background: var(--ink); }
.project-sheet tbody tr[data-invoice] { cursor: pointer; }
.sheet-cal .cell.booked { font-weight: 600; color: var(--ink); cursor: pointer; }
.sheet-cal .cell.st-booked   { background: #e7efe9; border-color: #c9ddce; }
.sheet-cal .cell.st-hold     { background: #f4e8dd; border-color: #e4d0bf; }
.sheet-cal .cell.st-hold2    { background: #f4ddd0; border-color: #e6c4ae; }
.sheet-cal .cell.st-estimate { background: #efe9f0; border-color: #d9c9db; }
.sheet-cal .cell.st-complete { background: #e9ecf1; border-color: #ccd3de; }
.sheet-cal .cell.st-mixed    { background: #f4e7dd; border-color: #e6c9b4; }
.sheet-cal .cell.today { outline: 2px solid var(--ink); outline-offset: -2px; }
/* Time off, the same rule as the month grid on /calendar: a hatch rather than a
   colour, because every colour on this grid means the day is sold. Laid over
   whatever the cell already carries, so a booked day that's also blocked out
   shows both instead of one winning silently. The hatch is finer than the month
   calendar's — these cells are a third the size. */
.sheet-cal .cell.dayoff {
    background-image: repeating-linear-gradient(45deg,
        rgba(0,0,0,.07) 0 2px, transparent 2px 5px);
}
.sheet-cal .cell.dayoff:not(.booked) { background-color: #f1efec; border-color: #ded9d2; color: var(--muted); }
.sheet-cal .cell.cal-hl {
    background: var(--gold); border-color: var(--gold); color: var(--ink);
    box-shadow: 0 0 0 2px rgba(238,123,60,.4); transform: scale(1.12); position: relative; z-index: 1;
}

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

/* ---- Status select ------------------------------------------ */
.status-select { font: inherit; font-size: .82rem; padding: .3rem .5rem; border-radius: 0; border: 1px solid var(--line); cursor: pointer; appearance: none; -webkit-appearance: none; }
.status-hold { background: #f4e8dd; color: #9a6a3a; }
.status-hold2 { background: #f4ddd0; color: #a8542c; }
.status-booked { background: #e7efe9; color: var(--ok); }
.status-complete { background: #e9ecf1; color: #4a5c74; }
.status-estimate { background: #efe9f0; color: #6e4a68; }

/* ---- Invoice chips + toggles -------------------------------- */
.invoice-chip { display: inline-flex; align-items: center; gap: .5rem; padding: .3rem .5rem; border: 1px solid var(--line); border-radius: 0; background: #f8fafb; font-size: .82rem; }
.invoice-chip.status-paid { border-color: #c9ddce; background: #f2f9f4; }
.inv-num { font-weight: 600; }
.inv-period { color: var(--muted); }
.inv-amount { color: var(--ink-soft); }
.inv-toggles { display: inline-flex; gap: .25rem; }
.toggle { font-family: var(--mono); font-size: .62rem; text-transform: uppercase; letter-spacing: .06em; padding: .2rem .5rem; border: 1px solid var(--line); border-radius: 0; background: #fff; color: var(--muted); cursor: pointer; }
.toggle:hover { border-color: var(--gold); }
.toggle.on { background: var(--ink); color: #fff; border-color: var(--ink); }
.toggle.tax.on { background: var(--gold); border-color: var(--gold); color: var(--ink); }

/* Invoices tab: interactive Sent/Paid toggles and the .33 tax checkbox. */
.status-toggles { display: inline-flex; gap: .35rem; }
.inv-tax-col, .inv-tax-cell { text-align: center; }
.tax-check {
    width: 22px; height: 22px; display: inline-flex; align-items: center; justify-content: center;
    padding: 0; font: inherit; font-size: .8rem; line-height: 1; cursor: pointer;
    border: 1px solid var(--line); border-radius: 0; background: #fff; color: transparent;
}
.tax-check:hover { border-color: var(--gold); }
.tax-check.on { background: var(--gold); border-color: var(--gold); color: var(--ink); font-weight: 700; }

.linkbtn-sm { background: none; border: 0; color: var(--gold); cursor: pointer; font: inherit; font-size: .8rem; }
.linkbtn-sm.danger { color: var(--muted); }
.linkbtn-sm.danger:hover { color: var(--danger); }
.inline-form { display: inline; margin: 0; }
.inv-create-form { display: flex; align-items: flex-end; gap: .6rem; margin: 0; flex-wrap: wrap; }
.inv-date-field { display: flex; flex-direction: column; gap: .2rem; font-size: .72rem; text-transform: none; letter-spacing: .06em; color: var(--muted); }
.inv-date-field input { font: inherit; text-transform: none; letter-spacing: 0; padding: .45rem .55rem; border: 1px solid var(--line); border-radius: 0; background: #fff; color: var(--ink); }

.flat-span { display: flex; align-items: baseline; gap: .5rem; margin: 0 0 .8rem; font-size: .9rem; font-variant-numeric: tabular-nums; }
.flat-span .muted { font-size: .72rem; text-transform: uppercase; letter-spacing: .06em; }

/* ---- Flat-fee deposit / progress billing ------------------- */
/* Same notice shape as the payout banner — hairline box, ember edge, money in
   mono — so the page has one "here's a state, here's what you can do" block. */
.deposit-bar { margin: .8rem 0 1rem; padding: .7rem .85rem; border: 1px solid color-mix(in srgb, var(--accent) 26%, transparent);
    border-left: 3px solid var(--accent); background: color-mix(in srgb, var(--accent) 6%, transparent); }
.deposit-track { display: flex; align-items: baseline; gap: .5rem; flex-wrap: wrap; font-size: .82rem; margin-bottom: .6rem; }
.deposit-track strong { font-family: var(--mono); font-variant-numeric: tabular-nums; }
.deposit-left { color: var(--accent); }
.deposit-done { color: var(--ok); }
.deposit-actions { display: flex; align-items: center; gap: .45rem; flex-wrap: wrap; }
.deposit-label { font-family: var(--mono); font-size: .62rem; text-transform: uppercase; letter-spacing: .1em;
    font-weight: 600; color: var(--muted); margin-right: .1rem; }
.deposit-actions .inline-form { margin: 0; }
.deposit-custom { display: flex; gap: .3rem; }
.deposit-custom input { width: 6.5rem; font: inherit; padding: .3rem .5rem; border: 1px solid var(--line); border-radius: 0; background: #fff; color: var(--ink); }

/* ---- Empty state -------------------------------------------- */
.empty { text-align: center; }
.empty p { color: var(--muted); margin-bottom: 1rem; }

/* ---- Calendar ----------------------------------------------- */
/* ---- Calendar view: two-column layout (calendar left, summary rail right) --- */
.cal-layout {
    display: grid;
    grid-template-columns: minmax(0, 1fr) var(--rail-w);
    gap: 1rem 1.25rem;
    align-items: start;
}
.cal-layout > div, .cal-layout > aside { min-width: 0; }
.cal-rail { display: flex; flex-direction: column; gap: 1rem; }
.cal-rail .cards-compact { grid-template-columns: 1fr; margin-bottom: 0; }   /* stack the cards in the narrow rail */
.cal-rail .progress-wrap { margin: 0; }
.progress-foot { font-size: .78rem; margin-top: .4rem; }
@media (max-width: 1100px) {
    .cal-layout { grid-template-columns: 1fr; }
    .cal-rail { flex-direction: row; flex-wrap: wrap; }
    .cal-rail .cards-compact { flex: 1; grid-template-columns: repeat(auto-fit, minmax(var(--card-min), 1fr)); }
    .cal-rail .progress-wrap { flex-basis: 100%; }
}

.calendar { background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius); padding: 1rem; box-shadow: none; }
/* Multi-month calendar: stack each month grid with its own title. */
.calendar-multi .cal-month + .cal-month { margin-top: 1.35rem; padding-top: 1.35rem; border-top: 1px solid var(--line); }
.cal-month-title { font-size: .92rem; font-weight: 600; margin-bottom: .55rem; }

/* ---- Calendar page span selector + year navigation ---------- */
.cal-toggle { align-items: center; justify-content: space-between; flex-wrap: wrap; gap: .75rem 1rem; }
.cal-yearnav { display: inline-flex; align-items: center; gap: .5rem; }
.cal-yearnav-label { font-weight: 600; font-size: 1rem; min-width: 3.2rem; text-align: center; }
.cal-yearnav-btn {
    display: inline-flex; align-items: center; justify-content: center;
    width: 2rem; height: 2rem; border: 1px solid var(--line); border-radius: 0;
    background: #fff; color: var(--ink-soft); font-size: 1.1rem; line-height: 1; text-decoration: none;
}
.cal-yearnav-btn:hover { border-color: var(--gold); color: var(--gold); }

/* ---- Year view: twelve compact month grids in a wrap-around grid ---- */
.calendar-year {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(min(100%, 250px), 1fr));
    gap: 1.25rem 1.4rem;
}
.calendar-year .cal-month-title { font-size: .9rem; text-align: center; margin-bottom: .4rem; }
.calendar-year .cal-head, .calendar-year .cal-grid { gap: 2px; }
.calendar-year .cal-dow { font-size: .58rem; padding-bottom: .2rem; }
.calendar-year .cal-cell { min-height: 30px; border-radius: 5px; padding: .12rem .2rem; }
.calendar-year .cal-num { font-size: .62rem; }
/* Compact cells: colour + hover tooltip carry the detail; badges/logos would
   crowd a 30px cell, so the amounts live in the year cards instead. */
.calendar-year .cal-logos, .calendar-year .cal-inv, .calendar-year .cal-pay { display: none; }
.calendar-year .cal-legend { grid-column: 1 / -1; margin-top: .2rem; }
.cal-head, .cal-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 4px; }
.cal-dow { text-align: center; font-family: var(--mono); font-size: .62rem; text-transform: uppercase; letter-spacing: .12em; color: var(--muted); padding-bottom: .4rem; }
.cal-cell { position: relative; min-height: 56px; border: 1px solid var(--line); border-radius: 0; padding: .3rem .35rem; background: #fff; }
.cal-cell.empty { border: 0; background: transparent; }
/* A booked day is clickable via a stretched link filling the cell (opens the
   project); the pay-day badge sits above it as its own link (opens the invoice). */
.cal-cell-link { position: absolute; inset: 0; z-index: 1; border-radius: inherit; }
.cal-cell:has(.cal-cell-link) { cursor: pointer; transition: box-shadow .12s, transform .12s, border-color .12s; }
.cal-cell:has(.cal-cell-link:hover) { border-color: var(--gold); box-shadow: var(--shadow); transform: translateY(-1px); }
.cal-cell-link:focus-visible { outline: 2px solid var(--gold); outline-offset: 1px; }
/* Booked days are coloured by their project's status (matches the status pills) */
.cal-cell.booked { background: #f4e7dd; border-color: #e6c9b4; }        /* mixed / fallback */
.cal-cell.st-booked   { background: #e7efe9; border-color: #c9ddce; }
.cal-cell.st-hold     { background: #f4e8dd; border-color: #e4d0bf; }
.cal-cell.st-hold2    { background: #f4ddd0; border-color: #e6c4ae; }
.cal-cell.st-estimate { background: #efe9f0; border-color: #d9c9db; }
.cal-cell.st-complete { background: #e9ecf1; border-color: #ccd3de; }
.cal-cell.today { outline: 2px solid var(--gold); outline-offset: -2px; }
.cal-num { font-family: var(--mono); font-size: .74rem; color: var(--ink-soft); }
.cal-inv { position: absolute; top: .35rem; right: .4rem; color: var(--gold); font-size: .7rem; }
.cal-logos { position: absolute; bottom: .3rem; left: .4rem; display: flex; gap: 2px; align-items: center; }
.cal-logo { width: 18px; height: 18px; border-radius: 0; object-fit: cover; border: 1px solid rgba(0,0,0,.08); background: #fff; }
.cal-logo--mono { display: inline-flex; align-items: center; justify-content: center; background: var(--ink); color: #fff; font-size: .6rem; font-weight: 600; border: 0; }
.cal-more { font-size: .62rem; color: var(--muted); }
.cal-cell.payday { border-color: #c9ddce; }
.cal-pay {
    position: absolute; left: .35rem; bottom: .3rem; z-index: 2;   /* above the stretched project link */
    font-size: .64rem; font-weight: 600; line-height: 1;
    color: #2f6b43; background: #e6f2ea; border: 1px solid #c9ddce;
    font-family: var(--mono); border-radius: 0; padding: .1rem .28rem; max-width: calc(100% - .7rem); overflow: hidden;
}
.cal-pay.paid { color: #fff; background: var(--ok); border-color: var(--ok); }
/* Pay-day badge that links into its invoice — nudges up on hover. */
a.cal-pay { text-decoration: none; cursor: pointer; transition: box-shadow .12s, transform .12s; }
a.cal-pay:hover { transform: translateY(-1px); box-shadow: 0 2px 6px rgba(0,0,0,.18); }
a.cal-pay:focus-visible { outline: 2px solid var(--gold); outline-offset: 1px; }
.cal-legend { display: flex; gap: 1.2rem; margin-top: .8rem; font-family: var(--mono); font-size: .66rem; color: var(--ink-soft); flex-wrap: wrap; }
.cal-legend .sw,
.sheet-cal-legend .sw { display: inline-block; width: 11px; height: 11px; border-radius: 0; margin-right: .35rem; vertical-align: -1px; }
.cal-legend .sw.st-booked { background: #e7efe9; border: 1px solid #c9ddce; }
.cal-legend .sw.st-hold { background: #f4e8dd; border: 1px solid #e4d0bf; }
.cal-legend .sw.st-hold2 { background: #f4ddd0; border: 1px solid #e6c4ae; }
.cal-legend .sw.st-estimate { background: #efe9f0; border: 1px solid #d9c9db; }
.cal-legend .sw.st-complete { background: #e9ecf1; border: 1px solid #ccd3de; }
.cal-legend .sw.inv { background: var(--gold); }
.cal-legend .sw.pay { background: #e6f2ea; border: 1px solid #c9ddce; }

/* Time off: the day is off the MARKET, which is not the same as sold, so it gets
   a hatch rather than a colour of its own — every colour on this grid means
   "somebody has this day". The hatch is a background-image only, so a day that
   is both blocked out and booked keeps its status colour underneath and shows
   the hatch on top: the collision is visible instead of one of the two winning
   silently. Cells with nothing else on them take the muted ground. */
.cal-cell.dayoff {
    background-image: repeating-linear-gradient(45deg,
        rgba(0,0,0,.06) 0 3px, transparent 3px 7px);
}
.cal-cell.dayoff:not(.booked) { background-color: #f1efec; border-color: #ded9d2; }
.cal-cell.dayoff:not(.booked) .cal-num { color: var(--muted); }
.cal-legend .sw.dayoff,
.sheet-cal-legend .sw.dayoff { background-color: #f1efec; border: 1px solid #ded9d2;
    background-image: repeating-linear-gradient(45deg, rgba(0,0,0,.08) 0 2px, transparent 2px 4px); }

/* Floating hover tooltip for a calendar day — shows the job name(s) + pay-day. */
.cal-tip {
    position: absolute; z-index: 60; max-width: 260px;
    background: var(--ink); color: #fff; font-size: .76rem; line-height: 1.4;
    padding: .45rem .6rem; border-radius: 0; box-shadow: 0 6px 20px rgba(0,0,0,.22);
    pointer-events: none;
}
.cal-tip[hidden] { display: none; }
.cal-tip > div + div { margin-top: .15rem; color: rgba(255,255,255,.85); }

/* ---- Forms -------------------------------------------------- */
.form-grid { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 1.1rem; }
.form-grid label, .form-grid > div { min-width: 0; }
.form-grid .fg-2 { grid-column: 1 / -1; }
.form-grid input, .form-grid select, .form-grid textarea {
    font: inherit; padding: .55rem .65rem; border: 1px solid var(--line); border-radius: 0; background: #fff;
    width: 100%; min-width: 0; max-width: 100%;
}
.form-grid input:focus, .form-grid select:focus, .form-grid textarea:focus { outline: 2px solid var(--gold); outline-offset: 1px; border-color: var(--gold); }
.fg-label { font-size: .82rem; color: var(--ink-soft); display: block; margin-bottom: .5rem; }

/* Sticky save bar at the top of the Settings form so it's reachable without
   scrolling past every panel to the button at the bottom. */
.settings-savebar { position: sticky; top: 0; z-index: 20; display: flex; align-items: center; justify-content: space-between;
    gap: 1rem; margin-bottom: 1.1rem; padding: .7rem .9rem; background: var(--panel);
    border: 1px solid var(--line); border-radius: 8px; box-shadow: 0 6px 18px rgba(0,0,0,.06); }
.settings-savebar .muted { min-width: 0; }

/* Brand-logo picker on the Settings tab: current mark beside the upload field. */
.cfg-logo-row { display: flex; align-items: flex-start; gap: 1rem; margin-top: 1.1rem; padding-top: 1.1rem; border-top: 1px solid var(--line); }
.cfg-logo-preview { width: 48px; height: 48px; flex: none; border-radius: 0; overflow: hidden; background: #17181C; color: #fff;
    display: grid; place-items: center; font-family: var(--mono); font-weight: 700; font-size: 1rem; }
.cfg-logo-preview.is-empty { color: var(--accent-glow); }
.cfg-logo-preview img { width: 100%; height: 100%; object-fit: cover; display: block; }
.cfg-logo-field { flex: 1 1 auto; min-width: 0; }
.cfg-logo-field input[type=file] { font: inherit; }
.cfg-logo-bg { display: flex; align-items: center; gap: .6rem; flex-wrap: wrap; }
.cfg-logo-bg-opt { display: inline-flex; align-items: center; gap: .4rem; font-size: .82rem; color: var(--ink-soft); }
.cfg-logo-bg input[type=color] { width: 40px; height: 26px; padding: 0; border: 1px solid var(--line); border-radius: 4px; background: #fff; cursor: pointer; }
.cfg-logo-bg input[type=color]:disabled { opacity: .4; cursor: not-allowed; }
.form-actions { display: flex; gap: .6rem; margin-top: .5rem; }
.btn-ghost { background: #fff; border-color: var(--line); color: var(--ink); }
.btn-ghost:hover { border-color: var(--line-strong); color: var(--ink); background: var(--wash); }
/* Detail-view action buttons: keep the white ghost look, just less chunky. */
.detail-status .btn { padding: .34rem .64rem; font-size: .78rem; font-weight: 500; }
.btn-danger { background: var(--danger-bg); color: var(--danger); border-color: #eecaca; }
.btn-danger:hover { background: var(--danger); color: #fff; }

/* ---- Day chips (range entry + booked-day list) -------------- */
.range-inputs { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; }
.range-inputs input[type=date] { font: inherit; padding: .45rem .55rem; border: 1px solid var(--line); border-radius: 0; }
.day-chips, .day-list { display: flex; flex-wrap: wrap; gap: .4rem; margin-top: .7rem; }
/* Booked days are the one place chips earn their keep — a date is a token, not a
   list row. Retinted off the ember token instead of the old off-palette peach,
   and set in mono like every other date in the app. */
.day-chip {
    display: inline-flex; align-items: center; gap: .35rem;
    background: color-mix(in srgb, var(--accent) 8%, transparent);
    border: 1px solid color-mix(in srgb, var(--accent) 24%, transparent);
    border-radius: 0;
    padding: .2rem .55rem; font-family: var(--mono); font-size: .72rem; color: var(--ink-soft);
}
.day-chip--future { background: none; border-style: dashed; }
.day-chip button { background: none; border: 0; color: var(--muted); cursor: pointer; font-size: .95rem; line-height: 1; padding: 0; }
.day-chip button:hover { color: var(--danger); }
.add-days { margin-top: 1.1rem; padding-top: 1rem; border-top: 1px dashed var(--line); }

/* Booked days grouped by week, each with a one-click "Remove week". */
.week-groups { display: flex; flex-direction: column; gap: .9rem; }
.week-group-head { display: flex; align-items: center; justify-content: space-between; gap: .8rem; }
.wg-label { font-weight: 600; font-size: .85rem; }
.wg-label .muted { font-weight: 400; margin: 0 .35rem; }
.wg-remove {
    background: none; border: 1px solid var(--line); border-radius: 0;
    padding: .2rem .55rem; font-size: .74rem; color: var(--muted); cursor: pointer;
}
.wg-remove:hover { border-color: var(--danger); color: var(--danger); }

/* ---- Detail ------------------------------------------------- */
.detail-status { align-items: center; flex-wrap: wrap; }
/* Keep the header action buttons a uniform size at every resolution:
   never let the flex row squeeze them or wrap their labels. */
.detail-status .btn { white-space: nowrap; flex-shrink: 0; }
/* Match the status dropdown to those buttons: same padding, radius, weight. */
.detail-status .status-select {
    font-size: inherit; font-weight: 600;
    padding: .65rem 1rem; border-radius: 0; flex-shrink: 0;
}
/* ---- Detail pages (client & project): wide main column + reference sidebar,
   flat uniform panels. Each column stacks independently, so there are no
   mismatched card pairs or empty cells. ---- */
.cd-grid { display: grid; grid-template-columns: minmax(0, 1fr) var(--rail-w); gap: 1rem; align-items: start; }
.cd-main, .cd-side { display: flex; flex-direction: column; gap: 1rem; min-width: 0; }
.cd-main .panel, .cd-side .panel { box-shadow: none; padding: 1.15rem 1.25rem; margin: 0; }
.cd-main .panel h2, .cd-side .panel h2 { font-size: .92rem; font-weight: 600; letter-spacing: -.005em; }
/* Section headers get a hairline rule under them (echoes the Dashboard feed),
   whether the panel uses a bare <h2> or a .panel-head row. */
.cd-main .panel > h2, .cd-side .panel > h2,
.cd-main .panel-head, .cd-side .panel-head { padding-bottom: .5rem; margin-bottom: .85rem; border-bottom: 1px solid var(--line-strong); }
.cd-main .panel-head h2, .cd-side .panel-head h2 { padding: 0; margin: 0; border: 0; }
.cd-main .panel-head .small, .cd-side .panel-head .small { font-family: var(--mono); font-size: .68rem; letter-spacing: .02em; }
.cd-main .panel-head a.small, .cd-side .panel-head a.small { text-transform: uppercase; letter-spacing: .06em; font-size: .62rem; }
/* the money summary at the right of a panel head (e.g. "$17,000 total value") reads bold */
.cd-main .panel-head span.small, .cd-side .panel-head span.small { font-weight: 600; color: var(--ink-soft); }
.cd-side .mini-h:first-child { margin-top: 0; }
.cd-main .pd-recon { margin: 0; }
@media (max-width: 900px) { .cd-grid { grid-template-columns: 1fr; } }

/* --- Favorites: a cross-type shortlist. It is a set of shortcuts to things that
   live elsewhere on the page, so it stays deliberately quieter than the sections
   it points at: one dense line each, no rules between them. --- */
.cfav { border-left: 2px solid var(--accent); }
.cfav-star { color: var(--gold); }
.cfav-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; }
.cfav-item { border-top: 1px solid var(--line); }
.cfav-item:first-child { border-top: 0; }
.cfav-item a { display: flex; align-items: baseline; gap: .55rem; padding: .4rem 0; color: inherit; text-decoration: none; }
.cfav-item a:hover .cfav-label { color: var(--accent); }
/* Bare: no ★ label, no rule under it — a panel head above already names the list. */
.cfav-bare { border-bottom: 0; padding-bottom: 0; margin-bottom: 0; }
.cfav-embed .cfav-item { border-top: 0; }
.cfav-embed .cfav-item a { padding: .17rem 0; font-size: .84rem; }
.cfav-embed .cfav-label { font-weight: 400; color: var(--ink-soft); }
.cfav-embed .cfav-sub { font-size: .76rem; }
.cfav-tag { flex: none; font-family: var(--mono); font-size: .58rem; text-transform: uppercase; letter-spacing: .06em;
    font-weight: 600; padding: .12rem .42rem; border-radius: 2px; white-space: nowrap; }
.cfav-tag--email { background: rgba(192, 95, 39, .1); color: var(--accent); }
.cfav-tag--note  { background: #eef2f7; color: #47576b; }
.cfav-tag--doc   { background: rgba(192, 95, 39, .08); color: var(--accent); }
.cfav-label { font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cfav-sub { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: .8rem; }

/* ============================================================
   DOCUMENT KINDS — colour + silhouette per category
   A stack of estimates, contracts, sheets and deliveries used to
   wear one grey pencil each, so telling them apart meant reading
   every line. Each kind now carries a tinted tile with its own
   icon, and its name in the meta line takes the same colour.

   Two rules hold this together:
     · never colour alone — the icon shape and the written kind
       carry the same information, so the list survives a
       colour-blind reader and a black-and-white print;
     · one hue per kind, spread around the wheel, all muted to the
       same depth so no single row shouts louder than its
       neighbours. Ember stays the money colour and is spent here
       only on the estimate, which IS the money.
   Set --dk per row (.dk-estimate, …) and everything inside — the
   tile, its icon, the kind label — follows.
   ============================================================ */
.dk-estimate { --dk: #C05F27; }   /* ember  — the price */
.dk-contract { --dk: #8E3A7E; }   /* plum   — the signed deal */
.dk-sheet    { --dk: #2F6FB0; }   /* blue   — rows and columns */
.dk-status   { --dk: #1E8F73; }   /* green  — where the work stands */
.dk-delivery { --dk: #0F7285; }   /* teal   — files going out */
.dk-letter   { --dk: #5B6470; }   /* slate  — a letter */
.dk-resume   { --dk: #8A6A2F; }   /* brass  — you */
.dk-doc      { --dk: var(--muted); }
.dk-link     { --dk: var(--muted); }   /* overridden inline with the host's colour */

/* ---- The same language extended to RECORDS ---------------------------------
   The dashboard's jump strip (.jump) shortcuts a client or a job as readily as a
   sheet, so those kinds need a place in this set too. Two of the four were
   already decided elsewhere in the app and are only being promoted here, which is
   the point — a concept should not change colour when it changes screen:

     · invoice — the indigo the Files tab already gives an uploaded invoice
                 (.vf-k-invoice). Ember is the money colour but it is spent on the
                 estimate, so the invoice takes the cooler neighbour rather than
                 two rows of money shouting at each other.
     · note    — the slate-blue the Favorites strip already gives a pinned note
                 (.cfav-tag--note).

   The two genuinely new ones fill the gaps left on the wheel:

     · client  — violet, clear of the contract's plum and the invoice's indigo. A
                 client is a who, and the only who in the set.
     · job     — moss. Booked work reads green everywhere in this app (the
                 calendar paints a booked day green), and moss is a warm, yellower
                 green than the status report's teal-green, so the two are told
                 apart by hue as well as by icon and word.

   Mail keeps the green the feed already gives arriving mail (.feed-row--mail), so
   a mail card and a mail row on the same page agree. */
.dk-client   { --dk: #6A4BA8; }   /* violet — the company */
.dk-job      { --dk: #5F7A3A; }   /* moss   — booked work */
.dk-note     { --dk: #47576B; }   /* slate  — something you wrote down */
.dk-invoice  { --dk: #3F5487; }   /* indigo — money sent out */
.dk-mail     { --dk: var(--c-in); }

.dk {
    flex: none; display: inline-grid; place-items: center;
    width: 1.55rem; height: 1.55rem; margin-top: .05rem;
    color: var(--dk, var(--muted));
    background: color-mix(in srgb, var(--dk, var(--muted)) 11%, transparent);
    box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--dk, var(--muted)) 26%, transparent);
}
.dk-ico { width: 14px; height: 14px; }
/* A tile holding a company's own mark rather than one of our icons. The tint
   comes off — a full-colour logo on a coloured wash is two brands fighting — and
   what's left is the hairline square, which is the shape the monogram fallback
   has always worn (.sj-logo-mono). The kind's colour still shows in the word
   beneath, so nothing is lost by dropping it here. */
.dk--mark { background: none; box-shadow: inset 0 0 0 1px var(--line-strong); }
.dk--mark img { width: 100%; height: 100%; object-fit: contain; display: block; padding: 2px; }
.dk-mono { font-family: var(--mono); font-size: .7rem; font-weight: 700; color: var(--ink-soft); }
/* The written kind carries the same colour as the tile — the second, redundant
   channel that makes the colour safe to rely on at a glance. */
.cdoc-kind { color: var(--dk, var(--ink-soft)); font-weight: 600; }
/* Favourite tags inherit their document's colour so the shortlist is sorted by
   eye too, not just the panel below it. */
.cfav-tag--doc { background: color-mix(in srgb, var(--dk, var(--accent)) 11%, transparent); color: var(--dk, var(--accent)); }

/* --- Documents panel: editable records, split out from uploaded Files --- */
.cdoc-list { display: flex; flex-direction: column; }
.cdoc-row { display: flex; align-items: flex-start; gap: .6rem; padding: .5rem 0; border-top: 1px solid var(--line); }
.cdoc-row:first-child { border-top: 0; }
/* Done: greyed in place, never folded away. The kind colour drops out (so the
   colour-coded rows above are the ones that still want something from you) but
   the row stays fully legible and its actions stay usable — an opacity fade would
   have made "Reopen" and "delete" look disabled. */
.cdoc-row--done { --dk: var(--muted); }
.cdoc-row--done .cdoc-name { color: var(--muted); font-weight: 400; }
.cdoc-row--done:hover .cdoc-name { color: var(--ink-soft); }
.cdoc-row--done .cdoc-meta { color: var(--muted); }
.cdoc-main { flex: 1 1 auto; min-width: 0; }
.cdoc-name { display: block; font-size: .88rem; font-weight: 500; color: var(--ink); text-decoration: none;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cdoc-name:hover { color: var(--accent); }
.cdoc-meta { display: flex; flex-wrap: wrap; align-items: center; gap: .3rem; font-size: .74rem; margin-top: .12rem; }
.cdoc-pill { font-family: var(--mono); font-size: .56rem; text-transform: uppercase; letter-spacing: .06em;
    font-weight: 600; padding: .08rem .4rem; border-radius: 2px; }
.cdoc-pill--play { background: #e7efe9; color: var(--ok); }
.cdoc-pill--done { background: var(--panel); color: var(--muted); border: 1px solid var(--line); }
.cdoc-actions { flex: none; display: flex; align-items: center; gap: .55rem; }
.cdoc-star { background: none; border: 0; cursor: pointer; font-size: 1rem; line-height: 1; color: var(--line-strong); padding: 0; }
.cdoc-star:hover { color: var(--gold); }
.cdoc-star.is-on { color: var(--gold); }
.cdoc-open { font-size: .78rem; color: var(--accent); text-decoration: none; }
.cdoc-open:hover { text-decoration: underline; }
@media (max-width: 560px) {
    .cdoc-row { flex-wrap: wrap; }
    .cdoc-actions { width: 100%; padding-left: 1.65rem; }
}

/* align-items:start so a short panel (e.g. Details) isn't stretched into a big
   empty card to match a tall neighbour (Contacts) — that was the wasted space. */
.detail-cols { display: grid; grid-template-columns: 1fr 1fr; gap: .8rem; margin-bottom: .8rem; align-items: start; }
.detail-cols .panel { padding: 1.1rem 1.2rem; }
.detail-cols .panel h2 { font-size: 1.02rem; }
.kv { display: grid; grid-template-columns: auto 1fr; gap: .35rem .9rem; margin: 0 0 .7rem; }
.kv dt { color: var(--muted); font-size: .85rem; }
.kv dd { margin: 0; font-size: .9rem; }
/* Client-detail Details: labels as mono micro-caps, like the sheet columns. */
.cd-side .kv dt { font-family: var(--mono); font-size: .62rem; text-transform: uppercase; letter-spacing: .1em; align-self: center; }
.cd-side .kv dd { font-size: .88rem; }
.notes-box { background: #f8fafb; border: 1px solid var(--line); border-radius: 0; padding: .7rem .9rem; font-size: .88rem; color: var(--ink-soft); }
.detail-actions { display: flex; gap: .5rem; margin-top: 1rem; flex-wrap: wrap; }
.detail-actions form { margin: 0; }
.panel-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem; }
.panel-head h2 { margin: 0; }
.invoice-list { display: flex; flex-direction: column; gap: .5rem; }
.invoice-line { display: flex; align-items: center; gap: .8rem; flex-wrap: wrap; }
.inv-dates { font-size: .78rem; color: var(--muted); }
.inv-actions { display: inline-flex; align-items: center; gap: .7rem; margin-left: auto; }

/* ---- Invoice email prep page -------------------------------- */
.mini-h { font-size: .95rem; margin: .8rem 0 .3rem; }
.small { font-size: .82rem; }
.note-warn { color: #9a6a3a; background: #f4e8dd; border: 1px solid #e4d0bf; padding: .4rem .6rem; border-radius: 6px; margin-top: .5rem; }
.link-form { display: flex; gap: .5rem; margin: .5rem 0; }
.link-form input { flex: 1; min-width: 0; font: inherit; padding: .5rem .6rem; border: 1px solid var(--line); border-radius: 0; }
.copy-row { display: flex; gap: .5rem; align-items: center; margin-top: .4rem; }
.copy-row input { flex: 1; min-width: 0; font: inherit; font-size: .82rem; padding: .5rem .6rem; border: 1px solid var(--line); border-radius: 0; background: #f8fafb; color: var(--ink-soft); }
.email-preview { border: 1px solid var(--line); border-radius: 10px; padding: 1.5rem; background: #fff; }

[x-cloak] { display: none !important; }

/* Compose email: single editable-preview panel (shared by booking + outreach) */
.mail-compose .bm-meta { display: grid; grid-template-columns: auto 1fr; gap: .3rem .8rem; font-size: .85rem; margin-bottom: .6rem; }
.mail-compose .bm-lbl { color: var(--muted); }
.mail-compose .bm-hint { margin: .2rem 0 1rem; }
.mail-compose .bm-form { margin: 0; }
.mail-compose .bm-actions { display: flex; gap: .5rem; flex-wrap: wrap; margin-top: 1rem; }
/* Template switch (e.g. Check-in / Cold intro) — a small segmented control. */
.mail-compose .tpl-toggle { display: inline-flex; gap: 2px; padding: 3px; margin-bottom: .9rem; background: #f1f3f5; border: 1px solid var(--line); border-radius: 9px; }
.mail-compose .tpl-tab { font: inherit; font-size: .82rem; cursor: pointer; border: 0; background: transparent; color: var(--ink-soft); padding: .34rem .85rem; border-radius: 6px; transition: background .15s, color .15s; }
.mail-compose .tpl-tab:hover { color: var(--ink); }
.mail-compose .tpl-tab.active { background: #fff; color: var(--ink); box-shadow: 0 1px 2px rgba(0,0,0,.08); }
/* Inline-editable lines in the preview: a soft gold wash so they read as tappable. */
.ce-eg,
.email-preview .ce { background: #fffaf0; box-shadow: inset 0 -1px 0 #e7d9b0; border-radius: 3px; }
.email-preview .ce { padding: 0 3px; cursor: text; outline: none; transition: background .15s, box-shadow .15s; }
.email-preview li.ce { margin-left: 0; }
.email-preview .ce:hover { background: #fbf1d6; }
.email-preview .ce:focus { background: #fff; box-shadow: 0 0 0 2px #C05F27; }
.email-preview .ce:empty::before { content: attr(data-ph); color: #b9bcc2; }

/* ---- Header: right-hand group (payout + sign-out) ----------- */
/* Grouping the pill and the sign-out form in one item, pushed right with an
   auto margin, keeps them together AND hugging the right edge — whether they
   sit inline on the bar or wrap to their own line at medium widths. */
.topbar-right { margin-left: auto; display: flex; align-items: center; gap: 1rem; flex-wrap: wrap; justify-content: flex-end; min-width: 0; }
.np-slot { display: inline-flex; align-items: center; min-width: 0; }
.next-payout {
    display: inline-flex; align-items: baseline; gap: .5rem;
    padding: .3rem .7rem; border: 1px solid rgba(255,255,255,.18); border-radius: 0;
    background: rgba(255,255,255,.05); color: #eee; white-space: nowrap;
}
.next-payout:hover { border-color: var(--gold); color: #fff; }
.next-payout .np-label { font-size: .68rem; text-transform: none; letter-spacing: .08em; color: #a9a9a9; }
.next-payout .np-amount { font-weight: 600; color: var(--gold); }
.next-payout .np-when { font-size: .8rem; color: #cfcfcf; }
.next-payout.overdue .np-amount, .next-payout.overdue .np-when { color: #e8a0a0; }

/* ---- Payout banner on the project --------------------------- */
/* One notice shape across the app: hairline box, a 3px coloured edge carrying the
   meaning, tinted from a palette token (the old #00A884 was a green that appears
   nowhere else). */
.payout-banner {
    display: flex; align-items: center; gap: .6rem;
    background: color-mix(in srgb, var(--ok) 7%, transparent);
    border: 1px solid color-mix(in srgb, var(--ok) 26%, transparent);
    border-left: 3px solid var(--ok);
    border-radius: 0; padding: .65rem .9rem; margin-bottom: 1.5rem; font-size: .85rem; color: var(--ink-soft);
}
.payout-banner strong { font-variant-numeric: tabular-nums; }
.payout-banner.overdue { background: var(--danger-bg); border-color: #eecaca; border-left-color: var(--danger); color: var(--danger); }
.payout-banner .pb-icon { font-size: 1rem; color: var(--ok); }
.payout-banner.overdue .pb-icon { color: var(--danger); }
/* Dashboard payout banner is a link to the invoice: no underline, whole bar clickable,
   with a chevron parked on the right that nudges on hover. */
.payout-banner-link { text-decoration: none; transition: border-color .12s, background .12s; }
.payout-banner-link .pb-go { margin-left: auto; color: var(--ok); transition: transform .12s; }
.payout-banner-link.overdue .pb-go { color: var(--danger); }
.payout-banner-link:hover { border-color: var(--ok); background: color-mix(in srgb, var(--ok) 12%, transparent); }
.payout-banner-link.overdue:hover { border-color: var(--danger); background: var(--danger-bg); }
.payout-banner-link:hover .pb-go { transform: translateX(3px); }

/* Dashboard: recent invoices list — a single stacked column in the dash-recent
   band (above "Booked this month"). One compact line each: #num, client, amount,
   status badge. */
.di-list { display: flex; flex-direction: column; }
.di-row {
    display: flex; align-items: center; gap: .5rem;
    padding: .4rem 0; border-top: 1px solid var(--line);
    color: var(--ink); font-size: .86rem;
}
.di-row:first-child { border-top: 0; }
.di-row:hover .di-client { color: var(--gold); }
.di-num { font-weight: 600; flex: none; }
.di-client { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--ink-soft); }
.di-amt { flex: none; font-weight: 600; font-variant-numeric: tabular-nums; }
.di-row .pay-badge { flex: none; }

/* ---- Weekly billing ----------------------------------------- */
/* Weeks are ledger lines, like every other list in the app — the boxed rows on a
   grey fill were a third row idiom for the same idea as the client page's
   invoices. Separator on the list child, money in mono at the right. */
.week-list { display: flex; flex-direction: column; }
.week-list > * + * { border-top: 1px solid var(--line); }
.week-row { display: grid; grid-template-columns: minmax(0, 1fr) auto auto; gap: .15rem 1rem;
    align-items: center; padding: .5rem 0; border: 0; border-radius: 0; background: none; }
.wk-label { font-weight: 500; font-size: .88rem; }
.wk-label .muted { font-weight: 400; margin-left: .4rem; }
.wk-days { font-family: var(--mono); font-size: .78rem; color: var(--ink-soft);
    font-variant-numeric: tabular-nums; text-align: right; }

/* Rate tag on a combined-billing week row (merged jobs, when rates differ). */
.wk-rate { display: inline-block; margin-left: .45rem; padding: .05rem .4rem; border: 1px solid var(--line);
    border-radius: 0; font-family: var(--mono); font-size: .62rem; font-weight: 600; letter-spacing: .04em;
    color: var(--ink-soft); background: var(--panel); vertical-align: middle; }

/* The at-a-glance Weekly-billing panel that opens a merged (split) job. Its ruled
   section head is the landmark — the extra ember spine was a second one. */
.billing-combined { border-left: 0; }
.wk-invoiced { font-size: .85rem; color: var(--ok); }

/* Inline invoice status on a week row / in the compact list — links to the PDF. */
.wk-inv, .inv-mini { display: inline-flex; align-items: center; gap: .5rem; text-decoration: none; color: var(--ink); }
.wk-inv:hover, .inv-mini:hover { text-decoration: none; }
.wk-inv:hover .wk-inv-num, .inv-mini:hover .inv-mini-num { text-decoration: underline; }
.wk-inv-num, .inv-mini-num { font-family: var(--mono); font-weight: 600; font-size: .8rem; }
.wk-inv-open { color: var(--muted); font-size: .8rem; }
.wk-inv-job { font-size: .78rem; }   /* "whole job" tag on a week billed by the job invoice */

/* Whole job on one invoice: the invoice leads, the weeks are indented underneath
   as its makeup — no invoice number repeated per week, no stray row for the same
   invoice below. */
/* Boxless, like every other grouped row: the invoice leads, its weeks indent
   behind a rail underneath. */
.job-billed { border: 0; background: none; }
.jb-head { display: grid; grid-template-columns: auto 1fr auto auto auto; gap: .6rem; align-items: center;
    padding: .5rem 0; text-decoration: none; color: var(--ink); }
.jb-head:hover { text-decoration: none; background: none; }
.jb-head:hover .jb-num { text-decoration: underline; }
.jb-num { font-family: var(--mono); font-weight: 600; font-size: .8rem; }
.jb-what { font-size: .88rem; font-weight: 500; min-width: 0; }
.jb-what .muted { font-weight: 400; }
.jb-amt { font-family: var(--mono); font-weight: 600; font-size: .88rem; font-variant-numeric: tabular-nums; }
.jb-lines { display: flex; flex-direction: column; margin: 0 0 .4rem .15rem;
    padding-left: 1.15rem; border-left: 2px solid var(--line); }
.jb-line { display: flex; align-items: center; justify-content: space-between; gap: 1rem;
    padding: .4rem 0; font-size: .82rem; }
.jb-line + .jb-line { border-top: 1px solid var(--line); }
.jb-when { font-weight: 600; }
.jb-when .muted { font-weight: 400; margin-left: .4rem; }
.jb-days { color: var(--ink-soft); white-space: nowrap; }

/* Same ledger line as the client page's invoice list: no box, no fill, the amount
   parked in mono where the eye already looks for money. */
.inv-mini-list { display: flex; flex-direction: column; margin-top: .5rem; }
.inv-mini-list > * + * { border-top: 1px solid var(--line); }
.inv-mini { padding: .5rem 0; border: 0; border-radius: 0; background: none; font-size: .84rem; }
.inv-mini-amt { font-family: var(--mono); font-weight: 600; font-variant-numeric: tabular-nums; margin-left: auto; }
.inv-mini-dates { font-family: var(--mono); color: var(--muted); font-size: .72rem; }
.inv-mini .pay-badge { margin-left: 0; }
.wk-inv .pay-badge, .inv-mini .pay-badge { align-self: center; }

.panel-foot { display: flex; align-items: center; justify-content: space-between; gap: 1rem; flex-wrap: wrap; margin-top: 1rem; padding-top: .9rem; border-top: 1px solid var(--line); }

.btn-sm { padding: .35rem .7rem; font-size: .8rem; }
.cards-note { font-size: .82rem; margin: -1rem 0 1.5rem; }
.card-sub { font-size: .62rem; text-transform: none; letter-spacing: 0; color: var(--muted); }

/* ---- Budget ------------------------------------------------- */
/* ---- Spending -----------------------------------------------
   A page opened at a shop counter, so it is built the way you use it: what is
   left, then the box you type into, then everything that is only reading. Sized
   for a thumb throughout — the amount field is deliberately the biggest thing on
   the screen, because it is the one thing you came here to touch. */
.spend-page { max-width: 720px; }

/* The headline. Tone comes off how much of the month's envelopes are left, so
   the answer is legible before a single figure is read. */
.sp-head { display: flex; align-items: flex-end; justify-content: space-between; gap: 1rem;
           flex-wrap: wrap; padding: 1rem 1.1rem; margin-bottom: .9rem;
           background: var(--panel); border-top: 3px solid var(--line-strong); }
.sp-head.is-clear { border-top-color: #00A884; }
.sp-head.is-close { border-top-color: var(--gold, #B7791F); }
.sp-head.is-over  { border-top-color: var(--danger); }
.sp-left-val { display: block; font-family: var(--mono); font-size: 2.1rem; font-weight: 600;
               line-height: 1; letter-spacing: -.02em; font-variant-numeric: tabular-nums; }
.sp-head.is-over .sp-left-val { color: var(--danger); }
.sp-left-sub { display: block; margin-top: .35rem; font-size: .78rem; color: var(--muted); }
.sp-today { text-align: right; }
.sp-today-label { display: block; font-family: var(--mono); font-size: .58rem; text-transform: uppercase;
                  letter-spacing: .1em; color: var(--muted); }
.sp-today-val { display: block; margin-top: .15rem; font-family: var(--mono); font-size: 1.1rem;
                font-weight: 600; font-variant-numeric: tabular-nums; }

/* The form. One row on a desktop, and it wraps to the amount + a full-width
   "what" on a phone, which is the order you fill them in anyway. */
.sp-add { display: flex; flex-wrap: wrap; align-items: stretch; gap: .5rem; margin-bottom: 1.1rem; }
.sp-amt { display: flex; align-items: center; gap: .3rem; padding: 0 .7rem; flex: 0 0 auto;
          background: var(--panel); border: 1px solid var(--line-strong); }
.sp-amt:focus-within { border-color: var(--accent); }
.sp-cur { font-family: var(--mono); font-size: 1.1rem; color: var(--muted); }
.sp-amt input { width: 5.5rem; font: inherit; font-family: var(--mono); font-size: 1.45rem;
                font-weight: 600; padding: .5rem 0; border: 0; background: none; outline: none;
                font-variant-numeric: tabular-nums; }
.sp-what { flex: 1 1 9rem; min-width: 0; font: inherit; padding: .5rem .6rem;
           border: 1px solid var(--line); border-radius: 0; }
.sp-cat { font: inherit; padding: .5rem .5rem; border: 1px solid var(--line); border-radius: 0; }
.sp-add .btn { padding: .5rem 1.2rem; }

/* An envelope: mark, name, bar, and what is left of it. */
.sp-envs { margin-bottom: 1.1rem; }
.sp-env { display: flex; align-items: center; gap: .65rem; padding: .6rem 0;
          border-bottom: 1px solid var(--rule-soft, #eef1f5); }
.sp-env:last-of-type { border-bottom: 0; }
.sp-env-body { flex: 1; min-width: 0; }
.sp-env-top { display: flex; align-items: baseline; justify-content: space-between; gap: .2rem .6rem; flex-wrap: wrap; }
.sp-env-name { flex: 1 0 auto; min-width: 5.5rem; font-size: .86rem; font-weight: 600;
               overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sp-env-nums { font-family: var(--mono); font-size: .68rem; color: var(--muted);
               font-variant-numeric: tabular-nums; white-space: nowrap; }
.sp-env-nums strong { color: var(--ink); font-weight: 600; }
/* The bar carries the envelope's own family colour, set by .bm-* on the mark
   beside it — so a full groceries bar is the same moss as its cart. */
.sp-bar { height: 4px; margin-top: .35rem; background: var(--line); overflow: hidden; }
.sp-bar span { display: block; height: 100%; background: var(--bm, var(--ink-soft)); }
.sp-env.is-over .sp-bar span { background: var(--danger); }
.sp-env-left { flex: none; width: 5.2rem; text-align: right; font-family: var(--mono);
               font-size: .84rem; font-weight: 600; font-variant-numeric: tabular-nums; }
.sp-env.is-over .sp-env-left { color: var(--danger); }
.sp-env-leftlab { display: block; font-size: .56rem; font-weight: 500; text-transform: uppercase;
                  letter-spacing: .1em; color: var(--muted); }
/* The month, underneath the week: the same envelopes at a quieter weight, because
   it is context rather than a decision. */
.sp-month { margin-top: 1.1rem; }
.sp-env--month .sp-env-name { font-weight: 500; color: var(--ink-soft); }
.sp-env--month .sp-bar { height: 3px; }
.sp-month-note { margin: .9rem 0 0; padding-top: .7rem; border-top: 1px solid var(--line); }

/* The rollover, named wherever it moves a figure. A number that changed because
   of last week and doesn't say so is a number you stop trusting. */
.sp-carry { display: inline-block; margin-left: .5rem; font-family: var(--mono); font-size: .68rem;
            font-variant-numeric: tabular-nums; }
.sp-carry--up   { color: var(--ok); }
.sp-carry--down { color: var(--gold, #B7791F); }
.sp-env-carry { font-style: normal; margin-left: .3rem; opacity: .75; cursor: help; }

.sp-unbudgeted { margin: .9rem 0 0; padding: .6rem .75rem; font-size: .8rem;
                 background: var(--bg); border-left: 3px solid var(--muted); }
.sp-unbudgeted strong { font-family: var(--mono); font-variant-numeric: tabular-nums; }

/* The ledger. */
/* The overspend, on its own line under the headline — never folded into it.
   Amber while the rest of the week could still cover it, red once it couldn't:
   the difference between "move some money" and "you are past the week". */
.sp-over { margin: -.5rem 0 .9rem; padding: .55rem .8rem; font-size: .82rem;
           background: color-mix(in srgb, var(--gold, #B7791F) 10%, transparent);
           border-left: 3px solid var(--gold, #B7791F); }
.sp-over.is-beyond { background: color-mix(in srgb, var(--danger) 9%, transparent);
                     border-left-color: var(--danger); }
.sp-over strong { font-family: var(--mono); font-variant-numeric: tabular-nums; }

.sp-day { font-family: var(--mono); font-size: .58rem; text-transform: uppercase; letter-spacing: .1em;
          color: var(--muted); margin: .9rem 0 .3rem; padding-bottom: .25rem;
          border-bottom: 1px solid var(--line); }
.sp-day:first-of-type { margin-top: 0; }
.sp-row { display: flex; align-items: center; gap: .6rem; padding: .45rem 0; }
.sp-row-what { flex: 1; min-width: 0; font-size: .86rem; overflow: hidden;
               text-overflow: ellipsis; white-space: nowrap; }
.sp-row-cat { font-family: var(--mono); font-size: .6rem; text-transform: uppercase; letter-spacing: .06em;
              color: var(--muted); white-space: nowrap; }
.sp-row-cat--none { color: var(--danger); opacity: .75; }
.sp-row-amt { font-family: var(--mono); font-size: .88rem; font-weight: 600;
              font-variant-numeric: tabular-nums; white-space: nowrap; }
/* A 32px target, because the one time you need it you are annoyed already. */
.sp-undo { display: flex; align-items: center; justify-content: center; width: 2rem; height: 2rem;
           padding: 0; font: inherit; font-size: .8rem; color: var(--muted); cursor: pointer;
           background: none; border: 0; }
.sp-undo:hover { color: #fff; background: var(--danger); }

.tabs { margin-bottom: 1.25rem; flex-wrap: wrap; }
/* The tab that leaves the page. Set apart by a hairline rather than a colour:
   it is still the money section, it just isn't another view of /budget. */
.seg.tabs .tabs-out { margin-left: .35rem; padding-left: .75rem;
                      border-left: 1px solid var(--line-strong); }

.pos { color: var(--ok); }

/* Runway chart — SVG draws shapes (fills a fixed-height box); labels are HTML */
/* Budget: runway stat tiles adopt the ribbon-tile language. */
.bud-card { --ribbon: var(--line-strong); border: 0; border-top: 3px solid var(--ribbon); box-shadow: none; background: var(--panel); }
.bud-liquid  { --ribbon: #00A884; }   /* green — money in hand */
.bud-cost    { --ribbon: #8E3A7E; }   /* plum  — cost / burn */
.bud-runway  { --ribbon: #2F6FB0; }   /* blue  — capacity / data */
.bud-runsout { --ribbon: #EE7B3C; }   /* ember — attention date */
.bud-card .card-value { font-variant-numeric: tabular-nums; }
/* The hopeful second reading on a runway tile — a held day is money, but it is
   money with a condition on it, so it never gets the tile's own weight. */
.bud-hope { margin-top: .3rem; font-family: var(--mono); font-size: .64rem;
            letter-spacing: .02em; color: var(--muted); font-variant-numeric: tabular-nums; }
/* When the liquid figure was typed in. Ember once the month-end check-in is owed:
   a stale balance is the one thing that quietly makes the whole forecast wrong. */
.bud-asof { margin-top: .3rem; font-family: var(--mono); font-size: .64rem;
            letter-spacing: .02em; color: var(--muted); }
.bud-asof.due { color: #EE7B3C; }
/* The month-end check-in: one row per account, one Save for all of them. */
.bud-checkin { margin-bottom: 1rem; }
.bud-checkin > summary { cursor: pointer; }
.bud-checkin-form { margin-top: .75rem; display: grid; gap: .45rem; max-width: 32rem; }
.bud-checkin-row { display: flex; align-items: center; gap: .6rem; }
.bud-checkin-name { flex: 1; min-width: 0; }
.bud-checkin-row .amt { width: 8rem; text-align: right; font: inherit; font-variant-numeric: tabular-nums;
                        padding: .45rem .55rem; border: 1px solid var(--line); border-radius: 0; }
.bud-checkin-form .btn { justify-self: start; }
/* The pencilled figure in the chart header, keyed to the dashed line's colour. */
.rc-hope-key { color: #7aa98a; }

/* Section headers to the card-header size with a hairline rule beneath. */
.budget-page .panel h2, .runway-chart .rc-head h2 { font-size: .92rem; font-weight: 600; letter-spacing: -.005em; }
.budget-page .panel > h2, .budget-page .panel-head, .runway-chart .rc-head { padding-bottom: .5rem; margin-bottom: .85rem; border-bottom: 1px solid var(--line-strong); }
.budget-page .panel-head h2 { padding: 0; margin: 0; border: 0; }
.budget-page .panel-head .small, .rc-head .small { font-family: var(--mono); font-size: .66rem; letter-spacing: .02em; }

/* Weekly flexible spend — the flexible half of the budget divided by the month's
   real length in weeks (days ÷ 7, see weeks_in_month()). Fixed lines never enter
   it: rent lands once, it isn't spent a seventh at a time. */
.bud-weekly { display: flex; align-items: center; gap: 1.1rem; flex-wrap: wrap;
              margin: .1rem 0 .9rem; padding: .85rem 1rem;
              background: var(--bg); border-left: 3px solid #8E3A7E; }
.bw-figure { font-family: var(--mono); font-size: 1.5rem; font-weight: 600; line-height: 1;
             letter-spacing: -.01em; font-variant-numeric: tabular-nums; white-space: nowrap; }
.bw-per { font-size: .8rem; font-weight: 500; color: var(--muted); margin-left: .15rem; }
.bw-note { font-size: .8rem; color: var(--ink-soft); }
.bw-note strong { font-family: var(--mono); font-weight: 600; font-variant-numeric: tabular-nums; }
.bw-sub { display: block; font-size: .72rem; color: var(--muted); margin-top: .15rem; }

/* Fixed / Flexible group heads carry their own subtotal. */
.bud-grouphead { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem;
                 margin: 1.3rem 0 .55rem; padding-bottom: .35rem; border-bottom: 1px solid var(--line); }
.bud-grouphead .mini-h { margin: 0; }
.bud-groupsum { font-family: var(--mono); font-size: .68rem; letter-spacing: .02em; color: var(--muted);
                font-variant-numeric: tabular-nums; white-space: nowrap; }

/* Per-line weekly equivalent — a quiet column beside the amount box. Blank on
   fixed rows so the Save/delete buttons stay in one vertical line. */
.bud-wk { width: 5.5rem; text-align: right; font-family: var(--mono); font-size: .72rem;
          color: var(--muted); font-variant-numeric: tabular-nums; white-space: nowrap; }

/* ---- Cost marks ---------------------------------------------
   One tile per kind of cost, worked out from the line's own label (budget_kind()
   in app/budget.php). Built exactly like the document-kind tile (.dk) — the hue
   at 11% behind the glyph, a hairline at 26% — so a marked list looks the same
   here as it does in the Library, and thirty rows of them still read as one calm
   column rather than a bag of stickers.

   Seven families, not seven-and-twenty categories: the icon says which cost it
   is, the colour only says what KIND of life it belongs to. Colours are lifted
   from the set already in use elsewhere in the app, so nothing new is invented
   for this screen. Every hex here is also legible in the dark theme, where the
   11% wash sits on ink instead of paper. */
.bm-home   { --bm: #3F5487; }   /* indigo — the roof and the bills that come with it */
.bm-food   { --bm: #5F7A3A; }   /* moss   — food, in and out */
.bm-move   { --bm: #2F6FB0; }   /* blue   — getting around */
.bm-care   { --bm: #0F7285; }   /* teal   — keeping yourself going */
.bm-life   { --bm: #6A4BA8; }   /* violet — life outside work */
.bm-work   { --bm: #8E3A7E; }   /* plum   — the work itself */
.bm-money  { --bm: #C05F27; }   /* ember  — money owed or set aside */
.bm-liquid { --bm: #00A884; }   /* green  — cash runway can reach (as .bud-liquid) */
.bm-other  { --bm: var(--muted); }   /* a line we couldn't name — colour withheld */
.bm {
    flex: none; display: inline-grid; place-items: center;
    width: 1.8rem; height: 1.8rem;
    color: var(--bm, var(--muted));
    background: color-mix(in srgb, var(--bm, var(--muted)) 11%, transparent);
    box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--bm, var(--muted)) 26%, transparent);
}
/* A hair larger than the document tile's glyph (.dk-ico, 14px): these marks are
   doing more work — telling thirty near-identical rows apart, not labelling one. */
.bm-ico { width: 15px; height: 15px; }
/* An empty tile's worth of space, so the "add" forms keep their fields in the
   same columns as the rows above them. Nothing to see: a cost with no name yet
   has no kind yet either, and a tile there would be asserting one. */
.bm-slot { flex: none; width: 1.8rem; }

.rc-head { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem; flex-wrap: wrap; margin-bottom: .6rem; }
.rc-head h2 { margin: 0; }
.rc-plot { position: relative; }
.rc-svg { width: 100%; height: clamp(170px, 30vw, 260px); display: block; }
.rc-dot { position: absolute; width: 9px; height: 9px; border-radius: 50%; background: #3b7a4e; border: 2px solid #fff; transform: translate(-50%, -50%); box-shadow: 0 0 0 1px #c9ddce; cursor: pointer; }
.rc-dot.neg { background: var(--danger); box-shadow: 0 0 0 1px #eecaca; }
/* Bigger invisible hit area so the tiny dot is easy to hover/tap. */
.rc-dot::after { content: ""; position: absolute; inset: -9px; border-radius: 50%; }
/* Hover/focus tooltip: the month + its rounded balance, floated above the dot. */
.rc-tip { position: absolute; left: 50%; bottom: calc(100% + 8px); transform: translateX(-50%);
          white-space: nowrap; background: var(--ink); color: #fff; font-size: .68rem; font-weight: 600;
          font-variant-numeric: tabular-nums; padding: .22rem .5rem; border-radius: 4px;
          pointer-events: none; opacity: 0; transition: opacity .12s; z-index: 6; }
.rc-dot:hover .rc-tip, .rc-dot:focus .rc-tip, .rc-dot:focus-visible .rc-tip { opacity: 1; }
.rc-xaxis { display: flex; justify-content: space-between; margin-top: 1.1rem; padding-top: .3rem; }
.rc-xaxis span { font-family: var(--mono); font-size: .64rem; text-transform: uppercase; letter-spacing: .06em; color: var(--muted); }

/* End-of-month cards */
/* Month breakdown — folded into the chart panel as one divided strip (no cards). */
.rc-months { display: grid; grid-template-columns: repeat(auto-fit, minmax(100px, 1fr)); gap: 1px; margin-top: 1.2rem; border-top: 1px solid var(--line); background: var(--line); }
.rc-month { background: var(--panel); padding: .75rem .6rem .65rem; text-align: center; }
.rc-month.neg { background: #fdf3f2; }
.rcm-label { display: block; font-family: var(--mono); font-size: .6rem; text-transform: uppercase; letter-spacing: .1em; color: var(--muted); }
.rcm-bal { display: block; font-family: var(--mono); font-size: 1.05rem; font-weight: 600; margin: .2rem 0 .1rem; font-variant-numeric: tabular-nums; }
.rc-month.neg .rcm-bal { color: var(--danger); }
.rcm-delta { display: block; font-family: var(--mono); font-size: .72rem; font-weight: 500; }
.rcm-delta.pos { color: var(--ok); }
.rcm-delta.minus { color: var(--gold); }
.full-proj summary { cursor: pointer; font-weight: 600; }
.full-proj[open] summary { margin-bottom: .8rem; }
.budget-table { width: 100%; border-collapse: collapse; font-size: .85rem; }
.budget-table th { text-align: left; font-family: var(--mono); font-size: .6rem; text-transform: uppercase; letter-spacing: .1em; color: var(--muted); padding: 0 .6rem .5rem; border-bottom: 1px solid var(--line-strong); }
.budget-table th.r, .budget-table td.r { text-align: right; }
.budget-table td { padding: .5rem .6rem; border-bottom: 1px solid var(--rule-soft, #eef1f5); }
.budget-table td.r { font-family: var(--mono); font-variant-numeric: tabular-nums; }
.budget-table td.end { font-weight: 600; color: var(--ink); }
.budget-table tr.neg td.end { color: var(--danger); }
.budget-table tr.neg { background: #fbeceb55; }

.edit-row { display: flex; gap: .5rem; align-items: center; margin: 0 0 .5rem; flex-wrap: wrap; }
.edit-row input[type=text], .edit-row select, .edit-row input[type=number] { font: inherit; padding: .45rem .55rem; border: 1px solid var(--line); border-radius: 0; }
.edit-row > input[name=description], .edit-row > input[name=name] { flex: 1; min-width: 140px; }
.edit-row .amt { width: 6.5rem; text-align: right; }
.edit-row .yr { width: 5rem; }
.edit-row .btn-ghost.danger { color: var(--muted); padding: .4rem .6rem; }
.edit-row .btn-ghost.danger:hover { color: #fff; background: var(--danger); border-color: var(--danger); }


/* ---- Where a check goes -------------------------------------
   Two panels, one idea. The Budget page holds the standing percentages and
   judges them; the Invoices sidebar divides a real number by them. They share
   the four share colours below so a share is the same colour wherever you meet
   it: fixed and flexible take the two living hues already used by the cost
   marks, savings takes runway's green (that money is runway), charity takes the
   violet of life outside work. */
.pcs-fixed,    .bs-field--fixed,    .pcs-row--fixed    { --sh: #3F5487; }
.pcs-flexible, .bs-field--flexible, .pcs-row--flexible { --sh: #5F7A3A; }
.pcs-savings,  .bs-field--savings,  .pcs-row--savings  { --sh: #00A884; }
.pcs-charity,  .bs-field--charity,  .pcs-row--charity  { --sh: #6A4BA8; }

/* -- Budget page: the percentages, and whether they survive the budget -- */
.bud-split { margin-top: 1.1rem; }
.bs-form { display: flex; flex-wrap: wrap; align-items: flex-end; gap: .6rem; margin-bottom: .9rem; }
.bs-field { position: relative; display: flex; align-items: center; gap: .35rem;
            padding-left: .55rem; border-left: 3px solid var(--sh, var(--line-strong)); }
.bs-label { font-size: .78rem; font-weight: 600; color: var(--ink-soft); min-width: 3.6rem; }
.bs-field input { font: inherit; font-family: var(--mono); width: 3.6rem; text-align: right;
                  padding: .4rem .45rem; border: 1px solid var(--line); border-radius: 0; }
.bs-pc { font-family: var(--mono); font-size: .72rem; color: var(--muted); }
.bs-warn { margin: 0 0 .8rem; padding: .5rem .7rem; font-size: .8rem;
           background: color-mix(in srgb, var(--gold, #B7791F) 10%, transparent);
           border-left: 3px solid var(--gold, #B7791F); }

/* The three sentences that say which shares are costs and which only look it. */
.bs-notes { margin: 0 0 1rem; padding: 0 0 0 1.1rem; font-size: .8rem; color: var(--ink-soft); }
.bs-notes li { margin-bottom: .25rem; }
.bs-notes strong { font-family: var(--mono); font-variant-numeric: tabular-nums; }

/* The check itself: two figures and a verdict, the verdict carrying the colour
   so a glance at the panel edge already tells you which way it went. */
.bs-check { padding: .8rem .9rem; background: var(--bg); border-left: 3px solid var(--ok); }
.bs-check.is-short { border-left-color: var(--danger); }
.bs-check-line { display: flex; align-items: baseline; flex-wrap: wrap; gap: .5rem; margin-bottom: .3rem; }
.bs-check-label { font-family: var(--mono); font-size: .6rem; text-transform: uppercase;
                  letter-spacing: .1em; color: var(--muted); min-width: 6.5rem; }
.bs-check-val { font-family: var(--mono); font-size: 1.05rem; font-weight: 600;
                font-variant-numeric: tabular-nums; }
.bs-check-note { font-size: .76rem; color: var(--muted); }
.bs-verdict { margin: .5rem 0 0; padding-top: .5rem; font-size: .82rem;
              border-top: 1px solid var(--line); }
.bs-verdict strong { font-family: var(--mono); font-variant-numeric: tabular-nums; }
.bs-check-none { margin: 0; }
/* Said only when the screen and the database disagree — a moved figure should
   never be a mystery, and "this is your saved budget" on every visit is noise. */
.bs-preview { margin: .5rem 0 0; font-family: var(--mono); font-size: .66rem;
              letter-spacing: .02em; color: var(--muted); }

/* Given-away money in the runway table reads as a deduction, not as income. */
.budget-table td.given { color: var(--muted); }

/* -- Invoices sidebar: the calculator -- */
.pcs { margin-top: 1rem; }
.pcs .panel-head { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem;
                   padding-bottom: .5rem; margin-bottom: .8rem; border-bottom: 1px solid var(--line-strong); }
.pcs .panel-head h2 { margin: 0; font-size: .92rem; font-weight: 600; }
.pcs-in { display: flex; align-items: center; gap: .35rem; padding: .1rem .55rem;
          border: 1px solid var(--line); }
.pcs-in:focus-within { border-color: var(--accent); }
.pcs-cur { font-family: var(--mono); color: var(--muted); }
.pcs-in input { flex: 1; min-width: 0; font: inherit; font-family: var(--mono); font-size: 1.05rem;
                padding: .45rem 0; border: 0; background: none; outline: none; }
/* One tap for the invoices still in play. */
.pcs-quick { display: flex; flex-wrap: wrap; gap: .35rem; margin-top: .5rem; }
.pcs-chip { display: inline-flex; align-items: baseline; gap: .35rem; padding: .25rem .5rem;
            font: inherit; font-size: .72rem; color: var(--ink-soft); cursor: pointer;
            background: var(--bg); border: 1px solid var(--line); }
.pcs-chip strong { font-family: var(--mono); font-variant-numeric: tabular-nums; }
.pcs-chip:hover { border-color: var(--line-strong); }
.pcs-chip.on { border-color: var(--accent); color: var(--accent); }
.pcs-empty { margin: .8rem 0 0; }

/* What is actually left to divide. The amount that LANDED is in the box above,
   so repeating it here would be the same number twice with different meanings. */
.pcs-net { margin: .9rem 0 .7rem; }
.pcs-net-val { display: block; font-family: var(--mono); font-size: 1.5rem; font-weight: 600;
               line-height: 1; letter-spacing: -.01em; font-variant-numeric: tabular-nums; }
.pcs-net-sub { display: block; margin-top: .25rem; font-size: .72rem; color: var(--muted); }

.pcs-rows { list-style: none; margin: 0; padding: 0; }
.pcs-row { display: flex; align-items: baseline; gap: .5rem; padding: .4rem 0 .4rem .55rem;
           border-bottom: 1px solid var(--rule-soft, #eef1f5);
           border-left: 3px solid var(--sh, var(--line-strong)); }
.pcs-row:last-child { border-bottom: 0; }
.pcs-label { font-size: .82rem; color: var(--ink); }
.pcs-pct { font-family: var(--mono); font-size: .66rem; color: var(--muted); }
.pcs-amt { margin-left: auto; font-family: var(--mono); font-size: .86rem; font-weight: 600;
           font-variant-numeric: tabular-nums; }
.pcs-foot { margin: .8rem 0 0; padding-top: .7rem; font-size: .8rem;
            border-top: 1px solid var(--line); }
.pcs-foot strong { font-family: var(--mono); font-variant-numeric: tabular-nums; }
.pcs-warn { margin: .6rem 0 0; font-size: .76rem; color: var(--danger); }

.scenario-row { display: flex; justify-content: space-between; align-items: center; gap: 1rem; padding: .55rem 0; border-bottom: 1px solid var(--line); }
.scenario-row:last-of-type { border-bottom: 0; }
.scenario-actions { display: flex; gap: .8rem; }

/* "Start from" on the new-scenario form — a label wrapping its select so the
   words and the control move together when the row wraps on a narrow screen. */
.sc-from { display: inline-flex; align-items: center; gap: .4rem; font-size: .8rem; color: var(--ink-soft); white-space: nowrap; }
.sc-from select { font: inherit; padding: .45rem .55rem; border: 1px solid var(--line); border-radius: 0; }

/* ---- Budget: "Try a change" ---------------------------------
   One slider per cost, and the figures that depend on them updating as you
   drag. Rows share a grid so names, percentages and amounts stay in columns. */
.bud-adjust { margin-top: 1.1rem; }
.ba-master { display: flex; align-items: center; gap: .8rem; padding: 0 0 .8rem; margin-bottom: .5rem;
             border-bottom: 1px solid var(--line); }
.ba-master label { display: flex; align-items: center; gap: .7rem; flex: 1; font-size: .8rem;
                   font-weight: 600; color: var(--ink-soft); }
.ba-master input[type=range] { flex: 1; }
.ba-master-val { font-family: var(--mono); font-size: .74rem; color: var(--muted); min-width: 3.5rem; text-align: right; }

.ba-row { display: grid; grid-template-columns: minmax(0, 1fr) 4.5rem minmax(6rem, 1.6fr) 3.6rem 6.5rem 1.5rem;
          gap: .8rem; align-items: center; padding: .45rem 0; border-bottom: 1px solid var(--rule-soft, #eef1f5); }
.ba-row:last-of-type { border-bottom: 0; }
/* The name cell carries the cost's mark. In here the tile would crowd a row
   that is mostly slider, so the mark is the bare glyph in its family colour —
   the same shape and the same hue as the tile in the list above, one weight
   lighter. */
.ba-name { display: flex; align-items: center; gap: .45rem; min-width: 0; font-size: .86rem; }
.ba-nametext { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ba-mark { flex: none; display: inline-grid; place-items: center; color: var(--bm, var(--muted)); }
.ba-mark .bm-ico { width: 13px; height: 13px; }
.ba-kind { font-family: var(--mono); font-size: .56rem; text-transform: uppercase; letter-spacing: .08em; color: var(--muted); }
.ba-pct { font-family: var(--mono); font-size: .72rem; color: var(--muted); text-align: right; }
.ba-pct.up { color: var(--accent); }
.ba-pct.down { color: var(--ok); }
.ba-amt { font-family: var(--mono); font-size: .82rem; font-weight: 600; text-align: right;
          font-variant-numeric: tabular-nums; }

/* The figures the sliders move. Only what can be reproduced exactly lives here. */
.ba-summary { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 1px;
              margin-top: 1rem; background: var(--line); border-top: 1px solid var(--line); }
.ba-fig { background: var(--panel); padding: .7rem .8rem; }
.ba-fig-label { display: block; font-family: var(--mono); font-size: .58rem; text-transform: uppercase;
                letter-spacing: .1em; color: var(--muted); }
.ba-fig-val { display: inline-block; margin-top: .2rem; font-family: var(--mono); font-size: 1.05rem;
              font-weight: 600; font-variant-numeric: tabular-nums; }
.ba-fig-delta { margin-left: .4rem; font-family: var(--mono); font-size: .72rem; font-weight: 600; }
.ba-fig-delta.up { color: var(--accent); }
.ba-fig-delta.down { color: var(--ok); }
.ba-note { margin: .7rem 0 0; }
.ba-locknote { margin: -.1rem 0 .5rem; }

/* A locked line is held, not disabled-looking — it still shows its amount, it
   just stops answering to the Everything slider. The padlock carries the state. */
.ba-lock { background: none; border: 0; padding: .15rem; cursor: pointer; line-height: 0;
           color: var(--muted); border-radius: 2px; }
.ba-lock:hover { color: var(--ink); background: var(--wash); }
.ba-lock.on { color: var(--accent); }
.ba-lock svg rect { fill: currentColor; }
.ba-lock svg path { fill: none; stroke: currentColor; stroke-width: 1.5; }
.ba-lock:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.ba-row.is-locked .ba-name { color: var(--ink-soft); }
.ba-row.is-locked input[type=range] { opacity: .35; cursor: not-allowed; }
.bud-adjust input[type=range]:disabled { cursor: not-allowed; }
.ba-master input[type=range]:disabled { opacity: .35; }

/* Toggled by :class, not x-show — see budget_adjust.php. */
.ba-hide { display: none !important; }   /* utility: must beat the element's own display */

/* Sliders in the app's own language, not the browser's blue. Firefox needs the
   -moz- rules spelled out separately; it ignores the -webkit- ones entirely. */
.bud-adjust input[type=range] { -webkit-appearance: none; appearance: none; width: 100%;
    height: 2px; background: var(--line-strong); border-radius: 0; cursor: pointer; }
.bud-adjust input[type=range]:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px; }
.bud-adjust input[type=range]::-webkit-slider-thumb { -webkit-appearance: none; width: 13px; height: 13px;
    border-radius: 50%; background: var(--accent); border: 2px solid var(--panel); box-shadow: 0 0 0 1px var(--accent); }
.bud-adjust input[type=range]::-moz-range-thumb { width: 13px; height: 13px; border-radius: 50%;
    background: var(--accent); border: 2px solid var(--panel); box-shadow: 0 0 0 1px var(--accent); }
.bud-adjust input[type=range]::-moz-range-track { height: 2px; background: var(--line-strong); }

.ba-save { display: flex; align-items: center; gap: .8rem; flex-wrap: wrap;
           margin-top: 1rem; padding-top: .9rem; border-top: 1px solid var(--line-strong); }
.ba-saveas { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; }
.ba-saveas input { font: inherit; padding: .45rem .55rem; border: 1px solid var(--line); border-radius: 0; }

/* ---- Hotsheet dashboard ------------------------------------- */
/* Tiles sized to match the Sheet view's compact cards. */

/* Dashboard title inherits the shared .page-head h1 (matches Projects "All jobs"). */

/* Dashboard widgets — bring the section headers, links and mini-calendar title
   into the same mono/uppercase micro-label voice as the stat grid, so the page
   reads as one system rather than two. */

/* ---- Dashboard: calendar up beside the tiles, graph below --- */
/* Let the month fill the row height so it lines up with the tile block. */

/* ---- Dashboard activity band: compact "Booked this month" over the merged
   "Recent activity" feed (notes/files/invoices, pinnable, category-filtered) ---- */
/* Card titles that double as links to their full page (Projects / Mail). Look
   like the heading; only the hover reveals they navigate. */

/* Shared leading logo tile (booked chips). */
.dr-logo { width: 1.9rem; height: 1.9rem; border-radius: 0; flex-shrink: 0; overflow: hidden;
    display: flex; align-items: center; justify-content: center; background: var(--panel); border: 1px solid var(--line); }
.dr-logo img { width: 100%; height: 100%; object-fit: contain; }
.dr-logo-mono { font-size: .82rem; font-weight: 700; color: var(--muted); }
.dr-meta { font-family: var(--mono); font-size: .66rem; color: var(--muted); white-space: nowrap; }


/* Lately's category filters (was the Recent activity card). */
.da-tabs { display: flex; gap: .35rem; flex-wrap: wrap; }
.da-tab { font-size: .74rem; font-weight: 600; padding: .25rem .6rem; border-radius: 2px; border: 1px solid var(--line);
    background: var(--panel); color: var(--muted); cursor: pointer; display: inline-flex; align-items: center; gap: .35rem; }
.da-tab:hover { color: var(--ink); border-color: var(--muted); }
.da-tab.on { background: var(--ink); color: #fff; border-color: var(--ink); }
.da-tab-n { font-size: .66rem; font-weight: 700; opacity: .65; }
.da-empty { margin: .4rem 0 0; }

.da-list { list-style: none; margin: .3rem 0 0; padding: 0; }
.da-row { position: relative; display: grid; grid-template-columns: auto minmax(0,1fr) auto auto auto; align-items: center; gap: .3rem .9rem; padding: .5rem .35rem; border-top: 1px solid var(--line); }
.da-row:first-child { border-top: 0; }
.da-row:hover { background: var(--bg); }
.da-row--pinned { background: rgba(238, 123, 60, .08); box-shadow: inset 3px 0 0 var(--accent); }
.da-row--pinned:hover { background: rgba(238, 123, 60, .13); }

.da-ico { width: 2rem; height: 2rem; border-radius: 0; flex: 0 0 auto; display: flex; align-items: center;
    justify-content: center; font-size: 1rem; background: var(--panel); border: 1px solid var(--line); overflow: hidden; }
.da-ico img { width: 100%; height: 100%; object-fit: cover; }
.da-ico--invoice { background: #f6f1e4; border-color: #e7dcbf; }
.da-ico--note { background: #eef2f7; }
.da-ico--doc { background: #eef2f7; }
/* Client logo standing in for a row's icon: contain (don't crop) on a plain
   ground, so a wordmark or non-square mark reads cleanly. */
.da-ico img.da-logo { object-fit: contain; padding: 2px; background: #fff; box-sizing: border-box; }
.da-ico:has(.da-logo), .da-ico:has(.da-ico-mono) { background: #fff; }
.da-ico-mono { font-size: .82rem; font-weight: 700; color: var(--muted); line-height: 1; }

.da-title { min-width: 0; font-size: .88rem; font-weight: 500; line-height: 1.25; }
.da-stretch { display: block; color: inherit; text-decoration: none; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.da-stretch::after { content: ''; position: absolute; inset: 0; }        /* whole row is the link */
/* The context token (client / project) is its own link, lifted above the whole-row
   overlay so it can carry you somewhere different from the row's main target. */
.da-ctx { position: relative; z-index: 3; color: inherit; text-decoration: none; border-bottom: 1px solid transparent; }
.da-ctx:hover { color: var(--accent); border-bottom-color: currentColor; }
.da-row:hover .da-title { color: var(--accent); }
.da-status { display: flex; align-items: center; gap: .4rem; justify-self: end; }
/* Lists lean into the mono voice so they feel of a piece with the numerals. */

/* Action pill — what happened (Added / Edited / Sent / Paid). */
.da-act { font-size: .64rem; font-weight: 700; text-transform: none; letter-spacing: .06em; white-space: nowrap;
    flex: 0 0 auto; align-self: center; padding: .2rem .5rem; border-radius: 2px; border: 1px solid var(--line);
    background: var(--panel); color: var(--muted); }
.da-act--paid { color: var(--ok); border-color: #c7e0cf; background: #e7efe9; }
.da-act--sent { color: #97772f; border-color: #e7dcbf; background: #f6f1e4; }
.da-act--new  { color: var(--ink-soft); }
.da-act--add  { color: var(--ink-soft); }

/* Pin toggle — sits above the stretched row-link so it stays clickable. */
.da-pinform { position: relative; z-index: 1; flex: 0 0 auto; margin: 0; line-height: 0; }
.da-pin { border: 0; background: none; cursor: pointer; padding: .3rem; border-radius: 6px; line-height: 0;
    color: var(--muted); opacity: 0; transition: opacity .12s, color .12s, background .12s, transform .12s; }
.da-pin-ico { width: 15px; height: 15px; display: block; }
.da-row:hover .da-pin { opacity: .7; }
.da-pin:hover { opacity: 1; color: var(--gold); background: var(--bg); transform: rotate(8deg); }
.da-pin.is-pinned { opacity: 1; color: var(--gold); }

@media (max-width: 760px) {
}

.calc h2 { margin-top: 0; }
.calc-row { display: flex; align-items: center; justify-content: space-between; gap: 1rem; margin-bottom: .5rem; }
.calc-row label { font-size: .88rem; color: var(--ink-soft); }
.calc-row input { width: 8rem; font: inherit; padding: .45rem .55rem; border: 1px solid var(--line); border-radius: 0; text-align: right; }
.calc-out { margin-top: 1rem; border-top: 1px solid var(--line); padding-top: .8rem; }
.calc-out > div { display: flex; justify-content: space-between; padding: .3rem 0; font-size: .92rem; }
.calc-out > div span { color: var(--ink-soft); }
.calc-out .gold { color: var(--gold); }
.calc-out .pos { color: var(--ok); }
.calc-net { border-top: 1px solid var(--line); margin-top: .3rem; padding-top: .6rem !important; font-size: 1.05rem !important; }
/* Calculator cards get a punchy key-line pop too, so they read as part of
   the new language rather than plain white panels. */
.calc { border-top: 3px solid #00A884; font-family: var(--mono); }
.calc + .calc { border-top-color: #2F6FB0; }
.panel.calc h2 { font-family: var(--font); font-size: .92rem; font-weight: 600; color: var(--ink); }
.task-peek { border-top: 3px solid #8E3A7E; }
.task-peek .panel-head h2 { font-family: var(--font); font-size: .92rem; font-weight: 600; color: var(--ink); }
.year-graph { border-top: 3px solid #EE7B3C; }
.split-line { align-items: center; }
.split-line .pct { width: 3.6rem; font: inherit; padding: .3rem .4rem; border: 1px solid var(--line); border-radius: 6px; text-align: right; }
.split-line span { flex: 1; color: var(--ink-soft); }
.pct-warn { color: var(--gold); font-size: .8rem; }

/* ---- Clients list ------------------------------------------- */
.client-list { display: flex; flex-direction: column; gap: 0; }
.client-row {
    /* Every row is its own grid, so a content-sized track (auto, or an fr
       sharing space with one) resolves per row: a client carrying "Fetch
       details" lands its columns in different places than one without, and
       the list goes ragged down the page. Only name and email may flex;
       the four trailing columns are fixed so every row agrees on the edges. */
    display: grid; gap: 1rem; align-items: center;
    grid-template-columns: minmax(0, 1.6fr) minmax(0, 1.5fr) 7.25rem 6rem 3.5rem 9rem;
    background: transparent; border: 0; border-bottom: 1px solid var(--line); border-radius: 0;
    padding: .8rem .5rem; box-shadow: none; color: var(--ink); transition: background .12s;
}
.client-row:hover { background: var(--panel); }
.cl-link { font-weight: 600; }
.cl-actions { text-align: right; }
.cl-contact-info { display: flex; align-items: center; min-width: 0; font-size: .82rem; }
.cl-contact-info .ci-email { color: var(--ink-soft); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cl-contact-info a.ci-email:hover { color: var(--gold); text-decoration: underline; }
[x-cloak] { display: none !important; }
.client-search {
    font: inherit; padding: .5rem .8rem; border: 1px solid var(--line); border-radius: 0;
    background: #fff; min-width: 240px; flex: 1 1 240px; max-width: 380px; color: var(--ink);
}
.client-search:focus { outline: none; border-color: var(--gold); box-shadow: 0 0 0 3px rgba(238,123,60,.15); }
.search-count { margin: -.4rem 0 .8rem; }
.sort-bar { display: flex; align-items: center; gap: .5rem; margin-bottom: 1rem; flex-wrap: wrap; }
.sort-options { display: flex; align-items: center; gap: .5rem; margin-left: auto; flex-wrap: wrap; }
.sort-label { flex: 0 0 auto; }
/* Square hairline chips. These were said to match the email inbox filter chips,
   which were round — the inbox has since moved onto .seg, so the family this
   belongs to is the square one it always actually looked like. */
.sort-bar a { font-family: var(--mono); font-size: .72rem; letter-spacing: .04em; color: var(--ink-soft); padding: .28rem .8rem; border: 1px solid var(--line); border-radius: 2px; background: #fff; }
.sort-bar a.on { background: var(--ink); color: #fff; border-color: var(--ink); }
.sort-bar a:hover:not(.on) { border-color: var(--gold); color: var(--gold); }
.cl-name { font-weight: 600; display: flex; align-items: center; gap: .5rem; min-width: 0; }
.cl-name-text { display: flex; flex-direction: column; gap: .1rem; min-width: 0; }
.cl-name-line { display: flex; align-items: center; gap: .5rem; min-width: 0; }
.cl-web { font-size: .78rem; font-weight: 400; color: var(--muted); display: inline-flex; align-items: center; gap: .15rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.cl-web:hover { color: var(--gold); text-decoration: underline; }
.cl-web-ico { font-size: .7rem; }
.cl-avatar { width: 32px; height: 32px; border-radius: 0; object-fit: cover; flex: none; border: 1px solid var(--line); background: #fff; }
.cl-avatar--mono { display: inline-flex; align-items: center; justify-content: center; background: var(--ink); color: #fff; font-size: .85rem; font-weight: 600; border: 0; }
.detail-title-row { display: flex; align-items: center; gap: 1rem; }
/* The back-link eyebrow and the title stack beside the logo, and the stack is
   held to the logo's own height — otherwise the eyebrow rides up past the top
   of the logo box and the header stops reading as a tidy row of columns. */
.detail-title-row > div { display: flex; flex-direction: column; justify-content: center; gap: .18rem; min-height: 52px; }
.detail-title-row .eyebrow { line-height: 1; }
.detail-title-row h1 { margin: 0; line-height: 1.15; }
.detail-logo { width: 52px; height: 52px; border-radius: 0; object-fit: cover; flex: none; border: 1px solid var(--line); background: #fff; }
.detail-logo--mono { display: inline-flex; align-items: center; justify-content: center; background: var(--ink); color: #fff; font-size: 1.4rem; font-weight: 600; border: 0; }
.logo-field { display: flex; align-items: center; gap: 1rem; }
.logo-preview { width: 64px; height: 64px; border-radius: 10px; border: 1px solid var(--line); background: #fff; display: flex; align-items: center; justify-content: center; overflow: hidden; flex: none; }
.logo-preview img { width: 100%; height: 100%; object-fit: cover; }
.logo-placeholder { font-size: 1.6rem; font-weight: 600; color: var(--muted); }
.cl-dot { width: 9px; height: 9px; border-radius: 50%; background: var(--muted); flex: none; }
.cl-dot.status-active { background: var(--ok); }
.cl-dot.status-dormant { background: var(--gold); }
.cl-dot.status-archived { background: var(--muted); }
.cl-dot.status-lead { background: var(--accent); }
.cl-badge { font-family: var(--mono); font-size: .58rem; text-transform: uppercase; letter-spacing: .08em; color: var(--muted); border: 1px solid var(--line); border-radius: 0; padding: .1rem .45rem; font-weight: 600; }
.cl-rate, .cl-terms, .cl-projects { font-family: var(--mono); font-size: .78rem; color: var(--ink-soft); }
.cl-rate { font-weight: 600; color: var(--ink); }
.cl-rate, .cl-terms { text-align: right; font-variant-numeric: tabular-nums; }

/* Last contact — the column de56913 dropped, back and year-scoped. The arrow is
   the point of it: who started the conversation, not just that one happened. */
.cl-touch { font-family: var(--mono); font-size: .74rem; color: var(--ink-soft); white-space: nowrap;
            display: flex; align-items: baseline; gap: .3rem; }
.cl-arrow { flex: none; width: .95rem; text-align: center; font-size: .9rem; line-height: 1;
            color: var(--accent); cursor: help; }
.cl-touch-on { font-weight: 600; color: var(--ink); }
/* Gone quiet: the date is the useful part, so the word "last" recedes. */
.cl-touch-cold { color: var(--muted); cursor: help; }
.cl-touch-sep { opacity: .5; }
.cl-touch-never { color: var(--muted); font-style: italic; }
/* Booked work is no longer what the grouping means, so it says so on the row. */
.cl-booked { font-family: var(--mono); font-size: .54rem; font-weight: 600; text-transform: uppercase;
             letter-spacing: .08em; color: var(--accent); border: 1px solid currentColor;
             border-radius: 2px; padding: .05rem .28rem; vertical-align: .1em; }
/* Grouped headers (default "Recent" view only) — a hairline separator above each
   run, mirroring the projects List; "Active this year" names get a gold tint. Any
   explicit sort renders one flat list with no groups or highlight. */
.client-group { margin-bottom: 1.4rem; }
.cg-head {
    display: flex; align-items: baseline; gap: .55rem;
    margin: 0 0 .55rem; font-size: .95rem; font-weight: 600;
    padding-bottom: .35rem; border-bottom: 1px solid var(--line);
}
.cg-tag {
    font-family: var(--mono); font-size: .56rem; font-weight: 600; text-transform: uppercase; letter-spacing: .08em;
    color: #fff; background: var(--accent); border-radius: 2px; padding: .12rem .4rem;
    align-self: center;
}
.cg-count { margin-left: auto; font-size: .78rem; font-weight: 400; color: var(--muted); }

/* The arrow legend, so the glyphs in the rows are decodable without a hover. */
.cg-hint { font-family: var(--mono); font-size: .62rem; font-weight: 400; color: var(--muted); letter-spacing: .01em; }
/* "Gone quiet" gets a rail too, but a dim one — it's a list to work through,
   not an alarm. The ember rail stays reserved for who you're actually talking to. */
.client-group--quiet .client-list { border-left: 2px solid var(--line-strong); padding-left: .75rem; margin-left: -.05rem; }
/* "Active this year" is marked by ONE continuous ember rail down the left of the
   group, not a wash behind every row — a filled band reads as a muddy block and
   fights the hairline grid. The rail plus the ember names carry the emphasis;
   hover behaves exactly like any other client row. */
.client-group--year .client-list {
    border-left: 2px solid var(--accent);
    padding-left: .75rem;
    margin-left: -.05rem;
}
.client-group--year .cl-link { color: var(--accent); }

/* ---- Scope of Work form ------------------------------------- */
.sow-form { display: flex; flex-direction: column; gap: 1.2rem; }
.sow-form .panel { margin: 0; box-shadow: none; }
/* Section headers in the redesign's card-header size, hairline rule beneath. */
.sow-form .panel h2 { font-size: .92rem; font-weight: 600; letter-spacing: -.005em; }
.sow-form .panel > h2, .sow-form .panel-head { padding-bottom: .5rem; margin-bottom: .9rem; border-bottom: 1px solid var(--line-strong); }
.sow-form .panel-head h2 { padding: 0; margin: 0; border: 0; }
.sow-form .panel-head .small { font-family: var(--mono); font-size: .66rem; letter-spacing: .02em; }
.sow-field { display: flex; align-items: center; gap: .8rem; margin-top: .7rem; }
.sow-field label { flex: 0 0 90px; font-size: .85rem; color: var(--ink-soft); font-weight: 500; }
.sow-field input { flex: 1; font: inherit; padding: .5rem .6rem; border: 1px solid var(--line); border-radius: 0; background: #fff; }
.sow-lines { display: flex; flex-direction: column; gap: .5rem; margin: .8rem 0; }
.sow-line { display: grid; grid-template-columns: 2fr 2fr .8fr 1fr auto; gap: .5rem; align-items: center; }
.sow-line--head { font-family: var(--mono); font-size: .6rem; color: var(--muted); text-transform: uppercase; letter-spacing: .1em; }
.sow-line input { font: inherit; padding: .45rem .55rem; border: 1px solid var(--line); border-radius: 0; background: #fff; min-width: 0; }
.sow-total { max-width: 360px; margin-top: 1rem; }
.sow-textarea { width: 100%; font: inherit; padding: .55rem .65rem; border: 1px solid var(--line); border-radius: 0; background: #fff; resize: vertical; line-height: 1.5; }
.sow-sublabel { display: block; font-size: .85rem; color: var(--ink-soft); font-weight: 500; margin-bottom: .35rem; }
.sow-actions { display: flex; justify-content: flex-end; gap: .7rem; }

/* ---- Digital-estimate "Share" panel ------------------------------------
   Every control on the row shares one height so the deposit field and the two
   buttons line up cleanly; corners are squared to match the rest of the UI. */
.est-share-controls { display: flex; flex-wrap: wrap; align-items: center; gap: .6rem; margin-top: .8rem; }
.est-share-controls .est-dep { display: inline-flex; align-items: center; gap: .5rem; margin: 0;
    font-size: .85rem; color: var(--ink-soft); font-weight: 500; white-space: nowrap; }
.est-share-controls .est-contract-req { display: inline-flex; align-items: center; gap: .4rem; margin: 0;
    font-size: .85rem; color: var(--ink-soft); font-weight: 500; cursor: pointer; }
.est-share-controls .est-contract-req input { width: auto; height: auto; margin: 0; accent-color: var(--accent); }
.est-share-note { margin-top: 1rem; }
.est-share-linklabel { display: block; margin-top: 1rem; }
.est-share-link { display: flex; gap: .5rem; margin-top: .4rem; max-width: 640px; }
.est-share-link input { flex: 1; }
.est-share .est-dep input,
.est-share .est-share-link input,
.est-share .btn { box-sizing: border-box; height: 2.6rem; border-radius: 0; }
.est-share .est-dep input,
.est-share .est-share-link input { font: inherit; padding: 0 .6rem; border: 1px solid var(--line); background: #fff; }
.est-share .est-dep input { width: 4.5rem; }
.est-share .btn { display: inline-flex; align-items: center; padding: 0 1.1rem; }
/* Save/Cancel moved to the top of the doc forms — sticky so it stays reachable
   while scrolling a long form. Background matches the page so panels scroll
   cleanly underneath it. */
.sow-actions--top {
    position: sticky; top: 0; z-index: 5;
    padding: .7rem 0; margin-bottom: .2rem;
    background: var(--bg); border-bottom: 1px solid var(--line);
}
@media (max-width: 720px) {
    .sow-line { grid-template-columns: 1fr 1fr; }
    .sow-line--head { display: none; }
}

/* ---- Documents: resume experience cards + awards rows ------- */
.panel-head { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem; }
.doc-stack { display: flex; flex-direction: column; gap: .55rem; margin: .8rem 0; }
.doc-entry { border: 1px solid var(--line); border-radius: 0; background: #f8fafb; overflow: hidden; }
.doc-entry__bar { display: flex; align-items: center; gap: .6rem; padding: .55rem .7rem; }
.doc-entry__toggle { flex: 1; min-width: 0; display: flex; align-items: center; gap: .55rem;
    background: none; border: 0; cursor: pointer; font: inherit; text-align: left; color: var(--ink); padding: 0; }
.doc-entry__caret { color: var(--muted); font-size: .8rem; width: .9rem; flex: none; }
.doc-entry__title { font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.doc-entry__meta { font-family: var(--mono); color: var(--muted); font-size: .74rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.doc-entry__flag { flex: none; font-family: var(--mono); font-size: .58rem; text-transform: uppercase; letter-spacing: .08em;
    color: var(--muted); border: 1px solid var(--line); border-radius: 0; padding: .08rem .45rem; }
.doc-entry__actions { flex: none; display: flex; gap: .6rem; }
.doc-entry__num { color: var(--gold); font-weight: 600; font-size: .82rem; flex: none; }
.doc-entry__body { padding: 0 .8rem .9rem; }
.doc-entry__body .doc-field { display: flex; flex-direction: column; gap: .35rem; font-size: .82rem; color: var(--ink-soft); margin-top: .7rem; }
.doc-entry__body .doc-field input, .doc-entry__body .doc-field textarea {
    font: inherit; padding: .5rem .6rem; border: 1px solid var(--line); border-radius: 0; background: #fff; }
.doc-entry--hidden { opacity: .55; }
.doc-entry--hidden .doc-entry__title { text-decoration: line-through; }
.awards-lines .sow-line { grid-template-columns: 2fr .8fr 2.4fr auto; }
@media (max-width: 720px) { .awards-lines .sow-line { grid-template-columns: 1fr 1fr; } }

/* ---- Documents: packages hub -------------------------------- */
.pkg-new { display: flex; gap: .6rem; flex-wrap: wrap; align-items: center; margin: .3rem 0 .2rem; }
.pkg-new input[type=text] { flex: 1; min-width: 240px; font: inherit; padding: .5rem .65rem; border: 1px solid var(--line); border-radius: 0; background: #fff; }
.pkg-new select { font: inherit; padding: .5rem .55rem; border: 1px solid var(--line); border-radius: 0; background: #fff; }
.pkg-list { display: flex; flex-direction: column; gap: .5rem; margin-top: 1rem; }
.pkg-row { display: grid; grid-template-columns: 1.6fr 1fr auto auto; gap: 1rem; align-items: center;
    padding: .7rem .85rem; border: 1px solid var(--line); border-radius: 9px; background: #f8fafb; color: var(--ink); }
.pkg-row:hover { border-color: var(--gold); }
.pkg-row__title { font-weight: 600; }
.pkg-row__client { color: var(--ink-soft); font-size: .88rem; }
.pkg-row__count { font-family: var(--mono); color: var(--muted); font-size: .74rem; }
.pkg-item { display: grid; grid-template-columns: 8.5rem 1fr auto; gap: 1rem; align-items: center;
    padding: .6rem .1rem; border-bottom: 1px solid var(--line); }
.pkg-item:last-child { border-bottom: 0; }
.pkg-item__kind { font-family: var(--mono); font-size: .6rem; text-transform: uppercase; letter-spacing: .08em; color: var(--gold); font-weight: 600; }
.pkg-item__title { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pkg-item__actions { display: flex; align-items: center; gap: .8rem; }
.pkg-add { display: flex; gap: .6rem; flex-wrap: wrap; margin-bottom: 1rem; }
.pkg-add-contract { display: flex; gap: .6rem; align-items: center; flex-wrap: wrap; }
.pkg-add-contract select { font: inherit; padding: .5rem .55rem; border: 1px solid var(--line); border-radius: 0; background: #fff; min-width: 240px; }
@media (max-width: 720px) {
    .pkg-row { grid-template-columns: 1fr auto; }
    .pkg-row__client, .pkg-row__date { display: none; }
    .pkg-item { grid-template-columns: 1fr; gap: .3rem; }
}

/* ---- Documents hub — lighter, more editorial ---------------- */
.docs { max-width: none; }
.docs .page-head { margin-bottom: 1.5rem; }
/* Flatter, quieter panels — no drop shadow, hairline border only. */
.docs .panel { box-shadow: none; padding: 1.5rem 1.65rem; }
.docs .panel h2 { font-size: .92rem; font-weight: 600; letter-spacing: -.005em; }
.docs .panel > h2, .docs .panel-head { padding-bottom: .5rem; margin-bottom: .85rem; border-bottom: 1px solid var(--line-strong); }
.docs .panel-head h2 { padding: 0; margin: 0; border: 0; }
.docs .panel > p { margin: .4rem 0 0; line-height: 1.55; }

/* Two-column layout — main (files + packages) beside a reference sidebar,
   mirroring the client / project detail pages. Collapses to one column. */
.docs-grid { display: grid; grid-template-columns: minmax(0, 1fr) var(--rail-w); gap: 1rem; align-items: start; }
.docs-main, .docs-side { display: flex; flex-direction: column; gap: 1rem; min-width: 0; }
.docs-main > .panel { margin: 0; }
.docs-side .panel { padding: 1.15rem 1.25rem; margin: 0; }
.docs-side .panel h2 { font-size: .92rem; }
@media (max-width: 900px) { .docs-grid { grid-template-columns: 1fr; } }

/* Master documents: two stacked open-links, tight under the description. */
.docs-master-links { display: flex; flex-direction: column; align-items: flex-start; gap: .6rem; margin-top: 1rem; }
.docs-master-links .doc-open { margin-top: 0; }
/* Elegant arrow link instead of a heavy solid button, with room above the type. */
.doc-open {
    display: inline-flex; align-items: center; gap: .45rem;
    margin-top: 1.4rem; align-self: flex-start;
    font-size: .9rem; font-weight: 600; color: var(--ink);
}
.doc-open::after { content: "→"; color: var(--gold); transition: transform .15s ease; }
.doc-open:hover { color: var(--gold); }
.doc-open:hover::after { transform: translateX(3px); }
/* The packages block breathes a little more; create-row separated from the head. */
.docs .pkg-new { margin: .1rem 0 0; padding-top: .2rem; }
.docs .pkg-list { margin-top: 1.1rem; }

/* ---- Export & backup ---------------------------------------- */
.export-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 1.2rem; }
.export-form { display: flex; align-items: center; gap: .6rem; flex-wrap: wrap; margin-top: 1rem; }
.export-form label { font-size: .85rem; color: var(--ink-soft); }
.export-form select { font: inherit; padding: .55rem .6rem; border: 1px solid var(--line); border-radius: 0; background: #fff; }

/* ---- Calendar subscription ---------------------------------- */
.cal-url-row { display: flex; gap: .55rem; margin: 1rem 0 .9rem; flex-wrap: wrap; }
.cal-url { flex: 1 1 16rem; min-width: 0; font: inherit; font-size: .82rem; font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
           padding: .55rem .65rem; border: 1px solid var(--line); border-radius: 0; background: #f8fafb; color: var(--ink-soft); }
.cal-actions { display: flex; flex-wrap: wrap; gap: .5rem; }
.cal-steps { padding-left: 1.15rem; margin: .3rem 0 0; }
.cal-steps li { padding: .18rem 0; }

/* ---- The export lock ----------------------------------------
   The password gate in front of the downloads. Ember edge while it's shut so
   it reads as a gate rather than another panel; quiet green line once open. */
.exp-lock { border-left: 4px solid var(--accent); }
.exp-lock--open { border-left-color: var(--ok); }
.exp-lock-form { display: flex; gap: .55rem; flex-wrap: wrap; margin-top: .9rem; }
.exp-lock-form input[type="password"] { flex: 1 1 14rem; min-width: 0; font: inherit;
    padding: .55rem .65rem; border: 1px solid var(--line); border-radius: 0; background: #fff; }
.exp-lock-open-row { display: flex; align-items: center; justify-content: space-between; gap: .8rem; flex-wrap: wrap; }
.exp-lock-open-row form { margin: 0; }
.exp-lock-open-row .muted { display: block; }
.exp-locked-note { margin: 1rem 0 0; font-style: italic; }

/* ---- Notifications (Config → Notifications) ------------------
   All prefixed .push-* : the panel, the opt-in rows, and the history list.
   The "this device" panel is drawn in three states — unknown until push.js has
   asked the browser, then on or off — because the server has a list of devices
   but no way to tell which row is the browser you're currently looking at. */
.push-panel[data-state="unknown"] .push-when-on,
.push-panel[data-state="unknown"] .push-when-off,
.push-panel[data-state="on"]  .push-when-off,
.push-panel[data-state="off"] .push-when-on { display: none; }
.push-panel[data-state="on"] form.push-when-on { display: inline; }

.push-actions { display: flex; flex-wrap: wrap; gap: .5rem; margin-top: .8rem; }
.push-note { margin: .7rem 0 0; font-size: .82rem; color: var(--ink-soft); min-height: 1.1em; }
.push-note--bad { color: var(--danger); }

/* One opt-in per row: the switch, then what it means. The label text carries the
   weight and the explanation sits under it in muted small — the same shape as the
   rest of Config, so these read as settings rather than as a form to fill in. */
.push-row { display: flex; align-items: flex-start; gap: .7rem; padding: .7rem 0; border-bottom: 1px solid var(--line); cursor: pointer; }
.push-row:last-child { border-bottom: 0; }
.push-row input[type="checkbox"] { margin-top: .22rem; width: 1rem; height: 1rem; flex: 0 0 auto; accent-color: var(--accent); }
.push-row > span { display: flex; flex-direction: column; gap: .15rem; min-width: 0; }
.push-row strong { font-weight: 500; }
.push-row--master { border-bottom: 1px solid var(--line-strong); padding-bottom: .9rem; margin-bottom: .2rem; }
.push-kinds { padding-left: 1.7rem; }

.push-devices td { vertical-align: top; }
.push-devices form { margin: 0; }

.push-log { list-style: none; padding: 0; margin: 0; }
.push-log li { display: flex; align-items: baseline; gap: .6rem; flex-wrap: wrap; padding: .45rem 0; border-bottom: 1px solid var(--line); }
.push-log li:last-child { border-bottom: 0; }
.push-log-when { font-family: var(--mono); font-size: .72rem; flex: 0 0 8.5rem; }
.push-log-title { flex: 1 1 12rem; min-width: 0; }
.push-log-status { font-family: var(--mono); font-size: .68rem; text-transform: uppercase; letter-spacing: .05em; color: var(--muted); }
.push-log-status--sent, .push-log-status--partial { color: var(--ok); }
.push-log-status--error { color: var(--danger); }

/* ---- Client detail ------------------------------------------ */
.rate-history { list-style: none; padding: 0; margin: 0; }
.rate-history li { display: flex; justify-content: space-between; padding: .3rem 0; border-bottom: 1px solid var(--line); font-family: var(--mono); font-size: .78rem; }
.rate-history li:last-child { border-bottom: 0; }
.rh-rate { font-weight: 500; }
/* Confirmed (email/manual) rates share the rate-history row but carry extra bits
   — a note, a source tag, a remove ×. Left-align them and float the × right,
   rather than letting space-between scatter three children across the row. */
.rate-history li.rh-confirmed { justify-content: flex-start; gap: .5rem; align-items: baseline; }
.rh-confirmed .small { color: var(--muted); font-size: .68rem; }
.rh-x { display: inline; margin: 0 0 0 auto; }
/* "Spotted in email" suggestions: without these the rate had no font rule and
   inherited the full body size in a proportional face, so it rendered noticeably
   larger than the rate history above it. Match the rate-history type exactly. */
.rate-sugs { margin-top: .5rem; }
.rate-sug { display: flex; align-items: baseline; gap: .5rem; padding: .25rem 0; font-family: var(--mono); font-size: .78rem; }
.rate-sug .linkbtn-sm { font-size: .72rem; }

/* CONTACTS, in two places and two shapes.
   The sidebar (.ccs-) is a readout: primary first, three people at most, and a
   link into the roster. The roster itself (.cc-) lives in the Workspace card,
   where the full page width buys one ledger line per person — name and role,
   email, phone — instead of the four-line block that made a fifteen-person
   studio scroll the narrowest column on the page. Edit and delete stay hidden
   until you hover the row, so a list of people reads as a list of people.

   Sidebar readout ------------------------------------------------ */
.ccs-list { display: flex; flex-direction: column; }
.ccs-list > * + * { border-top: 1px solid var(--line); }
.ccs-item { padding: .45rem 0; min-width: 0; }
.ccs-top { display: flex; align-items: center; gap: .4rem; min-width: 0; }
.ccs-top strong { font-size: .86rem; font-weight: 600; min-width: 0;
                  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ccs-role { font-size: .72rem; margin-top: .02rem; }
.ccs-sub { font-size: .76rem; margin-top: .06rem;
           overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ccs-more { display: inline-block; margin-top: .6rem; font-size: .78rem; color: var(--muted); }
.ccs-more:hover { color: var(--accent); }

/* The roster ------------------------------------------------------ */
.cc-filter { display: flex; align-items: center; gap: .7rem; margin-bottom: .7rem; }
.cc-filter-in { flex: 1; min-width: 0; font: inherit; font-size: .82rem; padding: .4rem .55rem;
                border: 1px solid var(--line); border-radius: 0;
                background: var(--panel); color: var(--ink); }
.cc-filter-in:focus { outline: none; border-color: var(--accent); }
.cc-list { display: flex; flex-direction: column; }
.cc-list > * + * { border-top: 1px solid var(--line); }
/* Every row is its own grid, so the column widths have to be stated rather than
   sized from content — `auto` would give each row its own idea of where the
   email starts and the list would come out ragged. The phone column is fixed
   and only exists when somebody in this list actually has a number (PHP adds
   .cc-list--tel). The name column keeps a 7rem floor: a truncated person is
   worse than a truncated address. */
.cc-view {
    display: grid; grid-template-columns: minmax(7rem, 1.6fr) minmax(0, 1.4fr) 3.4rem;
    gap: .15rem .9rem; align-items: center; padding: .45rem 0;
}
.cc-list--tel .cc-view { grid-template-columns: minmax(7rem, 1.6fr) minmax(0, 1.4fr) 6.75rem 3.4rem; }
.cc-who { display: flex; align-items: baseline; gap: .4rem; min-width: 0; }
.cc-who > * { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* When the row runs out of room the job title gives way long before the name
   does — you find a person by who they are, not by what they're called there. */
.cc-who strong { font-size: .86rem; font-weight: 600; flex: 0 0 auto; max-width: 100%; }
.cc-who .primary-badge { flex: none; overflow: visible; }
.cc-who .cc-role { flex: 0 1 auto; }
.cc-role { font-size: .78rem; }
.cc-role::before { content: "· "; }
.cc-email { min-width: 0; font-size: .8rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cc-phone { font-family: var(--mono); font-size: .76rem; color: var(--ink-soft);
            font-variant-numeric: tabular-nums; white-space: nowrap;
            overflow: hidden; text-overflow: ellipsis; }
.cc-actions { display: flex; align-items: center; justify-content: flex-end; gap: .45rem;
              opacity: 0; transition: opacity .12s ease; }
.cc-row:hover .cc-actions, .cc-row:focus-within .cc-actions { opacity: 1; }
.cc-actions .linkbtn-sm { color: var(--muted); font-size: .74rem; }
.cc-actions .linkbtn-sm:hover { color: var(--accent); }
.cc-actions .linkbtn-sm.danger:hover { color: var(--danger); }
.cc-del { font-size: 1rem; line-height: 1; padding: 0 .1rem; }
.cc-form { padding: .55rem 0 .75rem; }
.cc-add { margin-top: .8rem; }
/* Touch has no hover to reveal anything with, so the actions simply stay. */
@media (hover: none) { .cc-actions { opacity: 1; } }
/* Four columns need width, and the roster's width is the main column's, not the
   window's — on a 1000px screen the rail takes 320 of it. So the fold is asked
   for by the container as well as by the viewport (the @media below), and
   whichever notices first wins. */
.cc { container-type: inline-size; }
@container (max-width: 560px) {
    /* .cc-list qualifier only so this outweighs .cc-list--tel above — a query
       block doesn't beat a more specific selector on its own. */
    .cc-list .cc-view {
        grid-template-columns: minmax(0, 1fr) 3.2rem;
        grid-template-areas: "who act" "mail act" "tel act";
        align-items: start; padding: .55rem 0;
    }
    .cc-who   { grid-area: who; flex-wrap: wrap; }
    .cc-email { grid-area: mail; }
    .cc-phone { grid-area: tel; }
    .cc-actions { grid-area: act; align-self: center; }
    .cc-role { flex-basis: 100%; }
    .cc-role::before { content: none; }
}

.primary-badge { font-family: var(--mono); font-size: .56rem; text-transform: uppercase; letter-spacing: .08em; background: var(--accent); color: #fff; border-radius: 0; padding: .1rem .4rem; font-weight: 600; }
.contact-edit { display: flex; flex-direction: column; gap: .5rem; }
.cf-row { display: flex; gap: .5rem; flex-wrap: wrap; }
.cf-row input, .cf-row select { flex: 1; min-width: 120px; font: inherit; padding: .45rem .55rem; border: 1px solid var(--line); border-radius: 0; }
.cf-check { font-size: .85rem; color: var(--ink-soft); display: flex; align-items: center; gap: .4rem; }
.cf-actions { display: flex; gap: .5rem; }
.add-block { margin-top: .5rem; }
.add-block summary { cursor: pointer; color: var(--gold); font-size: .88rem; font-weight: 500; }
.add-block[open] summary { margin-bottom: .6rem; }

/* Project rows read as ledger lines, not cards: hairline-separated, no box and
   no fill, so they match the invoice / document / favourite rows on the same
   page. The separator lives on the list child (a row may be a plain link OR a
   <details> job group), and hover is the one shared cue — the title goes ember. */
.mini-project-list { display: flex; flex-direction: column; }
.mini-project-list > * + * { border-top: 1px solid var(--line); }
.mini-project {
    display: grid; grid-template-columns: minmax(0, 1fr) 2.6em 5.6em auto;
    gap: .2rem .9rem; align-items: center;
    padding: .5rem 0; border-radius: 0; color: var(--ink);
}
.mini-project:hover .mp-title { color: var(--accent); }
.mp-title { font-size: .88rem; font-weight: 500; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mp-year { font-family: var(--mono); color: var(--muted); font-size: .72em; font-weight: 400; }
.mp-days { font-family: var(--mono); font-size: .74rem; color: var(--ink-soft); text-align: right; font-variant-numeric: tabular-nums; }
.mp-value { font-family: var(--mono); font-size: .82rem; font-weight: 600; text-align: right; font-variant-numeric: tabular-nums; }
.mp-status { justify-self: start; font-family: var(--mono); font-size: .58rem; text-transform: uppercase; letter-spacing: .04em; padding: .14rem .45rem; border-radius: 0; border: 0; white-space: nowrap; }
.mp-status--mixed { color: var(--muted); }
/* Grouped-job summary row: extra right column carries the disclosure chevron. */
.mini-job-sum { grid-template-columns: minmax(0, 1fr) 2.6em 5.6em auto 1.2em; }
.mini-job-chev { font-family: var(--mono); font-size: .7rem; color: var(--muted); justify-self: end; transition: transform .15s; }
.mini-job[open] > .mini-job-sum .mini-job-chev { transform: rotate(90deg); }
/* the day-rate label on each piece reads bold */
.mini-job-piece .mp-title { font-weight: 600; }

/* A "job" that groups several same-name project pieces under one combined row.
   Open, the pieces indent behind a rail rather than sitting in a grey tray —
   one disclosure idiom, no second surface colour. */
.mini-job { border: 0; border-radius: 0; }
.mini-job > .mini-job-sum { border: 0; border-radius: 0; cursor: pointer; list-style: none; }
.mini-job > .mini-job-sum::-webkit-details-marker { display: none; }
.mini-job[open] > .mini-job-sum { background: none; }
.mini-job-count {
    margin-left: .3em; font-family: var(--mono); font-size: .56rem; font-weight: 600; color: var(--muted);
    text-transform: uppercase; letter-spacing: .08em;
}
.mini-job-pieces {
    display: flex; flex-direction: column; background: none;
    margin: 0 0 .35rem .3rem; padding-left: .85rem; border-left: 2px solid var(--line);
}
.mini-job-pieces > * + * { border-top: 1px solid var(--line); }
.mini-job-piece { background: none; padding: .42rem 0; }

.touchpoint-list { list-style: none; padding: 0; margin: 0 0 .5rem; }
.touchpoint-list li { display: flex; gap: .6rem; align-items: baseline; padding: .35rem 0; border-bottom: 1px solid var(--line); font-size: .84rem; }
.touchpoint-list li:last-child { border-bottom: 0; }
.tp-date { font-family: var(--mono); font-size: .72rem; color: var(--ink-soft); white-space: nowrap; min-width: 7em; }
.tp-ch { font-family: var(--mono); color: var(--gold); font-size: .66rem; text-transform: uppercase; letter-spacing: .06em; white-space: nowrap; }
.tp-sum { color: var(--ink-soft); }

/* ---- Client invoice ledger ---------------------------------- */
.client-invoices { margin-top: 1rem; }
/* One shared column grid for the whole list (via subgrid), so #num / project /
   amount / status / due line up across every row — a longer due date ("in 30
   days") can't push its row's amount out of the column any more. */
.cinv-list { display: grid; grid-template-columns: 2.8rem minmax(0, 1fr) auto auto auto; column-gap: .8rem; }
.cinv-row {
    display: grid; grid-column: 1 / -1; grid-template-columns: subgrid;
    align-items: center;
    padding: .45rem 0; border-bottom: 1px solid var(--line); font-size: .82rem;
}
.cinv-row:last-child { border-bottom: 0; }
.cinv-num { font-family: var(--mono); font-weight: 600; }
.cinv-proj { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cinv-amt { font-family: var(--mono); font-variant-numeric: tabular-nums; font-weight: 600; text-align: right; }
.pay-badge { justify-self: start; }
.cinv-due { font-family: var(--mono); color: var(--ink-soft); white-space: nowrap; font-size: .72rem; text-align: right; }
.cinv-od-text { color: var(--danger); font-weight: 600; }
.cinv-overdue .cinv-num { color: var(--danger); }
@media (max-width: 620px) {
    /* Back to independent rows so each can reflow to two lines on a phone. */
    .cinv-list { display: flex; flex-direction: column; }
    .cinv-row {
        grid-column: auto;
        grid-template-columns: 3.5rem minmax(0, 1fr) auto; column-gap: .8rem; row-gap: .2rem;
    }
    .cinv-row .pay-badge { grid-column: 2; justify-self: start; }
    .cinv-due { grid-column: 3; }
}

/* ---- Invoices tab ------------------------------------------- */
/* Two-column layout, like the Documents hub: calendar + sheet on the left,
   summary cards stacked on the right. */
.inv-layout { display: grid; grid-template-columns: minmax(0, 1fr) var(--rail-w); gap: 1.25rem; align-items: start; }
.inv-main { min-width: 0; }
.inv-side { display: flex; flex-direction: column; gap: 1rem; }
.inv-side .cards { margin-bottom: 0; }
.inv-cards { grid-template-columns: 1fr; }   /* stack the four cards in the narrow rail */
/* Summary tiles in the Dashboard language: flat, square, 3px top ribbon, no
   box border/shadow — one color per money state. */
.inv-card { --ribbon: var(--line-strong); border: 0; border-top: 3px solid var(--ribbon); box-shadow: none; background: var(--panel); }
.inv-card--billed      { --ribbon: #2F6FB0; }   /* blue  — total invoiced */
.inv-card--collected   { --ribbon: #00A884; }   /* green — money in */
.inv-card--outstanding { --ribbon: #EE7B3C; }   /* ember — owed / attention */
.inv-card--overdue     { --ribbon: var(--danger); }   /* red — past due */
.inv-card--fees        { --ribbon: #8A6F31; }   /* brass — the processor's cut */
.inv-card .card-value { font-variant-numeric: tabular-nums; }

/* "kept $X" under a paid invoice's amount — the money that survived the fee. */
.inv-kept { display: block; font-size: .72rem; color: var(--muted); font-variant-numeric: tabular-nums; }

/* The Net column: the same number as Amount minus the tax set-aside. Quieter
   than the gross beside it — it's the reading of that figure, not a second
   claim — but tabular, because the point of a column is to be added up by eye. */
.inv-net-col { white-space: nowrap; }
/* Colour comes from the shared .sh-net column rule now, so these only carry the
   figures' shape. Setting a colour here would beat the column and put the one
   number you actually read back in grey. */
.inv-net { color: inherit; font-variant-numeric: tabular-nums; }
tfoot .inv-net-col { font-variant-numeric: tabular-nums; }

/* The Tax column, sitting between Amount and Net: the money that comes off the
   top. Written as a minus so the three figures read across as the sum they
   actually are — gross, less this, leaves that. Colour is the shared .sh-tax
   rust; only the shape belongs here. */
.inv-taxamt-col { white-space: nowrap; font-variant-numeric: tabular-nums; }
.inv-taxamt { color: inherit; font-variant-numeric: tabular-nums; }
/* Phone list: net rides under the gross in the same right-hand stack. */
.ilr-net { display: block; font-style: normal; font-weight: 400; font-size: .72rem;
    color: var(--muted); font-variant-numeric: tabular-nums; }

/* Worked fee example in Config → Settings: rail, rate, cut, what's left. */
.fee-example { width: 100%; max-width: 34rem; margin: 1rem 0 0; border-collapse: collapse; font-size: .85rem; }
.fee-example caption { text-align: left; font-size: .72rem; text-transform: uppercase; letter-spacing: .06em; color: var(--muted); padding-bottom: .4rem; }
.fee-example td { padding: .38rem .5rem .38rem 0; border-top: 1px solid var(--line); }
.fee-example .fx-rate { color: var(--muted); font-family: var(--mono, monospace); font-size: .76rem; }
.fee-example .fx-cut,
.fee-example .fx-net { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.fee-example .fx-cut { color: var(--muted); }
.fee-example .fx-net { font-weight: 600; }

/* Estimates hub — reuses the invoice layout; its own tile ribbons + status
   badges. Ribbon meaning mirrors the money states: blue total, ember awaiting,
   green approved, neutral drafts/expired. */
.est-card--total    { --ribbon: #2F6FB0; }   /* blue  — total pipeline value */
.est-card--awaiting { --ribbon: #EE7B3C; }   /* ember — out to the client */
.est-card--approved { --ribbon: #00A884; }   /* green — won */
.est-card--other    { --ribbon: var(--line-strong); }
.est-slash { color: var(--muted); font-weight: 400; }
.est-titlelink { font-weight: 600; }
.est-rowact { white-space: nowrap; }
.est-rowact .linkbtn-sm + .linkbtn-sm { margin-left: .6rem; }
/* Status pills: square mono tags in the sheet's language, one tint per state. */
.est-badge--draft    { color: var(--ink-soft); border-color: var(--line-strong); }
.est-badge--sent     { color: var(--accent); border-color: color-mix(in srgb, var(--accent) 45%, var(--line)); background: color-mix(in srgb, var(--accent) 7%, transparent); }
.est-badge--approved { color: var(--ok); border-color: color-mix(in srgb, var(--ok) 45%, var(--line)); background: color-mix(in srgb, var(--ok) 8%, transparent); }
.est-badge--expired  { color: var(--danger); border-color: #eecaca; background: var(--danger-bg); }

/* ---- Links hub: every shared client-facing link, one outbox. Reuses the
   estimate sheet + invoice mobile-list shells; adds a type marker, a copy
   button confirmation, and the mobile row shell. ---- */
.links-typemark { display: inline-block; font-family: var(--mono); font-size: .6rem; font-weight: 600; text-transform: uppercase; letter-spacing: .06em; color: var(--muted); padding: .18rem .42rem; border: 1px solid var(--line); border-left-width: 2px; white-space: nowrap; }
.links-typemark--estimate { border-left-color: var(--accent); }
.links-typemark--contract { border-left-color: var(--ink); }
.links-typemark--delivery { border-left-color: var(--ok); }
.links-typemark--invoice { border-left-color: var(--warn, #b8862f); }
.links-typemark--sheet { border-left-color: #2F6FB0; }   /* the same blue as .dk-sheet */
.links-detail, .links-proj { font-size: .76rem; margin-top: .15rem; }
.links-copy.is-copied { color: var(--ok); }
/* Mobile card rows borrow the invoice list shell (.ilr-* body classes). */
.links-lrow { display: flex; flex-direction: row; align-items: flex-start; gap: .6rem; padding: .6rem .8rem; border: 1px solid var(--line); border-radius: var(--radius); background: var(--panel); box-shadow: var(--shadow); color: var(--ink); }
.links-lrow:hover { border-color: var(--gold); }
.links-lrow .links-typemark { flex: 0 0 auto; }
.links-actions { display: flex; gap: 1.1rem; margin-top: .25rem; }

/* Below this the rail can't leave the sheet enough room, so stack: cards on top
   (full-width row again), sheet below at the page's full width — no side-scroll. */
@media (max-width: 1100px) {
    .inv-layout { grid-template-columns: 1fr; }
    .inv-side { order: -1; }
    .inv-cards { grid-template-columns: repeat(auto-fit, minmax(var(--card-min), 1fr)); }
}

/* Fit the invoice sheet to its column — no horizontal scroll. The two free-text
   columns (Client, Job) wrap; the compact data columns stay on one line. */
.inv-sheet { font-size: .78rem; }
.inv-sheet th, .inv-sheet td { padding: .4rem .5rem; white-space: normal; }
.inv-sheet .sheet-date, .inv-sheet .num, .inv-sheet .inv-status-cell,
.inv-sheet .inv-tax-cell, .inv-sheet .inv-tax-col, .inv-sheet .inv-timing { white-space: nowrap; }
.inv-sheet .sheet-client { flex-wrap: wrap; }   /* let a long client name wrap under its logo */

.card-value.danger-val { color: var(--danger); }
.inv-numlink { font-weight: 600; }
/* The assistant revised this invoice and nobody has opened it yet. Sits beside
   the number in the list and beside the status badge on a phone. */
.inv-unread { display: inline-block; margin-left: .35rem; font-family: var(--mono);
    font-size: .58rem; font-weight: 600; text-transform: uppercase; letter-spacing: .06em;
    padding: .16rem .38rem; background: #fdf3e6; color: #8a5a25; border: 1px solid #e6c99a; }
.project-sheet tbody tr.inv-row-overdue { box-shadow: inset 3px 0 0 var(--danger); }
.inv-row-overdue .inv-numlink { color: var(--danger); }
.inv-timing { white-space: nowrap; }
.inv-email-link { margin-left: .4rem; font-size: .75rem; }
.inv-del-form { margin-left: .4rem; }
.inv-del-form .linkbtn-sm { font-size: .75rem; }

/* ---- Invoices: phone list view (the wide sheet is desktop-only). Reads like
   the Projects list; each row links to the invoice preview to act on it. ---- */
.inv-main { display: flex; flex-direction: column; }
.inv-list-mobile { display: none; }
.inv-lrow {
    display: flex; flex-direction: row; align-items: flex-start; gap: .65rem;
    padding: .6rem .8rem; border: 1px solid var(--line); border-radius: var(--radius);
    background: var(--panel); box-shadow: var(--shadow); color: var(--ink);
}
.inv-lrow:hover { border-color: var(--gold); }
.inv-lrow--overdue { box-shadow: inset 3px 0 0 var(--danger), var(--shadow); }
/* Settled on a phone: paid or void invoices, finished or expired documents. The
   card version of the grey row in the wide table — same rule, same meaning, so
   a finished thing looks finished on either screen. */
.inv-lrow--done { opacity: .55; filter: grayscale(1); }
.inv-lrow--done:hover { opacity: 1; filter: none; }
.ilr-avatar { flex: 0 0 auto; width: 30px; height: 30px; border-radius: 0; object-fit: cover; border: 1px solid var(--line); background: #fff; }
.ilr-avatar--mono { display: inline-flex; align-items: center; justify-content: center; background: var(--ink); color: #fff; font-weight: 600; font-size: .82rem; border: 0; }
.ilr-body { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: .25rem; }
.ilr-top { display: flex; justify-content: space-between; align-items: baseline; gap: .6rem; }
.ilr-client { font-weight: 600; font-size: .92rem; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ilr-amt { font-weight: 600; font-variant-numeric: tabular-nums; white-space: nowrap; text-align: right; }
.ilr-mid { font-size: .8rem; color: var(--ink-soft); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ilr-bot { display: flex; justify-content: space-between; align-items: center; gap: .6rem; }
.ilr-due { font-size: .78rem; color: var(--muted); white-space: nowrap; }
.inv-lrow--overdue .ilr-due .cinv-od-text { color: var(--danger); font-weight: 600; }

/* Desktop features the sheet: filters + full sheet lead, and the pay-day
   calendars sit below as a fold that's collapsed by default at every size
   (click the toggle to expand). */
@media (min-width: 769px) {
    .inv-filters  { order: 1; }
    .inv-sheet-wrap { order: 2; }
    .inv-cal-fold { order: 3; }
}

@media (max-width: 720px) {
    .client-row { grid-template-columns: 1fr auto; grid-template-areas: "name edit" "contact touch"; row-gap: .35rem; }
    .cl-name { grid-area: name; }
    .cl-contact-info { grid-area: contact; }
    .cl-touch { grid-area: touch; justify-content: flex-end; }
    .cl-actions { grid-area: edit; align-self: center; }
    .cl-rate, .cl-terms { display: none; }
    .cg-hint { display: none; }
}

/* ---- Header on narrow screens ------------------------------- */
/* The nav collapses behind the hamburger; the payout pill sits on its own
   full-width row so nothing clips. */
@media (max-width: 768px) {
    .topbar { gap: .5rem .85rem; padding: .6rem .85rem; }
    /* The floating bottom bar (.mnav) supersedes the hamburger here, so hide
       both the toggle and the collapsible topnav entirely. */
    .nav-toggle { display: none; }
    .topnav { display: none; }
    .topbar-right { flex-basis: 100%; order: 3; flex-wrap: nowrap; justify-content: flex-end; }
}
@media (max-width: 560px) {
    .next-payout .np-label { display: none; }
    .next-payout { min-width: 0; overflow: hidden; }
}

@media (max-width: 720px) {
    .form-grid, .detail-cols, .cards { grid-template-columns: 1fr; }
    /* Let the (now single-column) grid items shrink to their track instead of
       being held open at content min-width — otherwise a panel runs a few px
       past the track and the page clips on the right (worse in Firefox). */
    .form-grid > *, .detail-cols > *, .cards > * { min-width: 0; }
    /* The embedded invoice email is inline-styled; keep any stray width inside
       its own box rather than pushing the page sideways. */
    .email-preview { overflow-x: auto; }
    .email-preview > * { max-width: 100%; }
    .project-row { grid-template-columns: auto 1fr auto; grid-template-areas: "logo main status" "logo meta value" "inv inv inv"; }
    .project-row .pr-meta { text-align: left; }
    .week-row { grid-template-columns: 1fr; }
}

/* ============================================================
   Floating mobile nav (.mnav) — the hovering bottom pill
   Hidden on desktop; shown ≤768px where it replaces the hamburger.
   ============================================================ */
.mnav { display: none; }

@media (max-width: 768px) {
    .mnav {
        display: block;
        position: fixed;
        left: 0; right: 0;
        /* Sit above the iOS/Android home indicator when present. */
        bottom: calc(env(safe-area-inset-bottom, 0px) + .7rem);
        z-index: 60;
        pointer-events: none; /* let taps fall through the gutters beside the pill */
        transition: transform .25s ease;
    }
    /* Slid away on scroll-down (mobile_nav.php toggles this); a scroll-up
       brings it back. */
    .mnav.mnav--hidden { transform: translateY(200%); }
    /* Keep page content clear of the floating bar. */
    .wrap { padding-bottom: 5.5rem; }

    .mnav-bar {
        pointer-events: auto;
        display: flex;
        align-items: stretch;
        gap: .1rem;
        width: max-content;
        max-width: calc(100% - 1.4rem);
        margin: 0 auto;
        padding: .35rem;
        background: rgba(26,26,26,.92);
        -webkit-backdrop-filter: blur(12px);
        backdrop-filter: blur(12px);
        border: 1px solid rgba(255,255,255,.08);
        border-radius: 2px;
        box-shadow: 0 6px 24px rgba(0,0,0,.28), 0 2px 6px rgba(0,0,0,.18);
    }

    .mnav-item {
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        gap: .12rem;
        min-width: 54px;
        padding: .4rem .55rem;
        background: none;
        border: 0;
        border-radius: 2px;
        color: #cfccc4;
        font: inherit;
        font-size: .62rem;
        line-height: 1;
        cursor: pointer;
        text-align: center;
        transition: color .15s, background .15s;
    }
    .mnav-item:hover { color: #fff; }
    /* Inline SVG icons (Lucide): sized in px, drawn in the item's text colour. */
    .mnav-ico { width: 22px; height: 22px; display: block; }
    .mnav-label { font-weight: 500; letter-spacing: .01em; white-space: nowrap; }
    /* Active section: gold text + a soft tinted well, echoing the topnav. */
    .mnav-item.active {
        color: var(--gold);
        background: rgba(238,123,60,.16);
    }

    /* ---- More sheet ---- */
    .mnav-scrim {
        pointer-events: auto;
        position: fixed;
        inset: 0;
        background: rgba(0,0,0,.18);
        z-index: -1; /* behind the sheet + bar, in front of the page */
    }
    .mnav-sheet {
        pointer-events: auto;
        position: absolute;
        left: 50%;
        bottom: calc(100% + .5rem);
        transform: translateX(-50%);
        width: min(20rem, calc(100vw - 1.4rem));
        display: grid;
        grid-template-columns: 1fr 1fr;
        gap: .25rem;
        padding: .5rem;
        background: rgba(26,26,26,.96);
        -webkit-backdrop-filter: blur(12px);
        backdrop-filter: blur(12px);
        border: 1px solid rgba(255,255,255,.08);
        border-radius: 16px;
        box-shadow: 0 10px 30px rgba(0,0,0,.32);
    }
    .mnav-sheet-item {
        display: flex;
        align-items: center;
        gap: .55rem;
        padding: .6rem .7rem;
        border-radius: 10px;
        color: #e6e3dc;
        font-size: .9rem;
    }
    .mnav-sheet-item:hover { background: rgba(255,255,255,.06); color: #fff; }
    .mnav-sheet-item.active { color: var(--gold); background: rgba(238,123,60,.16); }
    .mnav-sheet-item .mnav-ico { width: 19px; height: 19px; flex: 0 0 auto; }
    /* The Search entry is a <button>; strip native button chrome to match the links. */
    button.mnav-sheet-item { font: inherit; text-align: left; width: 100%; border: 0; background: none; cursor: pointer; }

    /* Enter transition for the sheet (Alpine toggles the -start class off). */
    .mnav-sheet-in { transition: opacity .14s ease, transform .14s ease; }
    .mnav-sheet-in-start { opacity: 0; transform: translateX(-50%) translateY(6px); }
}

/* Tighten the pill on very narrow phones so all six items still fit. */
@media (max-width: 360px) {
    .mnav-item { min-width: 46px; padding: .4rem .35rem; font-size: .58rem; }
    .mnav-ico { width: 20px; height: 20px; }
    .mnav-bar { gap: 0; }
}

/* ============================================================
   Pull-to-search affordance (pull-search.js) — a pill that rides
   down from the top edge as you pull, then opens the command
   palette on release. Touch/narrow screens only.
   ============================================================ */
.pull-search {
    display: none;
    position: fixed; top: 0; left: 0; right: 0; z-index: 70;
    justify-content: center;
    padding: .8rem 1rem;
    padding-top: calc(.8rem + env(safe-area-inset-top, 0px));
    transform: translateY(-100%);   /* parked just above the viewport */
    pointer-events: none;           /* purely a visual affordance */
    will-change: transform;
}
.pull-search:not(.no-anim) { transition: transform .22s ease; }
.pull-search-inner {
    display: inline-flex; align-items: center; gap: .5rem;
    padding: .5rem .95rem; border-radius: 2px;
    background: var(--panel); border: 1px solid var(--line); box-shadow: var(--shadow);
    color: var(--muted); font-size: .9rem; font-weight: 500;
}
.pull-search svg { width: 18px; height: 18px; }
.pull-search.armed .pull-search-inner { color: var(--gold); border-color: var(--gold); }
@media (max-width: 768px) { .pull-search { display: flex; } }

/* ============================================================
   Knowledge Base (Notes hub) — /notes
   ============================================================ */
.kb-head { display: flex; align-items: flex-end; gap: 1rem; flex-wrap: wrap; margin-bottom: .5rem; }
.kb-head > div { flex: 1; }
.kb-head h1 { margin: .05rem 0 .2rem; font-size: 1.55rem; }
.kb-head p.muted { margin: 0; font-size: .88rem; }
.kb-new { white-space: nowrap; }
.kbd {
    display: inline-block; margin-left: .35rem; padding: .05rem .4rem; border-radius: 5px;
    border: 1px solid rgba(0,0,0,.15); background: rgba(255,255,255,.35); font-size: .72em; opacity: .8;
}

.kb-search { position: relative; margin: .7rem 0 1rem; }
.kb-search input {
    width: 100%; font: inherit; font-size: .98rem; padding: .6rem 1rem .6rem 2.9rem;
    border: 1px solid var(--line); border-radius: var(--radius); background: var(--panel); box-shadow: none;
}
.kb-search input:focus { outline: 2px solid var(--gold); outline-offset: 1px; border-color: transparent; }
.kb-search .mag { position: absolute; left: 1rem; top: 50%; transform: translateY(-50%); color: var(--muted); font-size: 1.1rem; }
.kb-search .slash { position: absolute; right: 1rem; top: 50%; transform: translateY(-50%); border: 1px solid var(--line); border-radius: 0; padding: .05rem .45rem; font-family: var(--mono); font-size: .72rem; color: var(--muted); background: var(--bg); }

/* Active-projects strip — quick jump to a live (hold/booked) project's notes.
   Chips reuse .kb-tag's filter behaviour; completed projects never appear. */
.kb-active { display: flex; flex-wrap: wrap; align-items: center; gap: .6rem; margin: .5rem 0 -.4rem; }
.kb-active-lbl {
    flex: none; font-family: var(--mono); font-size: .6rem; text-transform: uppercase; letter-spacing: .12em;
    color: var(--muted);
}
.kb-active-strip { display: flex; gap: .4rem; overflow-x: auto; padding: .2rem 0 .35rem; scrollbar-width: thin; }
.kb-proj {
    flex: none; width: auto; justify-content: flex-start; gap: .4rem; white-space: nowrap;
    border: 1px solid var(--line); background: var(--panel); box-shadow: var(--shadow);
    border-radius: 2px; padding: .32rem .7rem;
}
.kb-proj:hover { border-color: var(--gold); background: var(--panel); }
.kb-proj.active { background: var(--gold-soft, rgba(238,123,60,.16)); border-color: var(--gold); }
.kb-proj-dot { width: .55rem; height: .55rem; border-radius: 50%; flex: none; background: var(--muted); }
.kb-proj-dot.st-booked { background: var(--ok); }
.kb-proj-dot.st-hold   { background: #c99a2e; }
.kb-proj-dot.st-hold2  { background: #d97b2e; }
.kb-proj-name { max-width: 12rem; overflow: hidden; text-overflow: ellipsis; }

/* The dismiss / restore control tucks inside the chip's right edge — no border, no
   box, just a faint glyph that surfaces on hover (like the note-card pin). The chip
   keeps a little right padding so the glyph never crowds the count. */
.kb-proj-wrap { position: relative; display: inline-flex; flex: none; }
.kb-proj-wrap .kb-proj { padding-right: 1.45rem; }
.kb-proj-x, .kb-proj-restore {
    position: absolute; right: .3rem; top: 50%; transform: translateY(-50%);
    display: none; align-items: center; justify-content: center;
    width: 1.05rem; height: 1.05rem; padding: 0; border: 0; background: none;
    color: var(--muted); font-size: .9rem; line-height: 1; cursor: pointer; border-radius: 50%;
    opacity: 0; transition: opacity .12s, color .12s;
}
.kb-active-strip .kb-proj-x { display: inline-flex; }
.kb-active-hidden .kb-proj-restore { display: inline-flex; opacity: .5; }
.kb-proj-wrap:hover .kb-proj-x,
.kb-proj-wrap:focus-within .kb-proj-x { opacity: .45; }
.kb-proj-x:hover, .kb-proj-x:focus-visible { opacity: 1; color: var(--danger); }
.kb-proj-restore:hover, .kb-proj-restore:focus-visible { opacity: 1; color: var(--gold); }

/* "Hidden (N)" reveal — a quiet dashed pill; the tray drops onto its own line. */
.kb-active-toggle {
    flex: none; border: 1px dashed var(--line); background: none; color: var(--muted);
    font-family: var(--mono); font-size: .6rem; text-transform: uppercase; letter-spacing: .1em;
    border-radius: 2px; padding: .32rem .6rem; cursor: pointer;
}
.kb-active-toggle:hover { border-color: var(--gold); color: var(--gold); }
.kb-active-toggle .n { font-variant-numeric: tabular-nums; margin-left: .2rem; }
.kb-active-toggle[hidden] { display: none; }
.kb-active-hidden { display: none; }
.kb-active-hidden.show { display: flex; flex-basis: 100%; flex-wrap: wrap; gap: .4rem; padding: .1rem 0 .35rem; }

/* Empty-state lines live inside the .kb-notes multi-column flow, so they must span
   all columns — otherwise the browser splits the sentence across two columns. */
.kb-proj-empty { column-span: all; break-inside: avoid; text-align: center; padding: 2.5rem 1rem; color: var(--muted); }
.kb-proj-empty p { margin: 0 0 .9rem; }
.kb-proj-empty strong { color: var(--ink-soft); }
.kb-noresults { column-span: all; break-inside: avoid; text-align: center; padding: 2.5rem 1rem; }

.kb-grid { display: grid; grid-template-columns: 200px minmax(0, 1fr); gap: 1.5rem; align-items: start; }

/* Both grid tracks must be allowed to shrink below their content's intrinsic
   width (default is min-width:auto), or a long code line blows the column out
   past the viewport and clips the right-hand cards on resize. */
.kb-rail, .kb-notes { min-width: 0; }
.kb-rail h3 { font-family: var(--mono); font-size: .6rem; text-transform: uppercase; letter-spacing: .12em; color: var(--muted); margin: 1.1rem 0 .4rem; }
.kb-rail.searching { opacity: .5; }
.kb-facet { display: flex; flex-direction: column; gap: .15rem; }
.kb-tag {
    display: flex; justify-content: space-between; align-items: center; gap: .5rem; text-align: left;
    padding: .4rem .6rem; border-radius: 0; cursor: pointer; font: inherit; font-size: .9rem;
    color: var(--ink-soft); border: 1px solid transparent; background: none;
}
.kb-tag .kb-facet-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.kb-tag:hover { background: var(--panel); border-color: var(--line); }
.kb-tag.active { background: var(--gold-soft, rgba(238,123,60,.16)); color: var(--gold); font-weight: 600; }
.kb-tag .n { font-family: var(--mono); font-size: .7rem; color: var(--muted); font-variant-numeric: tabular-nums; }
.kb-tag.active .n { color: var(--gold); }

/* Fluid columns: the browser fits as many ~320px columns as the width allows and
   reflows on resize (3 → 2 → 1), instead of a fixed 2 that overflows when narrow. */
.kb-notes { columns: 300px; column-gap: .85rem; }
.kb-note {
    break-inside: avoid; background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius);
    box-shadow: none; padding: .8rem .9rem; margin: 0 0 .7rem; cursor: pointer;
    max-width: 100%;
    transition: border-color .12s, box-shadow .12s;
}
/* Type accent: a colored left edge on the whole card so kinds are scannable
   across the grid at a glance (the badge alone is too small to catch the eye). */
.kb-note { border-left: 3px solid var(--line); }
.kb-note[data-kind="note"]     { border-left-color: #b3a993; }
.kb-note[data-kind="tip"]      { border-left-color: var(--ok); }
.kb-note[data-kind="snippet"]  { border-left-color: #3f5487; }
.kb-note[data-kind="client"]   { border-left-color: #b98f34; }
.kb-note[data-kind="playbook"] { border-left-color: #6a4fa0; }
/* Hover highlights the other three sides only, so the type accent stays visible. */
.kb-note:hover, .kb-note:focus-visible { border-top-color: var(--gold); border-right-color: var(--gold); border-bottom-color: var(--gold); box-shadow: 0 2px 6px rgba(0,0,0,.06); outline: none; }
.kb-note-top { display: flex; align-items: center; gap: .5rem; margin-bottom: .4rem; }
.kb-kind { font-family: var(--mono); font-size: .56rem; text-transform: uppercase; letter-spacing: .08em; font-weight: 700; padding: .16rem .5rem; border-radius: 0; white-space: nowrap; }
.k-note { background: #eeece6; color: #6b6355; }
.k-tip { background: #eef4ef; color: var(--ok); }
.k-snippet { background: #eceef4; color: #3f5487; }
.k-client { background: var(--gold-soft, rgba(238,123,60,.16)); color: #9a5a2e; }
.k-playbook { background: #f2eefb; color: #6a4fa0; }
.kb-note-top h4 { margin: 0; font-size: .9rem; line-height: 1.25; min-width: 0; overflow-wrap: anywhere; }
.kb-pin { display: inline-flex; align-items: center; color: var(--gold); }
.kb-pin-ico { width: 14px; height: 14px; vertical-align: -2px; }
/* Click-to-pin toggle in the card's top-right. Subtle at rest, gold on hover, and
   fully lit (gold) once pinned — the same visual language as the dashboard pin. */
.kb-pinbtn { margin-left: auto; flex: none; border: 0; background: none; cursor: pointer;
    padding: .2rem; border-radius: 4px; line-height: 0; color: var(--muted); opacity: .4;
    transition: opacity .12s, color .12s, transform .12s; }
.kb-pinbtn:hover { opacity: 1; color: var(--gold); transform: rotate(8deg); }
.kb-pinbtn.is-pinned { opacity: 1; color: var(--gold); }
.kb-pinbtn:focus-visible { opacity: 1; color: var(--gold); outline: 2px solid var(--gold); outline-offset: 1px; }
.kb-pinbtn .kb-pin-ico { width: 14px; height: 14px; }
.kb-note-foot { display: flex; align-items: center; gap: .5rem; margin-top: .5rem; }
.kb-actions { margin-left: auto; display: flex; gap: .35rem; }
.iconbtn { border: 1px solid var(--line); background: var(--bg); border-radius: 0; font: inherit; font-size: .72rem; color: var(--ink-soft); cursor: pointer; padding: .12rem .5rem; }
.iconbtn:hover { border-color: var(--gold); color: var(--gold); }
.iconbtn.kb-icon-del:hover { border-color: var(--danger); color: var(--danger); }
.kb-note-body { position: relative; max-height: 260px; overflow: hidden; overflow-wrap: anywhere; font-size: .76rem; }
.kb-note-body::after { content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 2.2rem; background: linear-gradient(transparent, var(--panel)); }
.kb-meta { margin-top: .5rem; display: flex; gap: .4rem; flex-wrap: wrap; }
/* Own class (NOT .chip — that's the app's 2rem square icon badge) so tags render
   as inline pills at their natural width and never stack. */
.kb-chip { display: inline-block; font-family: var(--mono); font-size: .64rem; line-height: 1.6; color: var(--muted); background: var(--bg); border: 1px solid var(--line); border-radius: 0; padding: .1rem .45rem; white-space: nowrap; }
.kb-dates { font-family: var(--mono); font-size: .66rem; color: var(--muted); font-variant-numeric: tabular-nums; }
.kb-empty { text-align: center; color: var(--muted); padding: 2.5rem 1rem; border: 1px dashed var(--line); border-radius: var(--radius); }
.kb-empty p { margin: 0 0 1rem; }

/* -------- List view: the same note cards, re-laid-out as a scannable table --------
   No second markup — .is-list flattens each card into one grid row and lines the
   cells up with a shared column template, so every card behaviour (search, filter,
   pin, share, delete, open) carries over unchanged. */
/* Beats `.kb-head > div { flex: 1 }`, which would otherwise stretch the switch. */
.kb-head > .kb-viewswitch { flex: none; align-self: flex-end; }
.kb-viewswitch { display: inline-flex; border: 1px solid var(--line); }
.kb-viewbtn {
    font: inherit; font-size: .74rem; font-family: var(--mono); text-transform: uppercase; letter-spacing: .08em;
    color: var(--muted); background: var(--bg); border: 0; padding: .34rem .7rem; cursor: pointer;
}
.kb-viewbtn + .kb-viewbtn { border-left: 1px solid var(--line); }
.kb-viewbtn:hover { color: var(--gold); }
.kb-viewbtn.on { background: var(--gold-soft, rgba(238,123,60,.16)); color: var(--gold); font-weight: 700; }

.kb-list-head { display: none; }
.kb-where { display: none; }

/* Column widths. Two rules keep the heading and the rows in lockstep: no track may
   be content-sized (`auto` would resolve differently in each, since they're separate
   grids), and the flexible ones are `fr` rather than fixed lengths — an over-wide
   fixed set would crush the title to nothing and push the rest out of the panel.
   How many columns fit is a question about THIS box, not the window: the shelf and
   the filter rail leave it anywhere from ~265px (a 770px window) to ~810px, so the
   tiers are container queries. The base is the narrowest set, which fits anywhere. */
.kb-notes.is-list {
    --kb-cols: 1.6rem minmax(0, 1fr) 5.4rem;
    columns: auto; display: flex; flex-direction: column;
    border: 1px solid var(--line); background: var(--panel);
    container-type: inline-size;
}
.kb-notes.is-list .kb-list-head,
.kb-notes.is-list .kb-note { display: grid; grid-template-columns: var(--kb-cols); align-items: center; column-gap: .7rem; }
.kb-notes.is-list .kb-note[hidden] { display: none; }   /* filters use [hidden] */
.kb-notes.is-list .kb-list-head {
    /* +2px on the left matches the rows' 3px type accent, so the columns line up. */
    padding: .45rem .7rem .45rem calc(.7rem + 2px); border-bottom: 1px solid var(--line); background: var(--bg);
    font-family: var(--mono); font-size: .6rem; text-transform: uppercase; letter-spacing: .1em; color: var(--muted);
}
.kb-listsort {
    font: inherit; text-transform: inherit; letter-spacing: inherit; color: inherit;
    background: none; border: 0; padding: 0; text-align: left; cursor: pointer;
}
.kb-listsort:hover { color: var(--gold); }
.kb-listsort .dir { opacity: 0; }
.kb-listsort.sorted { color: var(--gold); }
.kb-listsort.sorted .dir { opacity: 1; }

/* Left edge untouched: it keeps the per-kind accent color set above. */
.kb-notes.is-list .kb-note {
    margin: 0; padding: .4rem .7rem; border-radius: 0; box-shadow: none;
    border-top: 1px solid var(--line); border-right: 0; border-bottom: 0;
}
.kb-notes.is-list .kb-note:first-of-type { border-top: 0; }
.kb-notes.is-list .kb-note:hover,
.kb-notes.is-list .kb-note:focus-visible { background: var(--bg); box-shadow: none; border-top-color: var(--line); }
/* The card's own sub-wrappers dissolve so their children become row cells. */
.kb-notes.is-list .kb-note-top,
.kb-notes.is-list .kb-note-foot { display: contents; }
.kb-notes.is-list .kb-note-body { display: none; }
.kb-notes.is-list .kb-note > *,
.kb-notes.is-list .kb-note-top > *,
.kb-notes.is-list .kb-note-foot > * { grid-row: 1; }
/* Title leads; everything that classifies the note (filing, tags, type, date) sits
   to its right, so a scan down the list reads as a plain column of names. */
.kb-notes.is-list .kb-pinbtn { grid-column: 1; margin: 0; justify-self: center; }
.kb-notes.is-list .kb-note-top h4 { grid-column: 2; font-size: .82rem; }
.kb-notes.is-list .kb-where { grid-column: 3; font-size: .72rem; color: var(--muted); }
.kb-notes.is-list .kb-kind { grid-column: 5; justify-self: start; }
/* Tags fade out at the column edge rather than being sliced through a chip. */
.kb-notes.is-list .kb-meta {
    grid-column: 4; margin: 0; flex-wrap: nowrap;
    -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 1.3rem), transparent);
    mask-image: linear-gradient(to right, #000 calc(100% - 1.3rem), transparent);
}
.kb-notes.is-list .kb-dates { grid-column: 3; white-space: nowrap; }
/* One date column, headed "Edited" — so the row shows the date alone. */
.kb-notes.is-list .kb-created,
.kb-notes.is-list .kb-edited .lbl { display: none; }
/* Anything that can run long gets one line and an ellipsis, so a wordy title or a
   long client name can never widen its column. */
.kb-notes.is-list .kb-note-top h4,
.kb-notes.is-list .kb-where,
.kb-notes.is-list .kb-meta { min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* Narrow boxes drop the optional columns rather than squeezing every column. Each
   one goes with its heading cell, so the two grids keep the same shape. At the
   narrowest the type badge goes too — the colored left edge still carries the type. */
.kb-notes.is-list .kb-kind,
.kb-notes.is-list .kb-where,
.kb-notes.is-list .kb-meta,
.kb-notes.is-list .kb-col-kind,
.kb-notes.is-list .kb-col-where,
.kb-notes.is-list .kb-col-tags { display: none; }

/* Actions float over the right edge on hover instead of holding a column open —
   at these widths a permanent ~9rem gutter for three buttons isn't affordable. */
.kb-notes.is-list .kb-note { position: relative; }
.kb-notes.is-list .kb-actions {
    /* grid-area:auto matters — an absolutely-positioned grid child WITH a placement
       is laid out against that grid area, not the row box. */
    position: absolute; grid-area: auto; right: .7rem; top: 0; bottom: 0; align-items: center;
    margin: 0; padding-left: 1.6rem; opacity: 0; pointer-events: none;
    background: linear-gradient(to right, transparent, var(--bg) 1.1rem);
}
.kb-notes.is-list .kb-note:hover .kb-actions,
.kb-notes.is-list .kb-note:focus-within .kb-actions { opacity: 1; pointer-events: auto; }

/* Wider box → bring back the type badge, then the filing column, then the tags
   column. The heading cells are auto-placed, so hiding one shifts the rest to match.
   Every tier keeps the title in column 2, right after the pin; the extra columns
   all open up between it and the date. */
@container (min-width: 360px) {
    .kb-notes.is-list .kb-list-head,
    .kb-notes.is-list .kb-note { --kb-cols: 1.6rem minmax(0, 1fr) 4.6rem 5.4rem; }
    .kb-notes.is-list .kb-kind { display: inline-block; grid-column: 3; }
    .kb-notes.is-list .kb-col-kind { display: block; }
    .kb-notes.is-list .kb-dates { grid-column: 4; }
}
@container (min-width: 430px) {
    .kb-notes.is-list .kb-list-head,
    .kb-notes.is-list .kb-note { --kb-cols: 1.6rem minmax(0, 1.5fr) minmax(0, 1fr) 4.6rem 5.4rem; }
    .kb-notes.is-list .kb-where,
    .kb-notes.is-list .kb-col-where { display: block; }
    .kb-notes.is-list .kb-kind { grid-column: 4; }
    .kb-notes.is-list .kb-dates { grid-column: 5; }
}
@container (min-width: 620px) {
    .kb-notes.is-list .kb-list-head,
    .kb-notes.is-list .kb-note { --kb-cols: 1.6rem minmax(0, 2.2fr) minmax(0, 1.3fr) minmax(0, 1.1fr) 4.6rem 5.4rem; }
    .kb-notes.is-list .kb-meta { display: flex; }
    .kb-notes.is-list .kb-col-tags { display: block; }
    .kb-notes.is-list .kb-kind { grid-column: 5; }
    .kb-notes.is-list .kb-dates { grid-column: 6; }
}

/* ---- Business Facts card (first item in the masonry; note cards flow beside it) ---- */
.kb-facts { break-inside: avoid; background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius); box-shadow: var(--shadow); padding: 1.05rem 1.2rem; margin: 0 0 1rem; }
.kb-facts-head { display: flex; align-items: center; gap: .5rem; margin-bottom: .2rem; }
.kb-facts-head h4 { margin: 0; font-size: .92rem; }
.kb-facts-head .kb-pin { font-size: .85rem; }
.kb-lock { margin-left: auto; font-family: var(--mono); font-size: .58rem; text-transform: uppercase; letter-spacing: .06em; color: var(--muted); border: 1px solid var(--line); border-radius: 0; padding: .1rem .4rem; white-space: nowrap; }
.kb-facts-head .iconbtn { margin-left: .1rem; }
.kb-facts-sub { margin: 0 0 .4rem; font-size: .78rem; color: var(--muted); }
/* Label sits above the value so the value always gets the full card width and
   never wraps into a cramped column (the old side-by-side layout squeezed long
   values like "Chris Lee Creative LLC" into 3–4 lines on narrow cards). */
.kb-fact { display: grid; grid-template-columns: 1fr auto; column-gap: .5rem; align-items: baseline; padding: .4rem .45rem; border-top: 1px solid var(--line); cursor: pointer; border-radius: 0; }
.kb-fact:first-of-type { border-top: 0; }
.kb-fact .lbl { grid-column: 1; grid-row: 1; font-family: var(--mono); font-size: .58rem; text-transform: uppercase; letter-spacing: .1em; color: var(--muted); }
.kb-fact .copyicon { grid-column: 2; grid-row: 1; justify-self: end; font-size: .68rem; color: var(--muted); opacity: 0; }
.kb-fact .val { grid-column: 1 / -1; grid-row: 2; font-family: var(--mono, ui-monospace, Menlo, Consolas, monospace); font-size: .85rem; color: var(--ink); overflow-wrap: anywhere; }
.kb-fact a.val-link { color: var(--gold); }
.kb-fact a.val-link:hover { text-decoration: underline; }
.kb-fact:hover { background: var(--bg); }
.kb-fact:hover .copyicon { opacity: 1; }
.kb-fact.copied .copyicon { opacity: 1; color: var(--ok); }
/* edit mode */
/* Edit rows stack (label + delete on top, then value, then link) so they fit the
   compact card width without a media query. */
.kb-facts-rows { display: flex; flex-direction: column; gap: .5rem; margin: .5rem 0 .6rem; }
.kb-facts-row { display: grid; grid-template-columns: 1fr auto; gap: .35rem .4rem; padding: .55rem; border: 1px solid var(--line); border-radius: 0; background: var(--bg); }
.kb-facts-row input { font: inherit; font-size: .84rem; border: 1px solid var(--line); border-radius: 6px; padding: .4rem .55rem; background: var(--panel); min-width: 0; }
.kb-facts-row input[name="fact_label[]"] { grid-column: 1; font-weight: 600; }
.kb-facts-row input[name="fact_value[]"], .kb-facts-row input[name="fact_url[]"] { grid-column: 1 / -1; }
.kb-facts-row input:focus { outline: 2px solid var(--gold); outline-offset: 1px; border-color: transparent; }
.kb-facts-del { grid-column: 2; grid-row: 1; align-self: start; border: 1px solid var(--line); background: var(--panel); color: var(--muted); border-radius: 6px; cursor: pointer; font-size: 1.1rem; line-height: 1; padding: .2rem .5rem; }
.kb-facts-del:hover { border-color: var(--danger); color: var(--danger); }
.kb-addrow { font: inherit; font-size: .82rem; color: var(--gold); background: none; border: 1px dashed var(--gold); border-radius: 6px; padding: .4rem .7rem; cursor: pointer; }
.kb-facts-foot { display: flex; gap: .5rem; justify-content: flex-end; margin-top: .8rem; }

/* ---- rendered markdown (shared by cards, preview, and future share pages) ---- */
.md-h1 { font-size: 1.3rem; margin: .3rem 0 .5rem; }
.md-h2 { font-size: 1.1rem; margin: .8rem 0 .35rem; }
.md-h3 { font-size: .98rem; margin: .7rem 0 .3rem; }
.kb-note-body p, .kb-preview-pane p { margin: .5rem 0; color: var(--ink-soft); }
/* denser type inside note cards (the reader/preview keeps the larger sizes) */
.kb-note-body .md-h1 { font-size: .95rem; margin: .2rem 0 .3rem; }
.kb-note-body .md-h2 { font-size: .85rem; margin: .5rem 0 .25rem; }
.kb-note-body .md-h3 { font-size: .81rem; margin: .45rem 0 .2rem; }
.kb-note-body p { margin: .35rem 0; }
.kb-note-body .md-ul, .kb-note-body .md-ol { margin: .35rem 0; }
.md-ul, .md-ol { margin: .5rem 0; padding-left: 1.25rem; color: var(--ink-soft); }
.md-ul li, .md-ol li { margin: .2rem 0; }
.md-img { max-width: 100%; max-height: 320px; border-radius: 0; border: 1px solid var(--line); margin: .5rem 0; display: block; }
/* pipe tables — hairline grid; the wrapper scrolls so a wide table never widens the page */
.md-table-wrap { max-width: 100%; overflow-x: auto; margin: .6rem 0; }
.md-table { border-collapse: collapse; width: 100%; font-size: .88rem; color: var(--ink-soft); }
.md-table th, .md-table td { border: 1px solid var(--line); padding: .38rem .6rem; text-align: left; vertical-align: top; }
.md-table th { background: var(--panel); color: var(--ink); font-weight: 600; white-space: nowrap; }
.md-table .md-r { text-align: right; font-variant-numeric: tabular-nums; }
.md-table .md-c { text-align: center; }
.kb-note-body .md-table { font-size: .78rem; }
.kb-note-body .md-table th, .kb-note-body .md-table td { padding: .25rem .4rem; }
/* inline code inside note bodies/preview */
.kb-note-body code, .kb-preview-pane code { background: #eef1f5; color: var(--ink); padding: .1em .4em; border-radius: 4px; font-family: var(--mono, ui-monospace, Menlo, Consolas, monospace); font-size: .86em; }
/* fenced code block — dark, readable; the inline-code style must NOT leak in here */
pre.md-code {
    position: relative; background: #15161a; border-radius: 0; padding: .85rem 1rem; margin: .6rem 0;
    max-width: 100%; overflow-x: auto;
}
pre.md-code code { background: none; color: #e8e6df; padding: 0; border-radius: 0; font-size: .92em; white-space: pre; }
.kb-copy {
    position: absolute; top: .5rem; right: .5rem; font: inherit; font-size: .72rem; cursor: pointer;
    border: 1px solid #34363d; background: #22242a; color: #cfcdc6; border-radius: 6px; padding: .2rem .55rem;
}
.kb-copy:hover { border-color: var(--gold); color: var(--gold); }
.kb-copy.done { color: var(--ok); border-color: var(--ok); }

/* ---- editor drawer ---- */
/* Above the floating mobile nav (z 60) so its footer Save button is reachable. */
.kb-scrim { position: fixed; inset: 0; background: rgba(20,20,20,.42); opacity: 0; pointer-events: none; transition: opacity .18s; z-index: 80; }
.kb-scrim.show { opacity: 1; pointer-events: auto; }
.kb-drawer { position: fixed; top: 0; right: 0; bottom: 0; width: min(620px, 96vw); background: var(--bg); box-shadow: -8px 0 30px rgba(0,0,0,.18); transform: translateX(100%); transition: transform .2s; z-index: 85; }
.kb-drawer.show { transform: translateX(0); }
.kb-drawer form { display: flex; flex-direction: column; height: 100%; }
.kb-drawer-head { display: flex; align-items: center; gap: .6rem; padding: 1rem 1.2rem; border-bottom: 1px solid var(--line); background: var(--panel); }
.kb-drawer-head .eyebrow { margin-right: auto; }
.kb-x { border: 0; background: none; font-size: 1.4rem; line-height: 1; cursor: pointer; color: var(--muted); }
.kb-x:hover { color: var(--ink); }
.kb-drawer-body { padding: 1.1rem 1.2rem; overflow-y: auto; flex: 1; display: flex; flex-direction: column; }
/* Let the write/preview area grow to fill the drawer down to the foot, instead of
   sitting in a short box with dead space beneath it. */
.kb-drawer .kb-body,
.kb-drawer .kb-preview-pane { flex: 1 1 auto; }
.kb-kinds { display: flex; gap: .4rem; flex-wrap: wrap; margin-bottom: .3rem; }
.kb-kindpick { font-family: var(--mono); font-size: .62rem; text-transform: uppercase; letter-spacing: .06em; font-weight: 700; padding: .28rem .65rem; border-radius: 0; border: 1px solid var(--line); background: var(--panel); color: var(--muted); cursor: pointer; }
.kb-kindpick.on { background: var(--ink); color: #fff; border-color: var(--ink); }
.kb-kindrow { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; margin-bottom: .3rem; }
.kb-kindrow .kb-kinds { margin-bottom: 0; }
.kb-pin-toggle { margin-left: auto; display: inline-flex; align-items: center; gap: .35rem; font-size: .8rem; color: var(--ink-soft); cursor: pointer; border: 1px solid var(--line); border-radius: 2px; padding: .2rem .6rem; }
.kb-pin-toggle input { margin: 0; }
.kb-attach { width: 100%; font: inherit; font-size: .9rem; border: 1px solid var(--line); border-radius: 0; padding: .5rem .6rem; background: var(--panel); }
.kb-preview-pane { cursor: text; }
.fg-hint { font-size: .85rem; margin: .3rem 0 0; }
/* A field freshly pre-filled by "Fetch details" — flagged until the user Saves. */
.suggested-field { border-color: var(--ok) !important; box-shadow: 0 0 0 2px color-mix(in srgb, var(--ok) 22%, transparent); }
.fg-note { display: block; font-size: .8rem; margin: .35rem 0 0; color: var(--muted); }
.fg-note--suggest { color: var(--ok); }
.kb-fld { display: block; font-size: .72rem; text-transform: none; letter-spacing: .08em; color: var(--muted); margin: 1rem 0 .35rem; }
.kb-fld-opt { text-transform: none; letter-spacing: 0; font-weight: 400; opacity: .85; }
.kb-title { width: 100%; font: inherit; font-size: 1.15rem; font-weight: 600; border: 1px solid var(--line); border-radius: 0; padding: .6rem .7rem; background: var(--panel); }
.kb-toolbar { display: flex; gap: .2rem; flex-wrap: wrap; background: var(--panel); border: 1px solid var(--line); border-bottom: 0; border-radius: 8px 8px 0 0; padding: .35rem; }
.kb-toolbar .tb { border: 0; background: none; font: inherit; font-size: .85rem; color: var(--ink-soft); cursor: pointer; padding: .28rem .55rem; border-radius: 6px; min-width: 2rem; }
.kb-toolbar .tb:hover { background: var(--bg); color: var(--gold); }
.kb-toolbar .tb .h { font-weight: 700; }
.tb-sep { width: 1px; background: var(--line); margin: .2rem .15rem; }
.tb-right { margin-left: auto; display: flex; gap: .2rem; }
.pvbtn { font-size: .72rem; color: var(--muted); border: 1px solid var(--line); border-radius: 6px; padding: .2rem .5rem; background: var(--bg); cursor: pointer; }
.pvbtn.on { color: var(--gold); border-color: var(--gold); }
.kb-tools { display: contents; }
.kb-tools.kb-hidden { display: none; }
.kb-body { width: 100%; min-height: 300px; font-family: var(--mono, ui-monospace, Menlo, Consolas, monospace); font-size: .82rem; line-height: 1.6; border: 1px solid var(--line); border-radius: 0 0 8px 8px; padding: .8rem; background: var(--panel); resize: vertical; }
.kb-body.drag { border-color: var(--gold); background: #fbf7ee; }
.kb-preview-pane { min-height: 300px; border: 1px solid var(--line); border-radius: 0 0 8px 8px; padding: .8rem 1rem; background: var(--panel); font-size: .72rem; }
.kb-preview-pane .md-h1 { font-size: .89rem; } .kb-preview-pane .md-h2 { font-size: .81rem; } .kb-preview-pane .md-h3 { font-size: .75rem; }
.kb-tagsinput { width: 100%; font: inherit; border: 1px solid var(--line); border-radius: 0; padding: .55rem .7rem; background: var(--panel); }
.kb-check { display: flex; align-items: center; gap: .5rem; margin-top: 1rem; font-size: .9rem; color: var(--ink-soft); cursor: pointer; }
.kb-drawer-foot { display: flex; align-items: center; gap: .5rem; padding: .8rem 1.2rem; border-top: 1px solid var(--line); background: var(--panel); }
.kb-drawer-foot .btn { padding: .4rem .85rem; font-size: .85rem; }
.kb-foot-dates { font-size: .7rem; color: var(--muted); margin-right: auto; }
.kb-del { color: var(--danger); border-color: var(--danger-bg); }
.kb-del:hover { background: var(--danger-bg); }

/* ---- standalone single-note editor page (/notes/<id>) ---- */
/* The same editor as the drawer, laid out on its own page so a note can live in
   its own pinned browser tab. Reuses the .kb-* editor controls; this is just the
   surrounding panel + footer. */
.kb-single { max-width: 820px; margin: 0 auto; }
/* Note-first: no card chrome — the note sits directly on the page (the .wrap
   supplies the side margins), just like the phone reading view. */
.kb-single-form { padding: 0; }
.kb-single-form .kb-fld:first-of-type { margin-top: .4rem; }
.kb-single-foot { display: flex; align-items: center; gap: .6rem; margin-top: 1.2rem; padding-top: 1rem; border-top: 1px solid var(--line); }
.kb-single-foot .kb-foot-dates { margin-right: auto; }
.kb-single-foot .btn { padding: .45rem 1rem; }
/* A long, generous writing surface on the standalone page — grows with the
   viewport and never crops the note tight. The preview pane matches so toggling
   Write/Preview doesn't jump the page height. */
.kb-single .kb-body,
.kb-single .kb-preview-pane { min-height: calc(100vh - 230px); }   /* fill the window, not a short cropped box */
.kb-single .kb-body { resize: vertical; }

/* ============================================================
   NOTE-FIRST EDITING SKIN
   Shared by the standalone solo page (.kb-single) and the hub
   drawer (.kb-drawer), at every width. The kind / pin / filing
   controls fold behind one "Details" tap; the writing surface is
   borderless; the note opens rendered (Preview) at a comfortable
   reading size. One calm, note-first feel on every surface.
   NB: .kb-fold* is intentionally NOT .kb-meta* — .kb-meta is the
   note cards' tag row (see above).
   ============================================================ */
.kb-fold { margin-bottom: .8rem; }
.kb-fold-toggle {
    display: flex; align-items: center; justify-content: space-between; gap: .5rem;
    width: 100%; font: inherit; font-size: .82rem; color: var(--ink-soft);
    background: var(--panel); border: 1px solid var(--line); border-radius: 2px;
    padding: .5rem .95rem; cursor: pointer;
}
.kb-fold-toggle:hover { border-color: var(--gold); color: var(--gold); }
.kb-fold-summary { font-weight: 600; letter-spacing: .01em; }
.kb-fold-caret { color: var(--muted); transition: transform .18s ease; }
.kb-fold.open .kb-fold-caret { transform: rotate(180deg); }
.kb-fold-body { display: none; padding: .85rem .1rem .2rem; }
.kb-fold.open .kb-fold-body { display: block; }

/* The "Note — first line is the title" helper label is write-mode guidance; the
   note leads on its own, so it steps back on the note-first surfaces. */
.kb-single .kb-body-label,
.kb-drawer  .kb-body-label { display: none; }

/* Borderless writing/reading surface + comfortable reading type. In Preview the
   formatting tools hide (kb-hidden), leaving just the small Write/Preview toggle. */
.kb-single .kb-toolbar,
.kb-drawer  .kb-toolbar { border: 0; background: none; border-radius: 0; padding: .1rem 0 .4rem; gap: .15rem; }
.kb-single .kb-body,  .kb-single .kb-preview-pane,
.kb-drawer  .kb-body, .kb-drawer  .kb-preview-pane {
    border: 0; border-radius: 0; background: none; padding: 0;
}
.kb-single .kb-preview-pane,
.kb-drawer  .kb-preview-pane { font-size: .85rem; line-height: 1.65; }
.kb-single .kb-preview-pane p,
.kb-drawer  .kb-preview-pane p { color: var(--ink); margin: .6rem 0; }
.kb-single .kb-preview-pane .md-h1, .kb-drawer .kb-preview-pane .md-h1 { font-size: 1.28rem; }
.kb-single .kb-preview-pane .md-h2, .kb-drawer .kb-preview-pane .md-h2 { font-size: 1.02rem; }
.kb-single .kb-preview-pane .md-h3, .kb-drawer .kb-preview-pane .md-h3 { font-size: .87rem; }
.kb-single .kb-body,
.kb-drawer  .kb-body { font-size: .85rem; }   /* write mode matches the preview (reading) size */

/* ---- shared badge + Share modal ---- */
.kb-shared { font-family: var(--mono); font-size: .6rem; text-transform: uppercase; letter-spacing: .06em; color: var(--gold); border: 1px solid var(--gold); border-radius: 0; padding: .08rem .4rem; white-space: nowrap; }
#kb-share-scrim { z-index: 90; }
.kb-share { position: fixed; left: 50%; top: 50%; transform: translate(-50%, -46%); width: min(460px, 92vw); background: var(--panel); border-radius: var(--radius); box-shadow: 0 20px 60px rgba(0,0,0,.3); z-index: 95; opacity: 0; pointer-events: none; transition: opacity .16s, transform .16s; }
.kb-share-cta { width: 100%; margin-top: .3rem; }
.kb-share-hint { margin: .7rem 0 0; font-size: .78rem; color: var(--muted); }
.kb-share.show { opacity: 1; pointer-events: auto; transform: translate(-50%, -50%); }
.kb-share-in { padding: 1.3rem 1.4rem; }
.kb-share-in h3 { margin: .1rem 0 .2rem; font-size: 1.15rem; overflow-wrap: anywhere; }
.kb-share-sub { margin: 0 0 1rem; font-size: .85rem; color: var(--muted); }
.kb-share-warn { background: var(--danger-bg); border: 1px solid #e6c9bf; color: #8a3b2f; border-radius: 0; padding: .6rem .7rem; font-size: .84rem; }
.kb-share-linkrow { display: flex; gap: .4rem; margin: .2rem 0 .8rem; }
.kb-share-linkrow input { flex: 1; min-width: 0; font: inherit; font-size: .8rem; font-family: var(--mono, ui-monospace, Menlo, Consolas, monospace); border: 1px solid var(--line); border-radius: 0; padding: .5rem .6rem; background: var(--bg); color: var(--ink-soft); }
.kb-share-row { display: flex; align-items: center; gap: .6rem; padding: .7rem 0; border-top: 1px solid var(--line); font-size: .9rem; }
.kb-share-row select { margin-left: auto; font: inherit; font-size: .82rem; border: 1px solid var(--line); border-radius: 0; padding: .3rem .5rem; background: var(--bg); }
.kb-toggle { margin-left: auto; width: 42px; height: 24px; border-radius: 2px; background: #d8d3c8; position: relative; cursor: pointer; border: 0; flex: none; transition: background .15s; }
.kb-toggle::after { content: ""; position: absolute; top: 2px; left: 2px; width: 20px; height: 20px; border-radius: 50%; background: #fff; transition: left .15s; }
.kb-toggle.on { background: var(--ok); }
.kb-toggle.on::after { left: 20px; }
.kb-share-foot { display: flex; justify-content: flex-end; margin-top: 1rem; }

/* ---- contextual notes panel (on project & client pages) ---- */
.kb-ctx-group { font-size: .7rem; text-transform: none; letter-spacing: .1em; color: var(--muted); margin: 1.1rem 0 .5rem; }
.kb-ctx-group:first-of-type { margin-top: .25rem; }
/* A note is a ledger line that opens, like a grouped job or a delivery — not a
   boxed card. Open, its body indents behind a rail. */
.kb-ctx-list { display: flex; flex-direction: column; }
.kb-ctx-list > * + * { border-top: 1px solid var(--line); }
.kb-ctx-note { border: 0; border-radius: 0; background: none; }
.kb-ctx-note[open] { border-color: transparent; }
.kb-ctx-sum { cursor: pointer; padding: .5rem 0; list-style: none; }
.kb-ctx-sum::-webkit-details-marker { display: none; }
.kb-ctx-sum:hover .kb-ctx-title { color: var(--accent); }
.kb-ctx-note[open] .kb-ctx-ex { display: none; }
.kb-ctx-top { display: flex; align-items: center; gap: .5rem; }
.kb-ctx-title { order: 1; flex: 1 1 auto; min-width: 0; font-weight: 600; font-size: .86rem; overflow-wrap: anywhere; }
.kb-ctx-top .kb-kind { order: 2; flex: none; }
.kb-ctx-top .kb-pin { order: 3; flex: none; }
.kb-ctx-top::after { content: '▸'; order: 4; flex: none; color: var(--muted); font-size: .78rem; }
.kb-ctx-note[open] .kb-ctx-top::after { content: '▾'; }
.kb-ctx-ex { display: block; margin-top: .25rem; font-size: .8rem; color: var(--muted); overflow-wrap: anywhere; }
.kb-ctx-full { margin: 0 0 .5rem .15rem; padding: 0 0 0 1.15rem; border-left: 2px solid var(--line); }
.kb-read { font-size: .68rem; }   /* note body in the contextual panel */
.kb-read p { margin: .4rem 0; color: var(--ink-soft); }
.kb-read .md-h1 { font-size: .81rem; } .kb-read .md-h2 { font-size: .74rem; } .kb-read .md-h3 { font-size: .69rem; }
.kb-ctx-links { display: flex; align-items: center; gap: .4rem; flex-wrap: wrap; margin-top: .5rem; }
.kb-ctx-links .linkbtn-sm { margin-left: auto; }
.kb-ctx-add { margin-top: .6rem; }
.kb-ctx-add > input[name="title"] { width: 100%; margin-bottom: .5rem; }
.kb-ctx-add input[type="text"], .kb-ctx-add select, .kb-ctx-add textarea { font: inherit; font-size: .88rem; border: 1px solid var(--line); border-radius: 0; padding: .45rem .6rem; background: var(--bg); }
.kb-ctx-add textarea { width: 100%; resize: vertical; margin-top: .5rem; }
.kb-ctx-add .cf-row { display: flex; gap: .5rem; }
.kb-ctx-add .cf-row select { flex: 0 0 auto; }
.kb-ctx-add .cf-row input { flex: 1; min-width: 0; }

/* ---- Trash ---- */
.kb-trash-link { display: inline-block; margin-top: 1.3rem; padding: .35rem .6rem; border-radius: 0; font-size: .85rem; color: var(--muted); }
.kb-trash-link:hover { color: var(--gold); background: var(--panel); }
.kb-trash-link .n { font-variant-numeric: tabular-nums; }
.kb-trash-list { display: flex; flex-direction: column; gap: .75rem; }
.kb-trash-row { display: flex; gap: 1rem; align-items: flex-start; justify-content: space-between; background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius); box-shadow: var(--shadow); padding: 1rem 1.15rem; }
.kb-trash-main { min-width: 0; }
.kb-trash-ex { margin: .4rem 0 0; font-size: .88rem; overflow-wrap: anywhere; }
.kb-trash-actions { display: flex; gap: .5rem; flex: none; }
@media (max-width: 620px) {
    .kb-trash-row { flex-direction: column; }
    .kb-trash-actions { width: 100%; }
}

@media (max-width: 720px) {
    .kb-grid { grid-template-columns: minmax(0, 1fr); }
    .kb-notes { columns: 1; }
    /* Hide the sorting rail (All / Type / Client / Project / Tags) on mobile — the
       Active strip + search cover filtering. The hidden "All" button stays in the
       DOM so tapping an active project chip again can clear the filter. */
    .kb-rail { display: none; }
    /* Active-projects strip stacks vertically instead of a horizontal slider. */
    .kb-active { flex-direction: column; align-items: stretch; gap: .4rem; margin: .6rem 0 1rem; }
    .kb-active-strip { flex-direction: column; overflow: visible; gap: .4rem; padding: 0; }
    .kb-proj-wrap { width: 100%; }
    .kb-proj-wrap .kb-proj { width: auto; flex: 1; min-width: 0; }
    .kb-proj-name { max-width: none; flex: 1; }
    /* No hover on touch — keep the dismiss glyph faintly present so it's reachable. */
    .kb-active-strip .kb-proj-x { opacity: .45; }
    .kb-active-toggle { align-self: flex-start; }
    .kb-active-hidden.show { flex-direction: column; }
    /* List view on a phone: title + edited only. The colored left edge still carries
       the type, and tapping the row opens the note, so the rest is noise here.
       --kb-cols goes on the rows themselves, not the container: the container-query
       tiers above declare it on these same elements, and an element's own declaration
       always beats an inherited one however specific the ancestor's selector is. */
    .kb-notes.is-list .kb-list-head,
    .kb-notes.is-list .kb-note { --kb-cols: minmax(0, 1fr) 5rem; }
    .kb-notes.is-list .kb-list-head { display: none; }
    .kb-notes.is-list .kb-note { padding: .55rem .6rem; }
    .kb-notes.is-list .kb-note-top h4 { grid-column: 1; }
    .kb-notes.is-list .kb-dates { grid-column: 2; text-align: right; }
    .kb-notes.is-list .kb-kind,
    .kb-notes.is-list .kb-where,
    .kb-notes.is-list .kb-meta,
    .kb-notes.is-list .kb-pinbtn,
    .kb-notes.is-list .kb-actions { display: none; }
}
@media (prefers-reduced-motion: reduce) {
    .kb-scrim, .kb-drawer, .kb-note { transition: none; }
}

/* -----------------------------------------------------------------------------
   Reconciliation strip (recon.php) — booked value vs what's been invoiced.
   Used on the project page and the invoice preview. Prefixed to avoid clashes.
--------------------------------------------------------------------------- */
.recon { border: 1px solid var(--line); border-radius: var(--radius); background: var(--panel); padding: .85rem 1rem; }
.recon-ok { border-color: var(--line); }
.recon-gap { border-color: #E4B95B; background: #FBF6EA; }
.recon-nums { display: flex; align-items: flex-end; gap: 1rem; flex-wrap: wrap; }
.recon-cell { display: flex; flex-direction: column; gap: .15rem; }
.recon-k { font-size: .68rem; text-transform: none; letter-spacing: .04em; color: var(--ink-soft); font-weight: 600; }
.recon-v { font-size: 1.05rem; font-weight: 700; font-variant-numeric: tabular-nums; }
.recon-op { color: var(--muted); font-weight: 600; padding-bottom: .15rem; }
.recon-match { color: var(--paid, #2e7d5b); }
.recon-mismatch { color: #9A6A00; }
.recon-note { margin-top: .75rem; display: flex; align-items: center; justify-content: space-between; gap: 1rem; flex-wrap: wrap; }
.recon-note p { margin: 0; font-size: .82rem; color: var(--ink-soft); max-width: 62ch; }
@media (max-width: 620px) {
    .recon-note { flex-direction: column; align-items: flex-start; }
}
.pd-recon { margin: 0 0 1.25rem; }
.pd-recon:empty { display: none; margin: 0; }

/* -----------------------------------------------------------------------------
   Tasks — the Kanban board (tasks.php) + the dashboard "On my plate" peek.
   One board for everything; cards tagged to a project. Classes are prefixed
   `task-` to avoid clashing with the generic .card / .chip used elsewhere.
--------------------------------------------------------------------------- */
.tasks-head { display: flex; align-items: flex-end; justify-content: space-between; gap: 1rem; flex-wrap: wrap; }
.task-filter { display: inline-flex; align-items: center; gap: .5rem; }
.task-filter select,
.task-add-project,
.task-add-priority,
.task-add-title {
    font: inherit; padding: .5rem .6rem; border: 1px solid var(--line);
    border-radius: 0; background: #fff; color: var(--ink);
}
.task-filter select:focus,
.task-add select:focus,
.task-add input:focus { outline: 2px solid var(--gold); outline-offset: 1px; border-color: var(--gold); }

/* Add-a-task composer */
.task-add {
    display: flex; gap: .6rem; align-items: center; flex-wrap: wrap;
    background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius);
    padding: .75rem; box-shadow: var(--shadow); margin-bottom: 1.25rem;
}
.task-add-title { flex: 1 1 240px; min-width: 0; }
.task-add-project { flex: 0 1 220px; }
.task-add-priority { flex: 0 0 auto; }

/* Board grid */
.task-board { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1rem; align-items: start; }
.task-col {
    background: #eef1f5; border: 1px solid var(--line); border-radius: var(--radius);
    display: flex; flex-direction: column; min-height: 140px;
    /* --accent is the per-column colour, set by the modifier classes below */
    --accent: var(--muted);
    border-top: 3px solid var(--accent);
}
.task-col--todo  { --accent: #6b7f96; }   /* slate — not started      */
.task-col--doing { --accent: var(--gold); }/* gold  — actively working */
.task-col--done  { --accent: var(--ok); }  /* green — finished         */
.task-col-head {
    display: flex; align-items: center; justify-content: space-between;
    padding: .8rem 1rem .6rem; border-bottom: 1px solid var(--line);
}
.task-col-name { font-weight: 600; font-size: .95rem; color: var(--accent); }
.task-col-count {
    font-size: .72rem; font-weight: 700; color: var(--accent);
    background: #fff; border: 1px solid var(--line);            /* fallback */
    background: color-mix(in srgb, var(--accent) 12%, #fff);
    border-color: color-mix(in srgb, var(--accent) 30%, #fff);
    border-radius: 2px; min-width: 1.4rem; text-align: center; padding: .05rem .45rem;
}

/* Drop zone */
.task-col-body {
    display: flex; flex-direction: column; gap: .55rem;
    padding: .75rem; flex: 1; border-radius: 0 0 var(--radius) var(--radius);
    transition: background .12s ease;
}
.task-col-body.is-over { background: #e7efe9; box-shadow: inset 0 0 0 2px var(--gold); }
.task-col-body.is-empty::after {
    content: attr(data-empty); display: block; text-align: center;
    color: var(--muted); font-size: .82rem; padding: 1.4rem .5rem; opacity: .8;
    border: 1px dashed var(--line); border-radius: 0;
}

/* Cards — the whole card is tinted by priority (--prio-bg), with a stronger
   left stripe + border in the full priority colour (--prio). */
.task-card {
    background: var(--prio-bg, #fff); border: 1px solid var(--prio-bd, var(--line));
    border-left: 4px solid var(--prio, var(--gold));
    border-radius: 0; padding: .6rem .7rem .6rem .6rem; box-shadow: 0 1px 2px rgba(0,0,0,.05);
    display: flex; align-items: flex-start; gap: .5rem;
    cursor: grab; position: relative;
}
.task-card:active { cursor: grabbing; }
.task-card.is-dragging { opacity: .45; }
.task-card-main { display: flex; flex-direction: column; gap: .4rem; min-width: 0; flex: 1; }

/* Priority — --prio is the strong colour (stripe/flag/dot); --prio-bg / --prio-bd
   tint the whole card. Deliberately red / orange / grey: distinct from the app's
   gold accent AND from the cool project-label palette, so a priority is never
   mistaken for a project. */
.prio-high   { --prio: #c0392b; --prio-bg: #fbe6e3; --prio-bd: #eeb7b0; }  /* red    */
.prio-medium { --prio: #cf6a1a; --prio-bg: #fcecd8; --prio-bd: #f0ca9c; }  /* orange */
.prio-low    { --prio: #7f8a97; --prio-bg: #eef1f4; --prio-bd: #d7dde3; }  /* grey   */

/* The always-visible priority flag; click cycles Low → Medium → High */
.task-flag {
    flex: none; background: none; border: none; cursor: pointer; padding: .05rem .1rem;
    font-size: .95rem; line-height: 1.25; color: var(--prio, var(--gold));
    border-radius: 5px; margin-top: .05rem;
}
.task-flag:hover { background: #eef1f5; }
.prio-low .task-flag { opacity: .8; }
.task-title { font-size: .9rem; line-height: 1.35; word-break: break-word; cursor: text; }
.task-title:focus { outline: 2px solid var(--gold); outline-offset: 2px; border-radius: 3px; }
.task-title-input {
    font: inherit; font-size: .9rem; width: 100%; padding: .2rem .35rem;
    border: 1px solid var(--gold); border-radius: 5px; outline: none;
}
.task-card.is-done .task-title { text-decoration: line-through; color: var(--muted); }

/* Project tag chip on a card */
.task-tag {
    align-self: flex-start; font-size: .68rem; font-weight: 600; letter-spacing: .01em;
    color: #7a642f; background: #f7f1e2; border: 1px solid #ece0c4;
    padding: .1rem .45rem; border-radius: 2px; max-width: 100%;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* Per-card hover tools (rename / delete) */
.task-card-tools { display: flex; gap: .1rem; opacity: 0; transition: opacity .1s ease; flex: none; }
.task-card:hover .task-card-tools,
.task-card:focus-within .task-card-tools { opacity: 1; }
.task-del-form { margin: 0; display: inline; }
.task-mini {
    font: inherit; line-height: 1; cursor: pointer; color: var(--muted);
    background: none; border: none; padding: .1rem .25rem; border-radius: 5px; font-size: .95rem;
}
.task-mini:hover { color: var(--ink); background: #eef1f5; }
.task-del:hover { color: var(--danger); background: var(--danger-bg); }
.task-hint { margin-top: 1rem; }

/* Project chip as a link (click a card's tag to filter the board to it) */
a.task-tag { text-decoration: none; cursor: pointer; }
a.task-tag:hover { filter: brightness(.96); }
/* "from mail" — where a card came from, on the cards the app made rather than
   you. Quieter than a project tag and set as plain underlined text on purpose:
   it is provenance, not another label competing for the eye, and its whole job is
   to be there when you ask "where did this come from?". */
.task-src { align-self: flex-start; font-size: .68rem; color: var(--muted);
    text-decoration: underline; text-underline-offset: 2px; text-decoration-style: dotted; }
.task-src:hover { color: var(--accent); }

.task-clear { display: inline-block; margin-top: .3rem; font-size: .8rem; font-weight: 600; color: var(--gold); }

/* Related-notes strip — shown when the board is filtered to one project */
.task-notes {
    background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius);
    box-shadow: var(--shadow); padding: 1rem 1.15rem; margin-bottom: 1.25rem;
}
.task-notes-head { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem; margin-bottom: .6rem; }
.task-notes-head h2 { margin: 0; font-size: 1rem; }
.task-notes-open { font-size: .82rem; font-weight: 600; color: var(--gold); white-space: nowrap; }
.task-notes-empty { margin: 0; }
.task-notes-list {
    list-style: none; margin: 0; padding: 0; display: grid;
    grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: .5rem;
}
.task-notes-list a {
    display: block; text-decoration: none; color: inherit; background: #f8fafb;
    border: 1px solid var(--line); border-radius: 0; padding: .5rem .65rem;
}
.task-notes-list a:hover { border-color: var(--gold); }
.task-note-title { display: block; font-size: .86rem; font-weight: 600; color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.task-note-ex { display: block; font-size: .76rem; color: var(--muted); margin-top: .15rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

@media (max-width: 760px) {
    .task-board { grid-template-columns: 1fr; }
    .task-add-project { flex-basis: 100%; }
}

/* Dashboard peek — a window into the board */
.task-peek { margin-bottom: 1.25rem; }
.task-peek .panel-head { margin-bottom: .75rem; }
.task-peek-link { font-size: .82rem; font-weight: 600; color: var(--gold); white-space: nowrap; }
.task-peek-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: .5rem; }
.task-peek-list li { display: flex; align-items: center; gap: .55rem; }
.task-peek-dot { width: .5rem; height: .5rem; border-radius: 50%; background: var(--prio, var(--gold)); flex: none; }
.task-peek-title { font-size: .9rem; color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.task-peek-empty { margin: 0; }

/* -------------------------------------------------------------------------- */
/* Client file vault — the Files panel on project + client pages.             */
/* Class-prefixed (vault-/vf-) to avoid collisions with generic app classes.  */
/* -------------------------------------------------------------------------- */
.vault-list { display: flex; flex-direction: column; gap: .5rem; }
.vault-row { display: flex; align-items: center; gap: .6rem; padding: .5rem .65rem; border: 1px solid var(--line); border-radius: 0; background: var(--panel); }
.vault-row:hover { border-color: var(--gold); }
.vault-glyph { font-size: 1.1rem; line-height: 1; flex: none; }
.vault-main { min-width: 0; flex: 1; }
.vault-name { display: block; font-weight: 600; font-size: .88rem; overflow-wrap: anywhere; }
.vault-name:hover { color: var(--gold); }
.vault-meta { display: flex; flex-wrap: wrap; align-items: center; gap: .35rem; margin-top: .2rem; font-family: var(--mono); font-size: .72rem; }
.vault-meta a { color: var(--muted); }
.vault-meta a:hover { color: var(--gold); }
.vault-del { flex: none; }

.vault-kind { font-family: var(--mono); font-size: .56rem; text-transform: uppercase; letter-spacing: .08em; font-weight: 700; padding: .14rem .45rem; border-radius: 0; white-space: nowrap; }
/* Where an uploaded file names the same thing as an editable document — a signed
   contract, a scope, a résumé, a letter — it takes that kind's colour from the
   .dk-* set below, so the concept looks the same in the Files tab as it does in
   Documents. The kinds with no document counterpart keep their own hues. */
.vf-k-contract     { background: color-mix(in srgb, #8E3A7E 11%, transparent); color: #8E3A7E; }
.vf-k-sow          { background: color-mix(in srgb, #C05F27 11%, transparent); color: #C05F27; }
.vf-k-invoice      { background: #eceef4; color: #3f5487; }
.vf-k-resume       { background: color-mix(in srgb, #8A6A2F 12%, transparent); color: #8A6A2F; }
.vf-k-cover_letter { background: color-mix(in srgb, #5B6470 12%, transparent); color: #5B6470; }
.vf-k-application  { background: #eef4ef; color: #3f7a52; }
.vf-k-tax          { background: #f6f0ea; color: #8a5a2f; }
.vf-k-legal        { background: #eceef4; color: #45507a; }
.vf-k-other        { background: #eeece6; color: #6b6355; }

.vault-add { margin-top: .6rem; }
.vault-add-row { margin-bottom: .55rem; }
.vault-add-row input[type="file"] { width: 100%; font: inherit; font-size: .84rem; }
.vault-add-row:has(input[type="text"]) { display: flex; gap: .5rem; flex-wrap: wrap; }
.vault-add input[type="text"] { flex: 1; min-width: 12rem; font: inherit; font-size: .88rem; border: 1px solid var(--line); border-radius: 0; padding: .45rem .6rem; background: var(--bg); }
.vault-add select { font: inherit; font-size: .88rem; border: 1px solid var(--line); border-radius: 0; padding: .45rem .6rem; background: var(--bg); }
.vault-add .cf-actions { display: flex; align-items: center; gap: .7rem; flex-wrap: wrap; }

/* Docs → Files vault: the Business / Clients bucket tabs and grouped lists. */
.docs .kb-facts { box-shadow: none; margin: 0; padding: 1.15rem 1.25rem; }
.dv-tabs { display: flex; gap: .4rem; border-bottom: 1px solid var(--line); margin-bottom: 1rem; }
.dv-tab { font: inherit; font-size: .9rem; font-weight: 600; color: var(--muted); background: none; border: 0;
          border-bottom: 2px solid transparent; padding: .5rem .7rem; margin-bottom: -1px; cursor: pointer; }
.dv-tab:hover { color: var(--ink); }
.dv-tab.is-active { color: var(--ink); border-bottom-color: var(--gold); }
.dv-blurb { margin: 0 0 .9rem; }
.dv-group { margin-bottom: 1.1rem; }
.dv-group:last-child { margin-bottom: 0; }
.dv-group-head { display: flex; align-items: baseline; gap: .6rem; margin: 0 0 .45rem; padding-bottom: .3rem;
                 border-bottom: 1px solid var(--rule-soft, #eef1f5); }
.dv-group-head a { font-weight: 600; font-size: .9rem; }
.dv-group-head a:hover { color: var(--gold); }

/* Vault drag-and-drop zone — quick multi-file upload (auto-named, no category). */
.vault-drop { display: block; margin: 0 0 .7rem; border: 1.5px dashed var(--line); border-radius: 0; background: var(--bg); cursor: pointer; transition: border-color .12s, background .12s; }
.vault-drop:hover { border-color: var(--gold); }
.vault-drop.is-drag { border-color: var(--gold); background: var(--gold-soft, rgba(238,123,60,.12)); }
.vault-drop.is-uploading { opacity: .6; pointer-events: none; }
.vault-drop-inner { display: flex; flex-direction: column; align-items: center; gap: .25rem; text-align: center; padding: 1rem .8rem; }
.vault-drop-icon { font-size: 1.15rem; line-height: 1; color: var(--gold); }
.vault-drop-text { font-size: .9rem; color: var(--ink-soft); font-weight: 500; }
.vault-drop-link { color: var(--gold); text-decoration: underline; }
.vault-drop-hint { display: block; }

/* Image files get a real thumbnail (served small through the private route). */
.vault-thumb-link { flex: none; line-height: 0; display: block; }
.vault-thumb { width: 2.4rem; height: 2.4rem; object-fit: cover; border-radius: 6px; border: 1px solid var(--line); background: var(--bg); display: block; }
.vault-thumb-link:hover .vault-thumb { border-color: var(--gold); }

/* Vault row actions: a Download button beside the delete link. */
.vault-actions { display: inline-flex; align-items: center; gap: .55rem; flex: none; }
.vault-dl { display: inline-flex; align-items: center; gap: .25rem; font-size: .78rem; color: var(--ink-soft); border: 1px solid var(--line); border-radius: 0; padding: .22rem .55rem; white-space: nowrap; }
.vault-dl:hover { border-color: var(--gold); color: var(--gold); }

/* Assign-to-client picker on an unfiled file row. */
.vault-assign { display: inline-flex; align-items: center; gap: .4rem; }
.vault-assign select { font-size: .8rem; padding: .22rem .4rem; max-width: 12rem; }

/* -----------------------------------------------------------------------------
 * Command palette — global quick search (Ctrl/⌘+K or "/"). See
 * app/partials/command_palette.php. Classes are cmdk- prefixed to stay clear of
 * the app's generic utility classes.
 * -------------------------------------------------------------------------- */
.cmdk-overlay {
    position: fixed; inset: 0; z-index: 900;
    background: rgba(26, 26, 26, .34);
    backdrop-filter: blur(1.5px);
}
.cmdk-modal {
    position: fixed; z-index: 901;
    top: 12vh; left: 50%; transform: translateX(-50%);
    width: min(620px, calc(100vw - 2rem));
    background: var(--panel);
    border: 1px solid var(--line);
    border-radius: 14px;
    box-shadow: 0 12px 48px rgba(0,0,0,.22), 0 2px 8px rgba(0,0,0,.08);
    overflow: hidden;
    display: flex; flex-direction: column;
    max-height: 70vh;
}
/* Enter animation (Alpine x-transition:enter hooks). */
.cmdk-in { transition: opacity .14s ease, transform .14s ease; }
.cmdk-in-start { opacity: 0; transform: translateX(-50%) translateY(-6px); }

.cmdk-search {
    display: flex; align-items: center; gap: .6rem;
    padding: .85rem 1rem;
    border-bottom: 1px solid var(--line);
}
.cmdk-search-icon { width: 18px; height: 18px; opacity: .7; flex: none; color: var(--muted); }
.cmdk-input {
    flex: 1 1 auto; border: 0; outline: 0; background: transparent;
    font-family: var(--font); font-size: 1.05rem; color: var(--ink);
    padding: .1rem 0;
}
.cmdk-input::placeholder { color: var(--muted); }
.cmdk-hint {
    flex: none; font-size: .7rem; color: var(--muted);
    border: 1px solid var(--line); border-radius: 5px; padding: .1rem .4rem;
}

.cmdk-results { overflow-y: auto; padding: .35rem; }
.cmdk-group { padding: .25rem 0; }
.cmdk-group-label {
    font-size: .68rem; text-transform: none; letter-spacing: .06em;
    color: var(--muted); padding: .35rem .6rem .25rem;
}
.cmdk-item {
    display: flex; align-items: center; gap: .65rem; width: 100%;
    text-align: left; background: transparent; border: 0; cursor: pointer;
    font-family: var(--font); color: var(--ink);
    padding: .5rem .6rem; border-radius: 0; line-height: 1.2;
}
.cmdk-item.is-active { background: var(--bg); }
.cmdk-item.is-active .cmdk-item-label { color: var(--gold); }
.cmdk-item-icon { flex: none; width: 1.3rem; height: 18px; display: inline-flex; align-items: center; justify-content: center; color: var(--muted); }
.cmdk-item-icon svg { width: 18px; height: 18px; }
.cmdk-item.is-active .cmdk-item-icon { color: var(--gold); }
.cmdk-item-label {
    flex: 1 1 auto; font-size: .92rem; font-weight: 500;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.cmdk-item-sub {
    flex: 0 1 auto; font-size: .78rem; color: var(--muted);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 45%;
}
.cmdk-item-type {
    flex: none; font-size: .66rem; color: var(--muted);
    border: 1px solid var(--line); border-radius: 5px; padding: .08rem .38rem;
}
/* Palette calculator row: an arithmetic answer, or a paycheck-split breakdown. */
.cmdk-calc { align-items: flex-start; }
.cmdk-calc-body { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: .35rem; }
.cmdk-calc-head { display: flex; align-items: baseline; gap: .5rem; min-width: 0; }
.cmdk-calc-value { font-size: 1.02rem; font-weight: 600; font-variant-numeric: tabular-nums; }
.cmdk-item.is-active .cmdk-calc-value { color: var(--gold); }
.cmdk-calc-expr {
    font-size: .74rem; color: var(--muted);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.cmdk-calc-rows { display: flex; flex-wrap: wrap; gap: .3rem .35rem; }
.cmdk-calc-chip {
    display: inline-flex; align-items: baseline; gap: .3rem;
    border: 1px solid var(--line); border-radius: 6px; padding: .12rem .42rem;
    font-size: .75rem;
}
.cmdk-calc-cat { color: var(--ink-soft); }
.cmdk-calc-pct { color: var(--muted); font-size: .66rem; }
.cmdk-calc-amt { font-weight: 600; font-variant-numeric: tabular-nums; }
/* The take-home line is the one you actually plan against — give it the accent. */
.cmdk-calc-chip.is-net .cmdk-calc-amt { color: var(--gold); }

.cmdk-empty { padding: 1.4rem 1rem; text-align: center; color: var(--muted); font-size: .9rem;
    line-height: 1.7; }
/* The example amounts in the empty state. Quieter than the kbd keys in the
   footer, which are keys you press — these are words you type. */
.cmdk-empty kbd { font-family: var(--mono); font-size: .82rem; color: var(--ink-soft);
    border: 1px solid var(--line); border-radius: 4px; padding: .05rem .3rem;
    background: var(--panel); }

.cmdk-foot {
    display: flex; gap: 1.1rem; justify-content: flex-end;
    padding: .5rem .9rem; border-top: 1px solid var(--line);
    font-size: .72rem; color: var(--muted);
}
.cmdk-foot kbd {
    font-family: var(--font); font-size: .66rem;
    border: 1px solid var(--line); border-radius: 4px;
    padding: .02rem .3rem; margin-right: .15rem; color: var(--ink-soft);
}
/* Enter opens most rows but COPIES a piece of wording — the quieter half of the
   hint says so without making the footer read like two separate keys. */
.cmdk-foot-or { opacity: .62; }
@media (max-width: 768px) {
    /* A compact, vertically-centred dialog — easier to reach than a long list
       pinned to the top. The empty state is just the field + recent items
       (command_palette.php trims the "Go to" list away on phones). */
    .cmdk-modal { top: 50%; transform: translate(-50%, -50%); width: calc(100vw - 1.4rem); max-height: 68vh; }
    .cmdk-in-start { transform: translate(-50%, -50%) translateY(-8px); }
    .cmdk-item-sub { display: none; }
    .cmdk-foot { display: none; }
}

/* Command-palette deep-link: briefly flag the card you jumped to. */
@keyframes cmdk-flash-pulse {
    0%   { box-shadow: 0 0 0 3px rgba(238,123,60,.55); }
    100% { box-shadow: 0 0 0 3px rgba(238,123,60,0); }
}
.cmdk-flash {
    animation: cmdk-flash-pulse 2s ease-out 1;
    border-radius: var(--radius);
    scroll-margin-top: 90px;
}

/* ============================================================
   SECURITY TAB  (two-factor setup)
   Prefixed .sec-* to avoid collisions with the app's generic UI
   classes. Reuses .panel / .btn styling; only the bits unique to
   2FA (the setup key, the backup-code grid, inline password rows)
   are defined here.
   ============================================================ */
.sec-key-row { display: flex; align-items: center; gap: .6rem; flex-wrap: wrap; }
.sec-key {
    font-family: ui-monospace, "SFMono-Regular", Menlo, Consolas, monospace;
    font-size: 1.05rem; letter-spacing: .04em;
    background: var(--wash, #f6f7f9); border: 1px solid var(--line);
    border-radius: 0; padding: .55rem .75rem; cursor: pointer; user-select: all;
    word-break: break-all;
}
.sec-confirm-form, .sec-pw-form { display: flex; gap: .6rem; flex-wrap: wrap; align-items: center; }
.sec-confirm-form input, .sec-pw-form input {
    padding: .55rem .7rem; border: 1px solid var(--line); border-radius: 0;
    font-size: .95rem; min-width: 12rem; background: #fff; color: var(--ink);
}
.sec-confirm-form input { font-family: ui-monospace, Menlo, Consolas, monospace; letter-spacing: .25em; max-width: 9rem; }
.sec-confirm-form input:focus, .sec-pw-form input:focus {
    outline: 2px solid var(--gold); outline-offset: 1px; border-color: var(--gold);
}
.sec-codes-panel { border-left: 4px solid var(--ok, #2e7d5b); }
.sec-codes {
    list-style: none; margin: .3rem 0 .9rem; padding: 0;
    display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: .4rem .9rem;
    max-width: 26rem;
}
.sec-codes code {
    font-family: ui-monospace, Menlo, Consolas, monospace; font-size: 1rem;
    letter-spacing: .06em; background: var(--wash, #f6f7f9);
    border: 1px solid var(--line); border-radius: 6px; padding: .3rem .5rem; display: block; text-align: center;
}
@media (max-width: 560px) {
    .sec-codes { grid-template-columns: 1fr; }
    .sec-confirm-form, .sec-pw-form { flex-direction: column; align-items: stretch; }
    .sec-confirm-form input, .sec-pw-form input { min-width: 0; max-width: none; }
}

/* ============================================================
   MOBILE REFINEMENTS  (≤768px)
   One consolidated pass so the whole small-screen feel lives (and
   tunes) in a single place. Placed last on purpose: at equal
   specificity it cleanly overrides the scattered per-component
   rules above. Goals: smaller headings + buttons, tighter panels,
   and denser list rows — less empty vertical space, more info at
   a glance.
   ============================================================ */
@media (max-width: 768px) {

    /* ---- Vertical rhythm: reclaim space around every page ---- */
    .page-head { margin-bottom: .85rem; }
    .page-head h1 { font-size: 1.4rem; margin: .12rem 0 .3rem; line-height: 1.2; }
    .page-head > p.muted { font-size: .85rem; margin-top: .2rem; }
    .eyebrow { font-size: .66rem; }
    .panel { padding: 1rem 1.05rem; }

    /* ---- Flush edges: pull content close to the screen so lists read like
       an app, not a card nested inside a page inside a gutter. Narrow the
       page gutter and trim panel side-padding. (Was 1.5rem wrap + 1.05rem
       panel ≈ 41px of inset each side; now ≈ .6 + .75 ≈ 22px.) ---- */
    .wrap { padding-left: .6rem; padding-right: .6rem; }
    .panel { padding-left: .75rem; padding-right: .75rem; }
    .cd-main .panel, .cd-side .panel,
    .docs .panel, .docs-side .panel { padding: 1rem 1.05rem; }
    .cd-main .panel h2, .cd-side .panel h2, .docs .panel h2 { font-size: .98rem; }
    .panel-head { margin-bottom: .7rem; }

    /* ---- Buttons: smaller everywhere except the already-tiny .btn-sm,
       and smaller still inside page-head / detail action rows. ---- */
    .btn:not(.btn-sm) { padding: .5rem .85rem; font-size: .88rem; }
    .head-actions .btn,
    .detail-status .btn,
    .detail-status .status-select { padding: .42rem .72rem; font-size: .82rem; }
    .head-actions .kbd { display: none; }   /* keyboard hint is useless on touch */

    /* ---- Page heads with ONE action (Projects, Clients): keep the
       headline and the now-compact button together on one row. ---- */
    .projects-head { align-items: center; gap: .75rem; }
    .projects-head h1 { font-size: 1.35rem; }
    .projects-head .head-actions { flex: 0 0 auto; }
    .cl-fetch-logos { display: none; }   /* bulk logo-fetch is a desktop-only chore */

    /* ---- Search + sort: on a phone the single wrapping row stranded "Name" up
       beside the search while the other options dropped below. Give the search
       its own full-width row, then the whole "Sort:" group its own row below —
       left-aligned (no desktop margin-left:auto) with tap-friendly options. ---- */
    .sort-bar { gap: .6rem; }
    .client-search { flex: 1 1 100%; max-width: none; min-width: 0; }
    .sort-options { margin-left: 0; width: 100%; gap: .4rem; }
    .sort-label { margin-right: .1rem; }
    .sort-bar a { padding: .38rem .7rem; font-size: .74rem; }   /* taller pill touch target; wraps gracefully if labels don't fit one row */

    /* ---- Detail + Tasks heads (many actions / a wide filter): title on
       its own line, actions or filter wrap tidily beneath it. ---- */
    .detail-head, .tasks-head { flex-direction: column; align-items: stretch; gap: .55rem; }
    .detail-head .head-actions, .detail-status { flex-wrap: wrap; gap: .4rem; }
    .detail-status .btn { flex: 0 1 auto; white-space: nowrap; }
    .tasks-head .task-filter { width: 100%; }
    .tasks-head .task-filter select { flex: 1; min-width: 0; max-width: 100%; }

    /* ---- Summary cards (Budget runway, month rollups): 2-up + compact,
       instead of one oversized card per row. ---- */
    .cards { grid-template-columns: 1fr 1fr; gap: .6rem; margin-bottom: 1rem; }
    .cards .card { padding: .7rem .8rem; }
    .cards .card-label { font-size: .66rem; }
    .cards .card-value { font-size: 1.25rem; margin-top: .1rem; }

    /* ---- Budget sub-tabs: break the joined segmented control into separate
       wrapping pills so all five (…Accounts, Expenses) are visible at once,
       rather than a single strip that ran off the right edge (which also gave
       the page a horizontal scroll and made the fixed bottom nav drift). ---- */
    .seg.tabs {
        display: flex; flex-wrap: wrap; gap: .4rem; max-width: 100%;
        border: 0; background: none; border-radius: 0; overflow: visible;
    }
    .seg.tabs a {
        flex: 0 0 auto; white-space: nowrap;
        border: 1px solid var(--line); border-radius: 2px; background: #fff;
    }
    .seg.tabs a.on { background: var(--ink); color: #fff; border-color: var(--ink); }

    /* ---- Projects list: compact rows. Amount sits on the right, directly
       under a smaller status control; everything sized down a notch. ---- */
    .project-row {
        grid-template-columns: auto 1fr auto;
        grid-template-areas: "logo main status" "logo meta value" "inv inv inv";
        align-items: start; padding: .5rem .65rem; gap: .12rem .6rem;
    }
    .project-row .pr-meta { text-align: left; }
    .pr-avatar { width: 30px; height: 30px; }
    .pr-title a { font-size: .88rem; }
    .pr-client { font-size: .76rem; margin-left: .35rem; }
    .pr-meta { font-size: .74rem; }
    .pr-value { justify-self: end; text-align: right; font-size: .82rem; }
    .pr-status { justify-self: end; }
    .pr-status .status-select { font-size: .72rem; padding: .18rem .4rem; }

    /* ---- Clients list: one clean line per client. The contact name/email and
       the Edit link are dropped (open the client to reach those) so the list
       scans fast: avatar + name + website, with last contact held on the right.
       The date earns its place here — sorting out who has gone quiet is the job
       this list does, and it is the one column worth a phone's width. ---- */
    .client-row {
        grid-template-columns: minmax(0, 1fr) auto; grid-template-areas: "name touch";
        padding: .55rem .75rem; gap: .1rem .6rem; align-items: center;
    }
    .cl-contact-info, .cl-actions { display: none; }
    .cl-touch { grid-area: touch; justify-content: flex-end; align-self: center; font-size: .68rem; }
    .cl-avatar { width: 26px; height: 26px; }
    .cl-name { font-size: .92rem; gap: .4rem; }

    /* ---- Forms (e.g. edit client): single column that can actually shrink,
       so inputs never trail off the right edge. ---- */
    .form-grid { grid-template-columns: minmax(0, 1fr); }

    /* ---- Task composer: keep every select inside the viewport ---- */
    .task-add select, .task-add input { min-width: 0; max-width: 100%; }

    /* ---- Docs / vault rows: name (line 1) → meta → actions drop to their
       own right-aligned line, so nothing gets squeezed off-screen. ---- */
    .vault-row { flex-wrap: wrap; row-gap: .3rem; }
    .vault-main { min-width: 0; }
    .vault-actions { flex: 1 1 100%; justify-content: flex-end; }

    /* ---- Client name must stay one line (ellipsis), never wrap under the
       avatar. ---- */
    .cl-name-line { flex-wrap: nowrap; }
    .cl-link { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

    /* ---- Client detail "mini project" rows: four columns won't fit a phone, so
       the row folds to two lines — name over its days/status, money held at the
       right where the eye already looks for it. ---- */
    .mini-project {
        grid-template-columns: minmax(0, 1fr) auto;
        grid-template-areas: "title value" "meta  status";
        gap: .1rem .7rem; align-items: center; padding: .55rem 0;
    }
    .mp-title  { grid-area: title; line-height: 1.3; }
    .mp-value  { grid-area: value; }
    .mp-days   { grid-area: meta; text-align: left; }
    .mp-status { grid-area: status; justify-self: end; }
    .mini-job-sum {
        grid-template-columns: minmax(0, 1fr) auto 1.1em;
        grid-template-areas: "title value chev" "meta  status chev";
    }
    .mini-job-chev { grid-area: chev; align-self: center; }
    .mini-job-count { white-space: nowrap; }
    .mini-job-pieces { padding-left: .6rem; margin-left: .15rem; }

    /* ---- Section sheets: tighter on a phone. One number does it — the head's
       edge-to-edge rule is calculated off --sheet-pad, so it follows. ---- */
    .client-detail .cd-main > .panel,
    .project-detail .cd-main > .panel,
    .project-detail .cd-main > .piece { --sheet-pad: .85rem; padding-top: .95rem; padding-bottom: 1rem; }

    /* ---- Workspace card: four tabs on a narrow screen scroll sideways, and the
       favourites shortlist above them stays a quiet one-line-each list. ---- */
    .cl-work-tabs { gap: .1rem; margin-bottom: .8rem; }
    .cl-work-tabs .dv-tab { font-size: .82rem; padding-left: .45rem; padding-right: .45rem; }
    .cl-work .cfav-embed { margin-bottom: .85rem; padding-bottom: .7rem; }
    .cfav-embed .cfav-item a { font-size: .82rem; }
    /* Delivery / document rows: actions drop to their own right-aligned line. */
    .cdel-new .btn-sm { font-size: .66rem; }

    /* ---- Top bar: keep the "next invoice" pill on the same row as the brand
       when it fits (wraps only if it truly must), and make it compact. ---- */
    .topbar-right { flex-basis: auto; order: 0; }
    .next-payout { padding: .28rem .55rem; gap: .35rem; min-width: 0; overflow: hidden; }
    .next-payout .np-when { font-size: .74rem; overflow: hidden; text-overflow: ellipsis; }

    /* ---- Payout banner (dashboard + project): tighter, fewer lines. ---- */
    .payout-banner { padding: .55rem .7rem; font-size: .82rem; line-height: 1.35; gap: .5rem; }
    .payout-banner .pb-icon { font-size: 1rem; }

    /* ---- Invoices: filters → list → folded calendars; hide the wide sheet. ---- */
    .inv-filters { order: 1; }
    .inv-list-mobile { order: 2; display: flex; flex-direction: column; gap: .5rem; }
    .inv-cal-fold { order: 3; }
    .inv-sheet-wrap, .inv-main .sheet-note { display: none; }

    /* ---- Single note (/notes/<id>): the shared NOTE-FIRST EDITING SKIN (above)
       already handles the fold, the borderless surface, and reading type on every
       width. These are the phone-only deltas: use the full width and quiet the
       footer. ---- */
    .kb-single { max-width: none; }
    .kb-single-foot { margin-top: 1rem; padding-top: .8rem; }
    .kb-single-foot .kbd { display: none; }

    /* ---- Booked this month: on a phone the horizontal chip strip crams 3–8
       jobs into slivers. Stack them as full-width rows instead — more room,
       less jammed — with hairline separators instead of vertical rules. ---- */

    /* ---- Recent activity on a phone: NAME-FIRST. The kind/action labels and
       the pin button are dropped (pinning moves to a swipe — dash-swipe.js);
       kind glyphs shed their tile so only real files read as thumbnails. Rows keep
       just the title + its context/time, on two fixed lines. ---- */
    .da-row { grid-template-columns: 1.7rem minmax(0,1fr);
              grid-template-areas: "ico title"
                                   "ico meta";
              align-items: start; gap: .1rem .55rem;
              padding: .6rem .3rem; touch-action: pan-y; }
    .da-status, .da-pinform { display: none; }        /* labels + tap-pin removed */
    /* Named areas, not auto-placement. Every row is the same two lines — the name,
       then its context and time under it — whether or not it has a thumbnail. */
    .da-ico { grid-area: ico; align-self: center; width: 1.7rem; height: 1.7rem; }
    .da-title { grid-area: title; order: 0; }
    .dr-meta { grid-area: meta; grid-column: auto; font-size: .68rem;
               min-width: 0; overflow: hidden; text-overflow: ellipsis; }
    /* Real file thumbnails keep their tile; the kind glyphs lose theirs and read as
       a quiet mark. They stay in the grid, so every title shares one left edge. */
    .da-ico:not(:has(img)) { background: none; border: 0; color: var(--muted); font-size: .95rem; }
    .da-excerpt { display: none; }
    /* Swipe affordance: a soft ember wash slides in from the edge while dragging. */
    .da-row.da-swiping { background: rgba(238,123,60,.10); transition: none; }
    .da-row .da-title, .da-row .dr-meta { transition: transform .18s ease; }

    /* ---- Stop iOS Safari from zooming when a note field is focused. Any input
       under 16px triggers an auto-zoom on tap; lift the editable note fields to
       exactly 16px on mobile (desktop keeps its compact sizing). ---- */
    .kb-body, .kb-single .kb-body, .kb-drawer .kb-body,
    .kb-attach { font-size: 16px; }

}

/* ============================================================
   EMAIL — client mail workspace (Phase 2: timeline + mini panel)
   Prefixed .em-* to avoid collisions. Responsive by default; only a
   small phone delta lives in the @media at the foot of this section.
   ============================================================ */

/* Mini list — the SHARED email row, used by the inbox, the dashboard "Recent
   client mail" strip, and the client Email panel. Markup lives in one place too:
   app/partials/email_row.php. Edit the partial + these rules together and all
   three surfaces stay in sync (logo, studio colour, favourite, hover). */
.em-mini-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; }
.em-mini + .em-mini { border-top: 1px solid var(--line); }
.em-mini-link { position: relative; display: flex; gap: .6rem; align-items: flex-start; padding: .55rem .5rem;
                border-radius: 7px; transition: background .12s ease, box-shadow .12s ease; }
/* Hover is a breath of ember, not the grey selection wash — the wash is a solid
   slab against the white panel and reads like a disabled row. The 2px rail is
   what actually says "this one". */
.em-mini-link:hover { background: color-mix(in srgb, var(--accent) 5%, transparent);
                      box-shadow: inset 2px 0 0 var(--accent); }
/* The row-wide "open email" click target sits under the content… */
.em-mini-stretch { position: absolute; inset: 0; z-index: 0; }
.em-mini-stretch::after { content: ''; position: absolute; inset: 0; }
/* …and the client name is lifted above it so it can link somewhere else. */
.cl-name-link { position: relative; z-index: 2; text-decoration: none; }
.cl-name-link:hover { text-decoration: underline; }
.em-mini-main { flex: 1; min-width: 0; }
.em-logo { flex: none; width: 26px; height: 26px; border-radius: 6px; overflow: hidden;
           margin-top: .1rem; background: var(--bg); }
.em-logo img { width: 100%; height: 100%; object-fit: contain; display: block; }
.em-logo-mono { width: 100%; height: 100%; display: grid; place-items: center;
                color: #fff; font-size: .8rem; font-weight: 600; }
.em-mini-top { display: flex; justify-content: space-between; align-items: baseline; gap: .5rem; }
.em-mini-from { font-weight: 600; font-size: .9rem; color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cl-name { font-weight: 500; }
.em-mini-date { font-family: var(--mono); color: var(--muted); font-size: .7rem; white-space: nowrap; flex: none; }
.em-mini-subj { display: block; font-size: .85rem; color: var(--ink-soft); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.em-mini-snip { display: block; font-size: .78rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* The dashboard client-mail strip had a dismiss button, a "mark all read"
   action bar and a faded just-read row. All three are gone with the strip: an
   app-side read flag applied to the whole archive meant nothing, because mail is
   read in Gmail. See app/actions/dashboard.php. What replaced it is below —
   the same flag, but asked only of TODAY, where it can still be answered. */

/* Came in today, not opened here yet. The mark has to be legible in a list where
   every row already carries a studio colour, so it doesn't add a fifth hue: a
   filled ember dot, the sender at full ink, and the arrival TIME instead of the
   date. The tint under it is a whisper — the dot is what you actually scan for.
   Hover keeps its own 2px rail, so a new row still answers the mouse. Opening the
   message (the lightbox card) clears the mark for good. */
.em-mini-new .em-mini-link { background: color-mix(in srgb, var(--accent) 4%, transparent); }
.em-mini-new .em-mini-link:hover { background: color-mix(in srgb, var(--accent) 10%, transparent); }
.em-mini-new .em-mini-from { font-weight: 700; }
.em-mini-new .em-mini-subj { color: var(--ink); }
.em-mini-new .em-mini-date { color: var(--accent); font-weight: 600; }
.em-new-dot { display: inline-block; width: 6px; height: 6px; border-radius: 50%; flex: none;
              background: var(--accent-glow); margin-right: .45rem; vertical-align: .08em;
              box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent-glow) 16%, transparent); }

.em-fav-star { color: var(--accent); }
.em-fav-tag { font-size: .68rem; background: var(--bg); border: 1px solid var(--line);
              border-radius: 2px; padding: .05rem .45rem; color: var(--ink-soft); margin-left: .35rem; }

/* Full timeline page */
.em-page { max-width: 820px; }
.em-thread { display: flex; flex-direction: column; gap: .5rem; }
.em-item { background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden; }
.em-item[open] { box-shadow: 0 1px 3px rgba(0, 0, 0, .05); }
.em-item-head { list-style: none; cursor: pointer; display: flex; gap: .7rem; align-items: flex-start; padding: .7rem .85rem; }
.em-item-head::-webkit-details-marker { display: none; }
.em-avatar { flex: none; width: 2rem; height: 2rem; border-radius: 50%; background: var(--bg); border: 1px solid var(--line); display: grid; place-items: center; font-weight: 600; font-size: .85rem; color: var(--ink-soft); }
.em-out .em-avatar { background: var(--gold); color: #fff; border-color: var(--gold); }
.em-hmain { min-width: 0; flex: 1; }
.em-hrow { display: flex; justify-content: space-between; align-items: baseline; gap: .5rem; }
.em-from { font-weight: 600; color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.em-date { color: var(--muted); font-size: .78rem; white-space: nowrap; flex: none; }
.em-subj { display: block; font-size: .9rem; color: var(--ink-soft); margin-top: .1rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.em-snip { display: block; font-size: .8rem; margin-top: .15rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.em-item[open] .em-snip { display: none; }
.em-body { padding: .3rem .85rem 1rem; border-top: 1px solid var(--line); }
.em-meta { padding: .5rem 0; }
.em-text, .em-html { font-size: .83rem; line-height: 1.55; color: var(--ink); word-break: break-word; }
.em-html a, .em-text a { color: var(--gold); text-decoration: underline; }
.em-html img { display: none; }                 /* belt-and-braces: never show images */
/* Now that the sender's own HTML is what gets rendered (email_render_body), their
   markup lands here — headings, lists, tables and layout wrappers a mail client
   sized for a 600px column. The sanitiser has already taken every width, colour
   and font off it, so these are the whole of its appearance: keep it inside the
   panel, and keep it looking like the rest of the app rather than like an email. */
.em-html table { max-width: 100%; border-collapse: collapse; }
.em-html td, .em-html th { padding: 0 .35rem; text-align: left; vertical-align: top; }
.em-html h1, .em-html h2, .em-html h3, .em-html h4 { font-size: .95rem; font-weight: 600; margin: .8rem 0 .3rem; color: var(--ink); }
.em-html ul, .em-html ol { margin: .4rem 0; padding-left: 1.25rem; }
.em-html li { margin: .12rem 0; }
.em-html p { margin: .45rem 0; }
.em-html blockquote { margin: .5rem 0; padding-left: .7rem; border-left: 2px solid var(--line); color: var(--ink-soft); }
.em-html pre { white-space: pre-wrap; word-break: break-word; }
.em-html hr { border: 0; border-top: 1px solid var(--line); margin: .8rem 0; }
.em-privacy-note { margin-top: 1rem; }

@media (max-width: 620px) {
    .em-item-head { padding: .6rem; gap: .55rem; }
    .em-avatar { width: 1.7rem; height: 1.7rem; font-size: .78rem; }
    .em-from, .em-subj { font-size: .85rem; }
}

/* ============================================================
   EMAIL LIGHTBOX — a hovering panel that opens one email in
   place (from the dashboard mail strip, the inbox, or a client's
   Email panel) without leaving the current page. Trigger: any
   link with data-em-lb. Managed by email-lightbox.js; the card
   body reuses the .em-* styles above.
   ============================================================ */
.em-lb { position: fixed; inset: 0; z-index: 950; display: none; }
.em-lb.open { display: block; }
body.em-lb-lock { overflow: hidden; }
.em-lb-scrim { position: absolute; inset: 0; background: rgba(16, 17, 20, .55); backdrop-filter: blur(2px); }
.em-lb-panel { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
    width: min(760px, calc(100vw - 2.5rem)); max-height: calc(100vh - 3rem);
    background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius);
    box-shadow: 0 24px 60px rgba(0, 0, 0, .35); display: flex; flex-direction: column;
    overflow: hidden; animation: emLbIn .14s ease-out; }
@keyframes emLbIn { from { opacity: 0; transform: translate(-50%, -46%); } to { opacity: 1; transform: translate(-50%, -50%); } }
.em-lb-x { position: absolute; top: .45rem; right: .5rem; z-index: 2; width: 1.9rem; height: 1.9rem;
    border: 0; background: transparent; color: var(--muted); font-size: 1.5rem; line-height: 1;
    cursor: pointer; border-radius: 6px; }
.em-lb-x:hover { background: var(--bg); color: var(--ink); }
.em-lb-body { overflow-y: auto; padding: 1.15rem 1.3rem 1.35rem; }
.em-lb-loading, .em-lb-gone { padding: 2.4rem 0; text-align: center; }
/* The opened-email header, shown only inside the lightbox (the full timeline
   already carries this in its <summary>). */
.em-lb-head { margin: 0 2rem .55rem 0; padding-bottom: .55rem; border-bottom: 1px solid var(--line); }
.em-lb-from { font-weight: 600; color: var(--ink); }
.em-lb-date { color: var(--muted); font-size: .78rem; margin-left: .5rem; }
.em-lb-subj { display: block; font-size: 1rem; font-weight: 600; color: var(--ink); margin-top: .2rem; }
.em-lb .em-actions--top { margin-top: 0; }
@media (max-width: 620px) {
    .em-lb-panel { top: 0; left: 0; transform: none; width: 100vw; max-height: 100vh; height: 100vh;
        border: 0; border-radius: 0; animation: emLbInM .16s ease-out; }
    @keyframes emLbInM { from { opacity: 0; transform: translateY(14px); } to { opacity: 1; transform: none; } }
    .em-lb-body { padding: 1rem; }
}
@media (prefers-reduced-motion: reduce) { .em-lb-panel { animation: none; } }

/* ---- The compose window -------------------------------------------------
   The same panel one layer up, for writing rather than reading. Wider, because
   the composer carries a wording rail beside the message and asks for 700px of
   its own container before it will show one — at the reading panel's width it
   would drop the rail below Send on every reply. Above .em-lb so it stacks over
   the message being answered rather than replacing it. */
.em-cw { z-index: 960; }
.em-cw .em-lb-panel { width: min(940px, calc(100vw - 2.5rem)); }
.em-cw-title { flex: none; margin: 0 2.2rem .1rem 0; padding: .9rem 1.3rem .55rem;
    border-bottom: 1px solid var(--line); font-size: .95rem; font-weight: 600; color: var(--ink); }
.em-cw-title:empty { display: none; }
/* Inside the window the composer IS the window — its own frame would be a second
   border a few pixels inside the first, and its slide-in would fight the panel's. */
.em-cw .em-lb-body { padding: .9rem 1.3rem 0; }
.em-cw .em-compose { margin-top: 0; padding: 0; border: 0; background: none; animation: none; }
/* Shorter than the inline composer's: here the To/Cc/Subject rows, the people row,
   the toolbar and the Send row all share one capped panel, and an editor sized for
   a whole page pushes Send off the bottom of it. */
.em-cw .em-compose-editor { min-height: clamp(10rem, 26vh, 17rem); max-height: 38vh; }
/* Send rides along the bottom of the window rather than sitting at the end of a
   scroll. On a short screen, or under a long recipient list, the form is taller
   than the panel — and a Send button you have to go looking for is the same bug
   this window was built to fix, one level down. */
.em-cw .em-compose-foot { position: sticky; bottom: 0; z-index: 1;
    padding: .55rem 0 .9rem; background: var(--panel); box-shadow: 0 -1px 0 var(--line); }
@media (max-width: 620px) {
    /* The phone rule above takes the reading panel full-screen, but it is written
       on .em-lb-panel alone — which the width above outranks. Say it again here or
       the compose window sits at left:0 with a 2.5rem strip of page beside it. */
    .em-cw .em-lb-panel { width: 100vw; }
    .em-cw-title { padding: .85rem 1rem .5rem; font-size: .9rem; }
    .em-cw .em-lb-body { padding: .85rem 1rem 0; }
}

/* ============================================================
   MEETING CARD  —  one calendar invite, opened.
   ------------------------------------------------------------
   Rendered by app/partials/meeting_card.php into two frames: the
   lightbox above (from a dashboard row, the calendar rail, or a
   client's Ahead list) and its own page at /calendar/event/{id}.
   Prefixed .mtg-* — .em-* belongs to mail and .cal-* to the month
   grid, and this is neither.

   The shape is deliberately the opposite of the row it opens from:
   the row is one line that has to line up with fifty others, and
   this is the one place the meeting gets to spread out.
   ============================================================ */

/* --- The panel head: when first, then what, then who --------------------
   The time leads because that's the question you clicked with. */
.mtg-head { margin: 0 2rem .7rem 0; padding-bottom: .6rem; border-bottom: 1px solid var(--line); }
.mtg-when { display: block; font-family: var(--mono); font-size: .74rem; letter-spacing: .04em;
    text-transform: uppercase; color: var(--accent); }
.mtg-len { margin-left: .5rem; color: var(--muted); text-transform: none; letter-spacing: 0; }
.mtg-title { margin: .18rem 0 0; font-size: 1.08rem; font-weight: 600; color: var(--ink); line-height: 1.25; }
.mtg-client { display: inline-block; margin-top: .22rem; font-size: .8rem; color: var(--muted); }
.mtg-client:hover { color: var(--accent); }
.mtg-flags { display: flex; gap: .4rem; margin-top: .45rem; }
.mtg-count, .mtg-tent { font-family: var(--mono); font-size: .58rem; letter-spacing: .08em;
    text-transform: uppercase; font-weight: 600; padding: .1rem .45rem; border-radius: 2px;
    color: var(--muted); border: 1px solid var(--line-strong); }
/* Starting now, or already running: the one state on this card worth a colour. */
.mtg-count.is-live { color: var(--accent); border-color: color-mix(in srgb, var(--accent) 38%, transparent);
    background: color-mix(in srgb, var(--accent) 12%, transparent); }
.mtg-tent { color: var(--ink-soft); }

/* --- The controls ------------------------------------------------------- */
.mtg-acts { display: flex; flex-wrap: wrap; gap: .4rem; margin: 0 0 .9rem; }

/* --- The facts: a two-column list you scan, not read -------------------- */
.mtg-facts { display: grid; grid-template-columns: 6.2rem 1fr; gap: .38rem .9rem;
    margin: 0 0 .9rem; padding: .75rem 0 0; border-top: 1px solid var(--line); font-size: .84rem; }
.mtg-facts dt { font-family: var(--mono); font-size: .62rem; letter-spacing: .08em;
    text-transform: uppercase; color: var(--muted); padding-top: .16rem; }
.mtg-facts dd { margin: 0; color: var(--ink); min-width: 0; overflow-wrap: anywhere; }
.mtg-n { font-family: var(--mono); color: var(--ink-soft); }
.mtg-guests { display: flex; flex-wrap: wrap; gap: .3rem .55rem; }
.mtg-guest { display: inline-flex; align-items: baseline; gap: .3rem; }
.mtg-rsvp { font-family: var(--mono); font-size: .56rem; letter-spacing: .06em; text-transform: uppercase;
    font-style: normal; color: var(--muted); border: 1px solid var(--line-strong);
    border-radius: 2px; padding: 0 .24rem; }
/* Only the two answers that change what you do get a colour. "Maybe" and
   silence are the same fact — nobody has committed — so they stay grey. */
.mtg-rsvp--accepted { color: var(--ok); border-color: color-mix(in srgb, var(--ok) 35%, transparent); }
.mtg-rsvp--declined { color: var(--danger); border-color: color-mix(in srgb, var(--danger) 35%, transparent); }
/* The tail of a long guest list. A <details> so it needs no script.
   flex-basis:100% rather than display:contents — contents would let the names
   share the row above, but Firefox has never reliably hidden a display:contents
   <details> when it's closed, and a "+9 more" that shows all nine is worse than
   one that starts on its own line. */
.mtg-more { flex: 1 0 100%; margin-top: .1rem; }
.mtg-more[open] { display: flex; flex-wrap: wrap; gap: .3rem .55rem; }
.mtg-more[open] > summary { flex: 1 0 100%; }
.mtg-more > summary { cursor: pointer; font-family: var(--mono); font-size: .62rem;
    letter-spacing: .06em; text-transform: uppercase; color: var(--muted); list-style: none; }
.mtg-more > summary::-webkit-details-marker { display: none; }
.mtg-more > summary:hover { color: var(--accent); }
.mtg-more[open] > summary { color: var(--ink-soft); }

/* --- The invite body ----------------------------------------------------
   Agendas and call sheets get pasted into these wholesale, so it scrolls
   inside the card rather than pushing the controls off the top. */
.mtg-body { font-size: .86rem; line-height: 1.55; color: var(--ink);
    max-height: 22rem; overflow-y: auto; padding-top: .75rem; border-top: 1px solid var(--line); }
.mtg-body p { margin: 0 0 .6rem; }
.mtg-body ul, .mtg-body ol { margin: 0 0 .6rem; padding-left: 1.2rem; }
.mtg-body a { color: var(--accent); text-decoration: underline; text-underline-offset: 2px;
    overflow-wrap: anywhere; }
.mtg-body table { border-collapse: collapse; }
.mtg-body td, .mtg-body th { border: 1px solid var(--line); padding: .2rem .4rem; }
.mtg-note { padding-top: .75rem; border-top: 1px solid var(--line); }

/* --- The standalone page ------------------------------------------------ */
.mtg-page { max-width: 760px; }
.mtg-panel { padding: 1.1rem 1.2rem 1.25rem; }
.mtg-source { margin-top: .8rem; }
/* --- Phone --------------------------------------------------------------
   Kept here rather than in the consolidated MOBILE pass above: that block is
   only last in the file for the sections written before it, and at equal
   specificity a later rule wins. A .mtg-* override up there would be undone
   by this very section. */
@media (max-width: 768px) {
    /* Facts stack rather than keeping a 6rem label column that would leave
       three words per line beside it. */
    .mtg-facts { grid-template-columns: 1fr; gap: .1rem; }
    .mtg-facts dt { padding-top: .5rem; }
    .mtg-facts dt:first-child { padding-top: 0; }
    /* The body loses its own scroll — on a full-height sheet the panel is
       already the scroller, and two nested scrollers is one too many. */
    .mtg-body { max-height: none; }
    .mtg-acts .btn { flex: 1 1 auto; }
    .mtg-title { font-size: 1rem; }
}

/* --- Compose / reply -----------------------------------------------------
   The reply / new-message form, loaded as a fragment into a .em-compose-slot
   under an email (timeline or lightbox) or a client's Email page. */
.em-compose-slot:empty { display: none; }
.em-compose { margin-top: .9rem; padding: .9rem; border: 1px solid var(--line);
    border-radius: var(--radius); background: var(--wash); animation: emComposeIn .14s ease-out;
    /* The composer is dropped into four different columns of four different
       widths (inbox, client panel, lightbox, draft review), so the rail decides
       whether it fits by measuring THIS box rather than the window. */
    container-type: inline-size; }
@keyframes emComposeIn { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: none; } }

/* Message left, wording right. One column until the box is wide enough to
   carry both — under that the rail drops below the Send row, still usable,
   just no longer beside the writing. */
.em-compose-grid { display: grid; grid-template-columns: minmax(0, 1fr); gap: .9rem; }
@container (min-width: 700px) {
    .em-compose.has-rail .em-compose-grid {
        grid-template-columns: minmax(0, 1fr) 15rem; align-items: start; }
}
.em-compose-main { min-width: 0; }
.em-compose-row { display: flex; align-items: center; gap: .6rem; margin-bottom: .5rem; }
.em-compose-lbl { flex: none; width: 3.2rem; color: var(--muted); font-size: .8rem; text-align: right; }
.em-compose-inp { flex: 1; min-width: 0; padding: .4rem .55rem; border: 1px solid var(--line);
    border-radius: 6px; background: var(--panel); color: var(--ink); font-size: .88rem; }
.em-compose-inp:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); }
.em-compose-all { flex: none; font-size: .78rem; color: var(--accent); text-decoration: none;
    border: 1px solid var(--line); border-radius: 2px; padding: .12rem .6rem; }
.em-compose-all:hover { background: var(--panel); }
/* ---- Formatting toolbar --------------------------------------------------
   One joined strip sitting on top of the editor, the way every editor's is:
   the buttons belong to the box below them, so they share its top edge rather
   than floating above it as ten separate tiles. */
.em-compose-toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: .15rem;
    margin: .3rem 0 0; padding: .25rem .3rem; border: 1px solid var(--line);
    border-bottom: 0; border-radius: var(--radius) var(--radius) 0 0; background: var(--panel); }
.em-tb-btn { border: 1px solid transparent; background: none; color: var(--ink-soft);
    border-radius: var(--radius); min-width: 1.75rem; padding: .2rem .4rem;
    font-size: .82rem; line-height: 1.3; cursor: pointer; }
.em-tb-btn:hover { background: var(--wash); color: var(--ink); }
/* Lit when the caret is standing in it — the button answers "is this already
   bold?" so you don't have to retype a word to find out. */
.em-tb-btn.is-on { background: var(--ink); color: var(--panel); border-color: var(--ink); }
.em-tb-btn.is-on:hover { background: var(--ink); color: var(--panel); }
.em-tb-btn sub { font-size: .62em; }
.em-tb-sep { width: 1px; align-self: stretch; margin: .1rem .3rem; background: var(--line); }

/* The link bar — borrows the row under the toolbar while it's open. */
/* A class rule outranks the browser's [hidden]{display:none}, so say it here or
   the link bar sits open under the toolbar in every composer, on every reply. */
.em-link-bar[hidden] { display: none; }
.em-link-bar { display: flex; align-items: center; gap: .4rem; padding: .35rem .4rem;
    border: 1px solid var(--line); border-bottom: 0; background: var(--wash); }
.em-link-lbl { flex: none; font-size: .7rem; text-transform: uppercase;
    letter-spacing: .06em; color: var(--muted); }
.em-link-url { flex: 1; min-width: 0; padding: .3rem .5rem; border: 1px solid var(--line);
    border-radius: var(--radius); background: var(--panel); color: var(--ink);
    font: inherit; font-size: .84rem; }
.em-link-url:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); }
/* Joined to the toolbar above it: one box with a lid, not two stacked panels.

   The height is the writing area, and it used to be 8.5rem — which sounds
   reasonable until you remember the box opens with your signature already in
   it. Two of those lines were spent before you typed anything, so a real
   message was written through a three-line slot with the rest scrolled out of
   sight. It scales with the window now: a third of the screen on a laptop,
   capped so it doesn't become a page of its own on a large monitor and never
   so short that the signature is most of it. */
.em-compose-editor { min-height: clamp(18rem, 40vh, 30rem); max-height: 60vh;
    overflow-y: auto; padding: .6rem .7rem;
    border: 1px solid var(--line); border-radius: 0 0 var(--radius) var(--radius);
    background: var(--panel); color: var(--ink);
    font-size: .9rem; line-height: 1.5; }
.em-compose-editor:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); }
/* What you type reads the way it will arrive: the marks the send path keeps,
   shown the way the recipient's client will show them. */
.em-compose-editor p { margin: 0 0 .65rem; }
.em-compose-editor ul, .em-compose-editor ol { margin: .2rem 0 .65rem; padding-left: 1.4rem; }
.em-compose-editor li { margin: .15rem 0; }
.em-compose-editor a { color: var(--accent); }
.em-compose-editor blockquote { margin: .5rem 0 0; padding-left: .8rem; border-left: 2px solid var(--line); color: var(--muted); }
.em-compose-editor .em-quote-intro { color: var(--muted); font-size: .82rem; margin-top: .6rem; }
.em-compose-editor .em-sig { color: var(--ink-soft); }
.em-compose-foot { display: flex; align-items: center; gap: .6rem; margin-top: .6rem; }
.em-compose-attach { cursor: pointer; font-size: .82rem; color: var(--muted);
    border: 1px solid var(--line); border-radius: 6px; padding: .3rem .6rem; background: var(--panel); }
.em-compose-attach:hover { border-color: var(--accent); color: var(--ink); }
.em-compose-files { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 40%; }
.em-compose-spacer { flex: 1; }

/* ---- Who else is at this company -----------------------------------------
   A row of people under the address fields. Hairline chips rather than filled
   buttons: this is a list you read past most of the time, and a row of solid
   controls above the subject line would out-shout the message.

   Prefixed .em-ppl-* — .em-side, .em-rail and .chip are all taken by other
   components, and app.css has form: a generic name here restyles something
   three screens away. */
.em-ppl-slot:empty { display: none; }
.em-ppl { margin: .1rem 0 .6rem; padding-left: 3.8rem; }   /* the label column (3.2rem) + its gap, so the row starts where the addresses do */
.em-ppl-h { display: flex; align-items: center; gap: .6rem; margin-bottom: .3rem;
    font-size: .66rem; text-transform: uppercase; letter-spacing: .07em; color: var(--muted); }
.em-ppl-find { flex: 0 1 11rem; margin-left: auto; padding: .18rem .4rem;
    border: 1px solid var(--line); border-radius: var(--radius);
    background: var(--bg); color: var(--ink); font: inherit; font-size: .74rem;
    text-transform: none; letter-spacing: 0; -webkit-appearance: none; appearance: none; }
.em-ppl-find:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); }
.em-ppl-row { display: flex; flex-wrap: wrap; gap: .3rem; }
.em-ppl-p { display: inline-flex; align-items: stretch;
    border: 1px solid var(--line); border-radius: var(--radius); background: var(--panel);
    overflow: hidden; max-width: 100%; }
.em-ppl-p:hover { border-color: var(--accent); }
.em-ppl-add { display: flex; flex-direction: column; align-items: flex-start; gap: .05rem;
    cursor: pointer; border: 0; background: none; padding: .25rem .45rem; min-width: 0;
    font: inherit; color: var(--ink); text-align: left; }
.em-ppl-add:hover { background: var(--wash); }
.em-ppl-add:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.em-ppl-n { font-size: .78rem; line-height: 1.2; white-space: nowrap; overflow: hidden;
    text-overflow: ellipsis; max-width: 13rem; }
.em-ppl-star { font-style: normal; font-size: .66rem; color: var(--accent); margin-left: .2rem; }
.em-ppl-r { font-size: .68rem; line-height: 1.2; color: var(--muted); white-space: nowrap;
    overflow: hidden; text-overflow: ellipsis; max-width: 13rem; }
/* Cc is the second choice, so it's a hairline away rather than a second button. */
.em-ppl-cc { flex: none; cursor: pointer; border: 0; border-left: 1px solid var(--line);
    background: none; padding: 0 .4rem; font: inherit; font-size: .66rem;
    letter-spacing: .04em; color: var(--muted); }
.em-ppl-cc:hover { background: var(--wash); color: var(--accent); }
.em-ppl-cc:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
/* Found in the mail rather than filed on purpose — a weaker claim, drawn weaker. */
.em-ppl-p.is-loose { border-style: dashed; }
/* Already on the message: still there, no longer asking to be pressed. */
.em-ppl-p.is-on { border-color: var(--line); background: var(--wash); opacity: .55; }
.em-ppl-p.is-on .em-ppl-add::before { content: '✓ '; color: var(--accent); }
.em-ppl-none { margin: .3rem 0 0; }

/* ---- Send later ----------------------------------------------------------
   A fold in the compose foot, opening upward. Upward because the composer
   often sits at the bottom of a long thread, where a panel opening downward
   would land off the screen and take a scroll to reach — and the whole point
   of the thing is that it's two presses. */
.em-later { position: relative; }
.em-later-sum { cursor: pointer; list-style: none; font-size: .82rem; color: var(--muted);
    border: 1px solid var(--line); border-radius: 6px; padding: .3rem .6rem;
    background: var(--panel); white-space: nowrap; }
.em-later-sum::-webkit-details-marker { display: none; }
.em-later-sum:hover { border-color: var(--accent); color: var(--ink); }
.em-later[open] .em-later-sum { border-color: var(--accent); color: var(--ink); }
.em-later-pop { position: absolute; bottom: calc(100% + .4rem); right: 0; z-index: 30;
    min-width: 15rem; display: flex; flex-direction: column; gap: .25rem;
    border: 1px solid var(--line-strong); border-radius: var(--radius);
    background: var(--panel); padding: .5rem; box-shadow: 0 8px 24px rgba(20,24,30,.14); }
.em-later-h { font-size: .64rem; text-transform: uppercase; letter-spacing: .08em;
    color: var(--muted); padding: 0 .15rem .1rem; }
.em-later-opt { display: block; width: 100%; text-align: left; cursor: pointer;
    border: 0; border-top: 1px solid var(--line); background: none;
    padding: .38rem .3rem; font: inherit; font-size: .82rem; color: var(--ink); }
.em-later-opt { display: flex; align-items: baseline; gap: .5rem; }
/* The clock time beside a relative label, pushed right so the column of times
   lines up and can be read on its own. */
.em-later-at { margin-left: auto; font-family: var(--mono); font-size: .74rem;
    color: var(--muted); font-variant-numeric: tabular-nums; }
.em-later-opt:hover { background: var(--wash); color: var(--accent); }
.em-later-opt:hover .em-later-at { color: var(--accent); }
.em-later-opt:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.em-later-pick { display: flex; flex-direction: column; gap: .2rem; margin-top: .35rem;
    padding-top: .45rem; border-top: 1px solid var(--line); }
.em-later-pick span { font-size: .7rem; color: var(--muted); }
.em-later-pick input { width: 100%; padding: .3rem .4rem; border: 1px solid var(--line);
    border-radius: var(--radius); background: var(--bg); color: var(--ink);
    font: inherit; font-size: .8rem; }
.em-later-pick input:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); }
.em-later-set { margin-top: .1rem; }
/* Attached files and a timer don't go together yet. The fold says so in place of
   the choices, rather than offering buttons that would refuse. */
.em-compose.has-files .em-later-opt,
.em-compose.has-files .em-later-pick,
.em-compose.has-files .em-later-set { display: none; }
.em-compose.has-files .em-later-sum { color: var(--muted); border-style: dashed; }
/* The two headings on the Drafts page, once it has more than one kind of row. */
.em-later-hd { font-size: .68rem; text-transform: uppercase; letter-spacing: .08em;
    color: var(--muted); font-weight: 500; margin: 1.4rem 0 .5rem; }
.em-later-hd:first-of-type { margin-top: 0; }

/* ---- The wording rail ----------------------------------------------------
   Your standing paragraphs beside the message rather than behind a dropdown.
   Quiet by design: it sits next to the thing you're actually writing, so it
   reads as reference (hairline, no fill) and only the row under the cursor
   comes forward. */
.em-compose-rail { min-width: 0; display: flex; flex-direction: column; gap: .4rem;
    border: 1px solid var(--line); border-radius: var(--radius);
    background: var(--panel); padding: .5rem; }
.em-rail-head { display: flex; align-items: baseline; gap: .5rem; }
.em-rail-t { flex: 1; font-size: .68rem; text-transform: uppercase; letter-spacing: .08em;
    color: var(--muted); }
.em-rail-edit { font-size: .72rem; color: var(--muted); text-decoration: none; }
.em-rail-edit:hover { color: var(--accent); }
.em-rail-find { width: 100%; padding: .3rem .45rem; border: 1px solid var(--line);
    border-radius: var(--radius); background: var(--bg); color: var(--ink);
    font: inherit; font-size: .8rem; -webkit-appearance: none; appearance: none; }
.em-rail-find:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); }
/* Tall enough to browse, capped so the rail never outgrows the message and
   pushes Send off the bottom of the screen. */
.em-rail-list { overflow-y: auto; max-height: 24rem; margin: 0 -.15rem; padding: 0 .15rem; }
.em-rail-g { font-size: .64rem; text-transform: uppercase; letter-spacing: .07em;
    color: var(--muted); padding: .55rem .15rem .2rem; }
.em-rail-group:first-child .em-rail-g { padding-top: .1rem; }
.em-rail-item { display: block; width: 100%; text-align: left; cursor: pointer;
    border: 0; border-top: 1px solid var(--line); background: none;
    padding: .4rem .3rem; font: inherit; color: var(--ink); }
.em-rail-item:hover { background: var(--wash); }
.em-rail-item:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
/* A beat of ember on the row you just used: with a dozen similar labels it is
   otherwise genuinely hard to remember which one you clicked. */
.em-rail-item.just-used { background: var(--glow); }
.em-rail-lbl { display: block; font-size: .8rem; line-height: 1.25; }
.em-rail-prev { display: block; margin-top: .12rem; font-size: .72rem; line-height: 1.35;
    color: var(--muted); overflow: hidden; display: -webkit-box;
    -webkit-line-clamp: 2; -webkit-box-orient: vertical; }
.em-rail-empty { margin: .5rem .15rem; }
.em-rail-foot:has(.em-rail-undo[hidden]) { display: none; }
.em-rail-undo { width: 100%; cursor: pointer; border: 1px solid var(--line);
    border-radius: var(--radius); background: var(--bg); color: var(--ink-soft);
    padding: .3rem; font: inherit; font-size: .74rem; }
.em-rail-undo:hover { border-color: var(--accent); color: var(--ink); }

@media (max-width: 620px) {
    .em-compose-lbl { width: 2.4rem; }
    .em-ppl { padding-left: 3rem; }   /* the narrower label column here */
    /* Roomier than the desktop chip, which is the opposite of the rule
       everywhere else on a phone — but a one-line name came out 25px tall,
       and a control you have to aim at is not dense, it is a miss. */
    .em-ppl-add { padding: .42rem .55rem; }
    .em-ppl-cc { padding: 0 .65rem; }
    .em-compose-files { max-width: 30%; }
    /* Shorter than the desktop box, and deliberately: with the keyboard up, half
       the phone is gone, and an editor sized for a laptop would leave Send below
       the fold on a message you've finished writing. Still comfortably more than
       the signature. */
    .em-compose-editor { min-height: 11rem; max-height: 40vh; }
    /* On a phone the rail is under the message, so it doesn't need to be a
       tall list of everything — it needs to be findable and short. */
    .em-rail-list { max-height: 13rem; }
}

/* ============================================================
   THE WORKSPACE CARD  (client + project detail)
   Everything you keep on a client — Notes / Documents / Files /
   Deliveries — behind one switcher, with the Favorites shortlist
   riding on top of the same card. Each panel renders "embedded"
   (no panel chrome); .dv-tabs supplies the switcher.
   See client_detail.php + the *_panel embedded mode.

   The hierarchy inside the card, loudest first:
     section head (ruled, ink)  →  tabs (ember underline on the
     live one)  →  favourites strip (mono micro-cap label, quiet
     rows)  →  the rows themselves.
   ============================================================ */
.cl-work .dv-tabs { margin-bottom: 1rem; }
.cl-work .dv-pane > .kb-ctx,
.cl-work .dv-pane > .cdocs,
.cl-work .dv-pane > .cdel,
.cl-work .dv-pane > .cc,
.cl-work .dv-pane > .vault { margin: 0; }
/* Belt and braces: nothing inside a tab pane draws its own card. A pane already
   sits on the card the tabs belong to, and a box inside a box was the thing that
   made these pages feel like a pile of unrelated widgets. */
.cl-work .dv-pane .panel { background: none; border: 0; box-shadow: none; padding: 0; }
.kb-ctx-foot { margin-top: .9rem; padding-top: .7rem; border-top: 1px solid var(--line); }
/* First element inside a pane shouldn't add its own top gap under the tabs. */
.cl-work .dv-pane > * > :first-child { margin-top: 0; }
/* Five tabs: let the strip scroll sideways rather than wrap into two rows. */
.cl-work-tabs { overflow-x: auto; scrollbar-width: none; }
.cl-work-tabs::-webkit-scrollbar { display: none; }
.cl-work-tabs .dv-tab { flex: none; }

/* Favourites shortlist embedded at the top of the workspace card, so a starred
   note, mail or document is always one glance away from the tabs it points at. */
.cl-work .cfav-embed { border-left: 0; margin-bottom: 1.1rem; padding-bottom: .85rem; border-bottom: 1px solid var(--line); }
.cl-work .cfav-embed-head { font-family: var(--mono); font-size: .62rem; text-transform: uppercase;
    letter-spacing: .1em; font-weight: 600; color: var(--muted); margin-bottom: .35rem; }
.cl-work .cfav-embed-head .cfav-star { color: var(--gold); }

/* ============================================================
   SHEETS + COLOURED SECTION HEADS  (client + project detail)

   Two deliberate exceptions to "everything sits on the ground":
   the Workspace card and the billing sections lift onto a white
   sheet. Both are working surfaces rather than read-only lists —
   Workspace holds a tab switcher and editors, billing holds the
   controls that raise invoices — and a surface is what says "you
   act inside here". The plain lists (Projects, Email, Booked
   days) stay on the ground, so the page still reads as two
   levels, not a pile of mismatched cards.

   And every section head takes a colour, from the same palette
   the document kinds use, so the landmarks carry down the page:
       Booking    green — where the job stands
       Projects   blue  — the work
       Invoices   ember — the money (matches the hero figure)
       Workspace  teal  — the things you keep
       Email      slate — the correspondence
   The head's rule takes a wash of the same hue, so the whole
   header reads as one coloured band, not a tinted word.
   ============================================================ */
.client-detail .cd-main > .panel,
.project-detail .cd-main > .panel,
.project-detail .cd-main > .piece {
    --sheet-pad: 1.25rem;
    background: var(--panel); border: 1px solid var(--line); box-shadow: none;
    padding: 1.15rem var(--sheet-pad) 1.3rem;
}
/* A split job's pieces are sheets too, so a merged job's column doesn't end in a
   run of naked disclosures below the sections above it. The piece supplies its
   own padding on the summary row, so the card itself carries none. */
.project-detail .cd-main > .piece { padding: 0; }
.project-detail .cd-main > .piece > .piece-head { padding: .7rem var(--sheet-pad) .7rem calc(var(--sheet-pad) - .45rem); }
.project-detail .cd-main > .piece > .piece-body { padding: .1rem var(--sheet-pad) 1rem calc(var(--sheet-pad) + .9rem); margin-left: var(--sheet-pad); }

/* On a sheet the head's rule is an internal divider, so pull it to the card's
   edges — a rule that stops short of the border looks like a mistake. */
.client-detail .cd-main > .panel > .panel-head,
.client-detail .cd-main > .panel > h2,
.project-detail .cd-main > .panel > .panel-head,
.project-detail .cd-main > .panel > h2 {
    margin-left: calc(var(--sheet-pad) * -1); margin-right: calc(var(--sheet-pad) * -1);
    padding-left: var(--sheet-pad); padding-right: var(--sheet-pad);
}

/* --- the head colours --- */
.client-detail .cd-main > .cl-projects  { --sec: #2F6FB0; }
.client-detail .cd-main > .cl-invoices  { --sec: var(--accent); }
.client-detail .cd-main > .cl-work,
.project-detail .cd-main > .cl-work     { --sec: #0F7285; }
.client-detail .cd-main > .cl-email     { --sec: #5B6470; }
.client-detail .cd-main > .cl-ahead     { --sec: #8E3A7E; }   /* what's committed */
.project-detail .cd-main > .ob-track    { --sec: var(--ok); }
.project-detail .cd-main > .pd-billing  { --sec: var(--accent); }
.project-detail .cd-main > .pd-days     { --sec: #2F6FB0; }

.client-detail .cd-main > .panel > .panel-head h2,
.client-detail .cd-main > .panel > h2,
.project-detail .cd-main > .panel > .panel-head h2,
.project-detail .cd-main > .panel > h2 { color: var(--sec, var(--ink)); }
.client-detail .cd-main > .panel > .panel-head,
.client-detail .cd-main > .panel > h2,
.project-detail .cd-main > .panel > .panel-head,
.project-detail .cd-main > .panel > h2 {
    border-bottom-color: color-mix(in srgb, var(--sec, var(--ink)) 55%, transparent);
}
/* The count keeps the section's hue but steps back from the title. */
.client-detail .cd-main > .panel > .panel-head h2 .muted,
.project-detail .cd-main > .panel > .panel-head h2 .muted {
    color: color-mix(in srgb, var(--sec, var(--ink)) 55%, var(--muted));
}

/* ---- ONE TYPE LADDER for the detail pages -----------------------------
   The section head used to land at .92rem — the same size as the row titles
   underneath it — so nothing looked like it contained anything. Four fixed
   steps now, loud to quiet:
       hero figure  2.15rem mono   (the money, once, at the top)
       section head 1.06rem        (Projects / Invoices / Workspace / Email)
       row title     .88rem        (a project, an invoice, a document)
       meta + micro-caps .74 / .62rem mono
   Written against `> .panel >` so it beats the generic .cd-main rule above. */
.client-detail .cd-main > .panel > .panel-head h2,
.client-detail .cd-main > .panel > h2,
.client-detail .cd-side > .panel > .panel-head h2,
.client-detail .cd-side > .panel > h2,
.project-detail .cd-main > .panel > .panel-head h2,
.project-detail .cd-main > .panel > h2,
.project-detail .cd-side > .panel > .panel-head h2,
.project-detail .cd-side > .panel > h2 {
    font-size: 1.06rem; font-weight: 600; letter-spacing: -.012em; line-height: 1.25;
}
/* A panel nested inside a split job's piece is a sub-section, so it sits one step
   under a top-level head — but still clearly above its own rows. Without this it
   fell back to .92rem and a piece's "Booked days" read smaller than the dates in
   it. */
.project-detail .piece-body .panel > h2,
.project-detail .piece-body .panel > .panel-head h2 { font-size: .96rem; font-weight: 600; }

/* The count beside a section title is data, not headline — mono, a size down. */
.client-detail .cd-main > .panel > .panel-head h2 .muted,
.client-detail .cd-side > .panel > .panel-head h2 .muted,
.project-detail .cd-main > .panel > .panel-head h2 .muted,
.project-detail .cd-side > .panel > .panel-head h2 .muted {
    font-family: var(--mono); font-size: .68em; font-weight: 500;
}
/* Tabs are a control inside a section, so they sit below its head, not level. */
.cl-work-tabs .dv-tab { font-size: .86rem; }

/* Row actions and row glyphs stop shouting. Ember is the money/attention colour;
   a column of "Mark done · Open · delete" links and a pencil on every line was
   spending it on housekeeping. They rest in soft ink and take the accent only on
   hover — so within a row, the NAME is the loudest thing again. */
.client-detail .cdoc-actions .linkbtn-sm,
.client-detail .cdoc-open { color: var(--ink-soft); font-size: .76rem; }
.client-detail .cdoc-actions .linkbtn-sm:hover,
.client-detail .cdoc-open:hover { color: var(--accent); }
.client-detail .cdoc-actions .linkbtn-sm.danger { color: var(--muted); }
.client-detail .cdoc-actions .linkbtn-sm.danger:hover { color: var(--danger); }
/* Hovering a row firms up its kind tile rather than recolouring it — the row
   keeps its identity, it just steps forward. */
.client-detail .cdoc-row:hover .dk {
    background: color-mix(in srgb, var(--dk, var(--muted)) 18%, transparent);
    box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--dk, var(--muted)) 46%, transparent);
}

/* Deliveries live in the workspace card as one more tab, and reuse the document
   row (.cdoc-*) — a delivery is another thing you make and send. Only the state
   pills and the two make-one buttons are its own. */
.cdoc-pill--live  { background: color-mix(in srgb, var(--ok) 14%, var(--panel)); color: var(--ok); }
.cdoc-pill--draft { background: var(--wash); color: var(--muted); }
.cdoc-pill--exp   { background: var(--danger-bg); color: var(--danger); }
.cdel-new { display: flex; gap: .45rem; flex-wrap: wrap; margin-top: .9rem; }
.cdel-new form { margin: 0; }
.cdel-new .btn-sm { font-family: var(--mono); font-size: .68rem; letter-spacing: .04em;
    text-transform: uppercase; padding: .34rem .7rem; border: 1px solid var(--line-strong);
    background: var(--panel); color: var(--ink-soft); }
.cdel-new .btn-sm:hover { border-color: var(--ink); color: var(--ink); }

/* ============================================================
   Onboarding tracker (ob-)  — the computed pipeline stepper on
   the project page. Renders project_pipeline() (app/pipeline.php).
   Node states: done (green), now (ember + ring), wait (ember ring),
   upcoming (hollow). ob- prefixed to avoid generic-class collisions.
   ============================================================ */
.ob-head-r { display: inline-flex; align-items: center; gap: .7rem; flex-wrap: wrap; justify-content: flex-end; }
.ob-head .eyebrow { margin: 0; }
.ob-stage { font-family: var(--mono); font-size: .68rem; letter-spacing: .1em; text-transform: uppercase;
    font-weight: 600; padding: .18rem .55rem; border: 1px solid var(--line-strong); color: var(--ink-soft); background: var(--wash); }
.ob-stage--onboarding { color: var(--accent); border-color: color-mix(in srgb, var(--accent) 40%, var(--line));
    background: color-mix(in srgb, var(--accent) 10%, var(--panel)); }
.ob-stage--booked, .ob-stage--complete { color: var(--ok); border-color: color-mix(in srgb, var(--ok) 40%, var(--line));
    background: color-mix(in srgb, var(--ok) 10%, var(--panel)); }
/* Mail badges, matching the needs-bar categories they sit on. */
.ob-stage--triage { color: var(--c-in); border-color: color-mix(in srgb, var(--c-in) 40%, var(--line));
    background: color-mix(in srgb, var(--c-in) 10%, var(--panel)); }
.ob-stage--draft { color: var(--c-out); border-color: color-mix(in srgb, var(--c-out) 40%, var(--line));
    background: color-mix(in srgb, var(--c-out) 10%, var(--panel)); }
.ob-turn { font-size: .74rem; font-weight: 600; color: var(--muted); white-space: nowrap; }
.ob-turn--you { color: var(--accent); }

/* Said once, above the steps, when this chain speaks for several same-name
   bookings — so "all of it billed" isn't misread as "this week billed". */
.ob-jobnote { font-size: .78rem; color: var(--muted); line-height: 1.5; margin: 0 0 1rem; }
.ob-steps { list-style: none; margin: 0; padding: 0; }
.ob-step { display: flex; gap: .8rem; }
.ob-rail { display: flex; flex-direction: column; align-items: center; }
.ob-node { width: 14px; height: 14px; border-radius: 50%; border: 2px solid var(--line-strong);
    background: var(--panel); flex-shrink: 0; margin-top: .2rem; }
.ob-bar { width: 2px; flex: 1; min-height: 14px; background: var(--line); margin: 2px 0; }
.ob-step:last-child .ob-bar { display: none; }
/* Fills the step row rather than shrinking to its longest sentence. Prose doesn't
   care either way — it's left-aligned and already wraps — but the invoice ledger
   below is a grid, and a column that sizes to its content squeezed the dates into
   an ellipsis while leaving half the panel empty. */
.ob-lbl { flex: 1; min-width: 0; padding-bottom: 1.1rem; }
.ob-step:last-child .ob-lbl { padding-bottom: 0; }
.ob-lbl b { font-weight: 600; font-size: .9rem; }
.ob-sub { display: block; font-size: .78rem; color: var(--ink-soft); margin-top: .05rem; }

.ob-step.is-done .ob-node { background: var(--ok); border-color: var(--ok); }
.ob-step.is-done .ob-bar { background: color-mix(in srgb, var(--ok) 45%, var(--line)); }
.ob-step.is-now .ob-node { background: var(--accent); border-color: var(--accent);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 18%, transparent); }
.ob-step.is-now .ob-sub { color: var(--accent); }
.ob-step.is-wait .ob-node { background: var(--panel); border-color: var(--accent); }
.ob-step.is-upcoming b, .ob-step.is-upcoming .ob-sub { color: var(--muted); }

.ob-act { display: inline-block; margin-top: .5rem; }
.ob-linkact { display: inline-block; margin-top: .35rem; font-size: .78rem; font-weight: 600;
    color: var(--accent); text-decoration: none; }
.ob-linkact:hover { text-decoration: underline; }

/* The money step's ledger — one row per invoice on the job. Deliberately quiet:
   the step's own line is the summary and this is the working, so it reads as a
   hairline table rather than as four more things shouting. Numbers are mono and
   right-aligned so two invoices can be compared down the column. */
.ob-led { list-style: none; margin: .5rem 0 0; padding: 0;
    border-top: 1px solid var(--line); }
.ob-led-row { display: grid; grid-template-columns: 10px 3.4rem minmax(0, 1fr) auto;
    align-items: baseline; gap: .1rem .7rem; padding: .34rem 0;
    border-bottom: 1px solid var(--line); font-size: .76rem; }
/* One node per invoice, in the step rail's vocabulary at half the size: filled
   green once it has actually gone out, hollow while it's still yours. Without it
   a job part-billed reads as if nothing had left the building. */
.ob-led-dot { grid-row: span 2; width: 8px; height: 8px; border-radius: 50%;
    border: 1.5px solid var(--line-strong); background: var(--panel);
    align-self: center; justify-self: center; }
.ob-led-no { grid-row: span 2; font-family: var(--mono); font-weight: 600;
    color: var(--ink); text-decoration: none; align-self: center; }
.ob-led-no:hover { color: var(--accent); }
.ob-led-for { color: var(--ink-soft); min-width: 0; overflow: hidden;
    text-overflow: ellipsis; white-space: nowrap; }
/* Gross with its net stacked under it, right-aligned so the two figures read as
   one number and its remainder rather than as two competing amounts. */
.ob-led-amt { font-family: var(--mono); color: var(--ink); text-align: right;
    display: flex; flex-direction: column; align-items: flex-end; line-height: 1.35; }
.ob-led-amt i { font-style: normal; font-size: .7rem; color: var(--muted); }
/* The state sits under the piece it describes; its pay-day sits at the end of the
   same line, under the money it will pay — so a column of countdowns can be read
   straight down. */
.ob-led-say { grid-column: 3; color: var(--muted); font-size: .72rem; }
.ob-led-when { grid-column: 4; font-family: var(--mono); font-size: .7rem;
    color: var(--ink-soft); text-align: right; white-space: nowrap; }
.ob-led-when.is-late { color: var(--danger); font-weight: 600; }
.ob-led-row.is-paid  .ob-led-say { color: var(--ok); }
.ob-led-row.is-draft .ob-led-say { color: var(--accent); }
/* Sent isn't finished, but it is off your desk — the line stops being grey so
   the eye reads it as a step taken rather than another thing pending. */
.ob-led-row.is-sent  .ob-led-say { color: var(--ink-soft); }
/* Green the moment it leaves; the halo is reserved for money actually banked. */
.ob-led-row.is-sent .ob-led-dot,
.ob-led-row.is-paid .ob-led-dot { background: var(--ok); border-color: var(--ok); }
.ob-led-row.is-paid .ob-led-dot { box-shadow: 0 0 0 2.5px color-mix(in srgb, var(--ok) 22%, transparent); }
/* Still yours to send: your-move drafts take the accent ring, ones dated ahead
   stay the plain hollow grey — a scheduled invoice isn't a thing you owe. */
.ob-led-row.is-draft .ob-led-dot { border-color: var(--accent); }

/* ============================================================
   The timeline strip — what has been promised, in date order.
   Hairline rows, mono dates, the evidence printed under each one. The date
   column is fixed so a glance down it reads as a column rather than as prose.
   ============================================================ */
.tl-list { list-style: none; margin: 0; padding: 0; }
/* The date column is wide enough for a SPAN — "Sep 7 – Sep 14" — because a span
   wrapping onto two lines pushes its own "in 9 days" out of line with every
   single-day row above it, and the column stops reading as a column. */
.tl-row { display: grid; grid-template-columns: 7.7rem minmax(0, 1fr) auto;
    gap: .2rem .9rem; align-items: baseline;
    padding: .6rem 0; border-top: 1px solid var(--line); }
.tl-row:first-child { border-top: 0; }
.tl-when { display: flex; flex-direction: column; gap: .1rem; }
.tl-when b { font-family: var(--mono); font-size: .82rem; color: var(--ink); }
.tl-in { font-size: .7rem; color: var(--muted); }
/* Today or tomorrow. The only colour in the strip, and it is on the WHEN rather
   than the whole row: what changed is how much room is left, not what the thing is. */
.tl-row.is-soon .tl-when b,
.tl-row.is-soon .tl-in { color: var(--accent); }
.tl-what { min-width: 0; }
.tl-lbl { display: block; font-size: .88rem; color: var(--ink); }
/* Whose promise it is, leading the row — a thing you owe reads differently from
   a thing you are waiting on, and that is the first thing you want to know. */
.tl-who { font-weight: 600; margin-right: .3rem; }
.tl-kind { font-size: .68rem; text-transform: uppercase; letter-spacing: .06em;
    color: var(--muted); margin-left: .45rem; }
.tl-quote { display: block; margin-top: .15rem; font-size: .76rem; color: var(--muted);
    font-style: italic; }
.tl-acts { display: flex; gap: .1rem; align-items: center; }
.tl-act { background: none; border: 0; padding: .15rem .3rem; cursor: pointer;
    font: inherit; font-size: .82rem; line-height: 1; color: var(--muted);
    text-decoration: none; border-radius: 3px; }
.tl-act:hover { color: var(--accent); background: var(--wash); }
.tl-act--off:hover { color: var(--danger); }

/* A phone has no room for a date column beside the sentence, so the row stacks
   and the actions sit under it rather than squeezing the words to nothing. */
@media (max-width: 620px) {
    .tl-row { grid-template-columns: minmax(0, 1fr) auto; }
    .tl-when { flex-direction: row; gap: .45rem; align-items: baseline; }
    .tl-what { grid-column: 1 / -1; }
}

.ob-markdone { margin: .3rem 0 0; }
.ob-markdone-btn { background: none; border: 0; padding: 0; cursor: pointer; font: inherit;
    font-size: .74rem; color: var(--muted); text-decoration: underline; text-underline-offset: 2px; }
.ob-markdone-btn:hover { color: var(--ink-soft); }

/* ============================================================
   In-play page + the compact progress dot-strip (Phase 4).
   The dot-strip mirrors the vertical tracker's node states in
   miniature: done (green), now (ember + ring), wait (ember ring),
   upcoming (hollow). Reuses .ob-stage--* for the status pill.
   ============================================================ */
.obdots { display: inline-flex; gap: 4px; align-items: center; flex: none; }
.obdot { width: 8px; height: 8px; border-radius: 50%; background: var(--panel);
    border: 1.5px solid var(--line-strong); display: inline-block; flex: none; }
.obdot.is-done { background: var(--ok); border-color: var(--ok); }
.obdot.is-now { width: 10px; height: 10px; background: var(--accent); border-color: var(--accent);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 18%, transparent); }
.obdot.is-wait { background: var(--panel); border-color: var(--accent); }

.ip-grouph { font-family: var(--mono); font-size: .68rem; letter-spacing: .1em; text-transform: uppercase;
    font-weight: 600; margin: 1.6rem 0 .5rem; }
.ip-grouph.is-you { color: var(--accent); }
.ip-grouph.is-client { color: var(--muted); }
.ip-list { border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden; background: var(--panel); }
.ip-row { display: flex; align-items: center; gap: 1rem; padding: .7rem 1rem; text-decoration: none;
    color: var(--ink); flex-wrap: wrap; }
.ip-row:hover { background: var(--wash); }
.ip-who { flex: 1; min-width: 160px; }
.ip-who b { font-weight: 600; font-size: .92rem; display: block; }
.ip-line { display: block; font-size: .78rem; color: var(--muted); margin-top: .1rem; }
.ip-line.is-you { color: var(--accent); font-weight: 500; }
.ip-line .ti-arrow { font-style: normal; }
.ip-empty { text-align: center; padding: 2.5rem 1.5rem; }
.ip-empty-h { font-size: 1.1rem; font-weight: 600; margin: 0 0 .3rem; }
.ip-foot { margin-top: 1rem; }
@media (max-width: 560px) { .ip-row .ob-stage { order: 3; } }

/* "Needs you" feed — attention bars styled like the payout banner (ember left
   accent, soft tint), compact, with breathing room before the calendar below. */
/* "Came back" — what the client did. Same bar family as "Needs you" so the page
   still reads as one system, but deliberately lighter: a 2px edge instead of 4px,
   a fainter wash, no dot-strip. These are settled facts, not calls to action, and
   the moment they carry the same weight as your to-do list they become to-dos. */
.dash-back { margin-bottom: 1.6rem; }
.dash-back-head { display: flex; align-items: baseline; justify-content: space-between; margin-bottom: .7rem; }
.dash-back-head h2 { margin: 0; }
.back-bar { --bar: var(--ok);
    display: flex; align-items: center; gap: .7rem;
    background: color-mix(in srgb, var(--bar) 5%, var(--panel));
    border: 1px solid color-mix(in srgb, var(--bar) 18%, var(--line));
    border-left: 2px solid var(--bar); padding: .45rem .8rem; }
/* Brightness IS the hierarchy here, and it must not borrow ember — ember means
   "money owed / your move" everywhere else, so a payment RECEIVED wearing it would
   invert the one colour the app is most consistent about. */
.back-bar--paid   { --bar: var(--c-in-fill); }   /* brightest — money actually in */
.back-bar--opened { --bar: var(--ink-soft); }    /* mildest — they came and got it */
.back-bar + .back-bar { margin-top: .4rem; }
.back-main { display: flex; align-items: center; gap: .6rem; flex: 1; min-width: 0;
    text-decoration: none; color: var(--ink); }
.back-ic { flex: none; display: flex; align-items: center; justify-content: center; color: var(--bar); }
.back-ico { width: 15px; height: 15px; display: block; }
.back-body { min-width: 0; }
.back-body b { display: block; font-weight: 600; font-size: .86rem; }
.back-meta { display: block; font-size: .74rem; color: var(--muted); }
.back-main:hover .back-body b { text-decoration: underline; }
.back-tag { flex: none; font-family: var(--mono); font-size: .6rem; letter-spacing: .08em;
    text-transform: uppercase; font-weight: 600; padding: .1rem .45rem; border-radius: 2px;
    color: var(--bar); background: color-mix(in srgb, var(--bar) 12%, transparent);
    border: 1px solid color-mix(in srgb, var(--bar) 30%, transparent); }
.back-seen { margin: 0; flex: none; }
.back-seen-btn { width: 22px; height: 24px; border: 0; background: none; cursor: pointer;
    color: var(--muted); font-size: 1rem; line-height: 1; display: flex; align-items: center;
    justify-content: center; opacity: 0; transition: opacity .12s ease, color .12s ease; }
.back-bar:hover .back-seen-btn,
.back-seen-btn:focus-visible { opacity: 1; }
.back-seen-btn:hover { color: var(--ink); }
.back-more { display: inline-block; margin-top: .5rem; font-size: .75rem; color: var(--muted); }

.dash-needs { margin-bottom: 2.2rem; }
.dash-needs-head { display: flex; align-items: baseline; justify-content: space-between; margin-bottom: .7rem; }
.dash-needs-head h2 { margin: 0; }
/* Every bar carries ONE colour, held in --bar, so a category recolors the whole
   row by setting a single value instead of restating six rules. The default is
   ember: money, the project path, your move on a job.

   The two mail categories borrow the pair the charts already use for in and out,
   so the colours mean the same thing here as they do there — teal for something
   that arrived and wants reading, plum for something written that wants sending.
   Deliberately quieter than ember: unread mail is rarely the most urgent thing
   on the page, and a dashboard where everything shouts says nothing. */
.needs-bar { --bar: var(--accent);
    display: flex; align-items: center; gap: .7rem;
    background: color-mix(in srgb, var(--bar) 7%, var(--panel));
    border: 1px solid color-mix(in srgb, var(--bar) 22%, var(--line));
    border-left: 4px solid var(--bar); padding: .5rem .8rem; }
.needs-bar--mail-in  { --bar: var(--c-in); }    /* arrived, wants reading */
.needs-bar--mail-out { --bar: var(--c-out); }   /* written, wants sending */
.needs-bar + .needs-bar { margin-top: .5rem; }
.needs-main { display: flex; align-items: center; gap: .6rem; flex: 1; min-width: 0; text-decoration: none; color: var(--ink); }
.needs-ic { flex: none; color: var(--bar); font-weight: 700; }
.needs-body { min-width: 0; }
.needs-body b { display: block; font-weight: 600; font-size: .86rem; }
.needs-act { display: block; font-size: .76rem; color: var(--bar); font-weight: 500; }
.needs-main:hover .needs-body b { text-decoration: underline; }
/* Which piece of a split job this row is about ("2ND HOLD · OCT 12–14"), and what
   the job's other pieces are doing — a pending hold shouldn't read as the whole
   booking being unsettled. */
.needs-piece { display: inline-block; margin-left: .45rem; padding: .06rem .32rem;
    font-family: var(--mono); font-size: .6rem; letter-spacing: .07em; text-transform: uppercase;
    font-weight: 600; color: var(--muted); border: 1px solid var(--line-strong); border-radius: 3px;
    white-space: nowrap; vertical-align: 1px; text-decoration: none; }
.needs-rest { color: var(--muted); font-weight: 400; }
/* Dashboard "Ahead" — what's coming up, read from Google Calendar. Deliberately
   quieter than the payout banner and the Needs-you bars: it's awareness, not a
   demand. Today's entries take the accent so they lift out of the list. */
.dash-ahead { margin: 0 0 1.25rem; }
.dash-ahead-head { display: flex; align-items: baseline; justify-content: space-between; margin-bottom: .4rem; }
.dash-ahead-head h2 { font-size: .82rem; text-transform: uppercase; letter-spacing: .07em;
    color: var(--muted); margin: 0; font-weight: 600; }
.dash-ahead-list { list-style: none; margin: 0; padding: 0; border: 1px solid var(--line); }
.dash-ahead-list li { display: flex; align-items: baseline; gap: .7rem; padding: .5rem .8rem;
    border-top: 1px solid var(--line); font-size: .88rem; }
.dash-ahead-list li:first-child { border-top: 0; }
.dash-ahead-list li.is-today { background: color-mix(in srgb, var(--accent) 7%, transparent); }
.da-when { font-family: var(--mono); font-size: .76rem; color: var(--muted); white-space: nowrap; flex: none; }
.dash-ahead-list li.is-today .da-when { color: var(--accent); font-weight: 600; }
.dash-ahead-list .da-title { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.da-client { flex: none; font-size: .78rem; color: var(--muted); }
.da-join { flex: none; font-size: .78rem; }
/* Both .da-title rules above are scoped on purpose: .da-title is ALSO Lately's
   row title (dash_lately.php is in fact its only markup now), so the bare
   `order: 3` here reordered every phone-width Lately row -- the context and time
   jumped above the name they belonged to. */
@media (max-width: 560px) {
    .dash-ahead-list li { flex-wrap: wrap; gap: .3rem .6rem; }
    .dash-ahead-list .da-title { flex: 1 1 100%; order: 3; }
}
/* A Pipeline row plus its out-of-band tick. The form can't live inside the row's
   <a>, so the two sit side by side and the row takes the slack. The wrapper owns
   the right-hand gutter — the tick sits outside the row's own padding, so without
   it the button runs straight into the card's edge. */
.ip-rowwrap { display: flex; align-items: center; gap: .4rem; padding-right: .9rem; }
.ip-rowwrap > .ip-row { flex: 1; min-width: 0; }
/* The wrapper is the row now, so it carries the divider the bare rows used to. */
.ip-rowwrap + .ip-rowwrap { border-top: 1px solid var(--line); }
.ip-done { margin: 0; flex: none; }
/* Rows with no tick hold its space, so the stage chips stay in one column. */
.ip-done-gap { flex: none; width: 26px; }
/* The same dot-strip the Pipeline rows carry, so the banner says where in the
   chain this sits at a glance. Hollow dots go transparent here — on the ember
   tint a --panel fill would read as filled rather than not-yet-reached. */
.needs-bar .obdots { flex: none; }
.needs-bar .obdot { background: transparent; border-color: color-mix(in srgb, var(--accent) 32%, var(--line-strong)); }
.needs-bar .obdot.is-done { background: var(--ok); border-color: var(--ok); }
.needs-bar .obdot.is-wait { background: transparent; border-color: var(--accent); }
.needs-bar .obdot.is-now { background: var(--accent); border-color: var(--accent);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 25%, transparent); }
.needs-done { margin: 0; flex: none; }
.needs-done-btn { width: 26px; height: 26px; border-radius: 50%; border: 1px solid var(--line-strong);
    background: var(--panel); color: var(--muted); cursor: pointer; font-size: .82rem; line-height: 1;
    display: flex; align-items: center; justify-content: center; }
.needs-done-btn:hover { border-color: var(--ok); color: var(--ok); background: color-mix(in srgb, var(--ok) 10%, var(--panel)); }
/* "Not now" — takes the bar off the dashboard without claiming it's done. Kept
   deliberately quieter than the ✓ beside it: one of these changes the job, the
   other only changes what you're looking at, and they must not read as peers. */
.needs-hide { margin: 0; flex: none; }
.needs-hide-btn { width: 22px; height: 26px; border: 0; background: none; cursor: pointer;
    color: color-mix(in srgb, var(--accent) 55%, transparent); font-size: 1.05rem; line-height: 1;
    display: flex; align-items: center; justify-content: center;
    opacity: 0; transition: opacity .12s ease, color .12s ease; }
.needs-bar:hover .needs-hide-btn,
.needs-hide-btn:focus-visible { opacity: 1; }
.needs-hide-btn:hover { color: var(--ink); }

/* Pipeline: a row that's been hidden from the dashboard says so, and offers its
   way back. It is NOT dimmed — it's still your move, still owed the same weight. */
.ip-hidden-tag { font-size: .62rem; letter-spacing: .06em; text-transform: uppercase;
    font-weight: 600; margin-left: .45rem; padding: .05rem .4rem; border-radius: 2px;
    color: var(--muted); background: var(--bg); border: 1px solid var(--line); }
.ip-unhide { margin: 0; flex: none; }
.ip-unhide-btn { width: 26px; height: 26px; border-radius: 50%; border: 1px solid var(--line-strong);
    background: var(--panel); color: var(--muted); cursor: pointer; font-size: .82rem; line-height: 1;
    display: flex; align-items: center; justify-content: center; }
.ip-unhide-btn:hover { border-color: var(--accent); color: var(--accent);
    background: color-mix(in srgb, var(--accent) 10%, var(--panel)); }

.needs-more { display: inline-block; margin-top: .7rem; font-size: .8rem; font-weight: 600; color: var(--accent); text-decoration: none; }
.needs-more:hover { text-decoration: underline; }

/* Booking quick-create — the review panel that turns a studio email into a project. */
.bk-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 1.2rem; align-items: start; }
@media (max-width: 760px) { .bk-grid { grid-template-columns: 1fr; } }
.bk-quote { border-left: 3px solid var(--line-strong); padding: .1rem 0 .1rem .9rem; }
.bk-subj { font-weight: 600; margin: .35rem 0; }
.bk-snip { margin: 0; }
.bk-detected { margin-top: 1rem; font-size: .85rem; color: var(--ink-soft); }
.bk-tag { display: inline-block; font-family: var(--mono); font-size: .64rem; letter-spacing: .1em; text-transform: uppercase;
    font-weight: 600; color: var(--accent); background: color-mix(in srgb, var(--accent) 10%, var(--panel));
    border: 1px solid color-mix(in srgb, var(--accent) 30%, var(--line)); padding: .12rem .5rem; margin-right: .4rem; }
.bk-form { display: flex; flex-direction: column; gap: 1rem; margin-top: .3rem; }
.bk-field { display: flex; flex-direction: column; gap: .3rem; }
.bk-label { font-size: .74rem; font-weight: 600; color: var(--ink-soft); text-transform: uppercase; letter-spacing: .05em; }
.bk-form input[type=text], .bk-form input[type=number], .bk-form input[type=date] {
    font: inherit; padding: .5rem .6rem; border: 1px solid var(--line); background: #fff; border-radius: 0; }
.bk-fixed { font-weight: 600; }
.bk-chip { font-size: .66rem; font-weight: 500; padding: .12rem .5rem; border-radius: 999px; margin-left: .3rem; }
.bk-chip--ok  { color: var(--ok); background: color-mix(in srgb, var(--ok) 10%, var(--panel)); }
.bk-chip--new { color: var(--accent); background: color-mix(in srgb, var(--accent) 10%, var(--panel)); }
.bk-hint { font-size: .75rem; color: var(--muted); }
.bk-money { display: inline-flex; align-items: center; gap: .35rem; }
.bk-money input { width: 8rem; }
.bk-cur, .bk-per, .bk-to { color: var(--muted); }
.bk-daterow { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; }
.bk-actions { display: flex; gap: .6rem; margin-top: .4rem; }

@media (max-width: 620px) { .ob-head { flex-wrap: wrap; } }

/* ============================================================
   The assistant panel — a button in the corner, and the drawer
   it opens. Present on every page for a signed-in user, and not
   rendered at all when there's no API key.

   Sits above everything (the email lightbox included) because it
   is summoned deliberately and dismissed with Esc.
   ============================================================ */
.ai-fab { position: fixed; right: 1.1rem; bottom: 1.1rem; z-index: 60;
    width: 46px; height: 46px; border-radius: 50%; border: 1px solid var(--accent);
    background: var(--accent); color: #fff; font-size: 1.1rem; line-height: 1;
    cursor: pointer; box-shadow: 0 2px 10px rgba(20,24,30,.18); }
.ai-fab:hover { background: var(--accent-glow); border-color: var(--accent-glow); }

.ai-panel[aria-hidden="true"] { display: none; }
.ai-panel { position: fixed; inset: 0; z-index: 70; }
.ai-scrim { position: absolute; inset: 0; background: rgba(20,24,30,.35); }
body.ai-lock { overflow: hidden; }

.ai-drawer { position: absolute; right: 0; top: 0; bottom: 0; width: min(430px, 100vw);
    background: var(--panel); border-left: 1px solid var(--line);
    display: flex; flex-direction: column; box-shadow: -2px 0 16px rgba(20,24,30,.12); }
.ai-head { display: flex; align-items: baseline; gap: .6rem; padding: .8rem 1rem;
    border-bottom: 1px solid var(--line); }
.ai-head b { font-size: .95rem; }
.ai-where { flex: 1; min-width: 0; }

/* What the assistant can see from here — one chip naming the record the page is
   about (app/sight.php writes it onto the body; assistant.js reads it).
   Deliberately a chip and not the muted sentence it replaced: it has to be
   checkable at a glance and droppable in one click, because an answer drawn
   confidently from the wrong document is worse than one drawn from none.
   Named .ai-see rather than .ai-chip — .chip is a 2rem square badge elsewhere. */
.ai-see { display: inline-flex; align-items: center; gap: .35rem; max-width: 100%;
    padding: .12rem .2rem .12rem .4rem; border: 1px solid var(--line);
    border-radius: 999px; background: var(--bg); vertical-align: middle; }
.ai-see-kind { font-size: .62rem; letter-spacing: .06em; text-transform: uppercase;
    color: var(--muted); flex: none; }
.ai-see-label { font-size: .74rem; color: var(--ink); min-width: 0;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ai-see-x { flex: none; background: none; border: 0; cursor: pointer; padding: 0 .18rem;
    font-size: .95rem; line-height: 1; color: var(--muted); border-radius: 999px; }
.ai-see-x:hover { color: var(--ink); }
.ai-see-back { background: none; border: 0; cursor: pointer; padding: 0; color: var(--muted);
    font-size: .72rem; text-decoration: underline; text-underline-offset: 2px; }
.ai-see-back:hover { color: var(--accent); }

/* The passage chip sits ahead of the page chip and is the narrower of the two,
   so it takes the accent — the rings rank, and the leading one should look it. */
.ai-see--sel { border-color: var(--accent); }
.ai-see--sel .ai-see-kind { color: var(--accent); }
.ai-see--sel .ai-see-label { max-width: 11rem; overflow: hidden;
    text-overflow: ellipsis; white-space: nowrap; font-style: italic; }

/* Highlight anything and this appears over it. Fixed, because it is positioned
   from a selection rectangle, which is measured in viewport coordinates. */
.ai-sel { position: fixed; z-index: 60; top: 0; left: 0; }
.ai-sel[hidden] { display: none; }
.ai-sel-btn { display: inline-flex; align-items: center; gap: .35rem;
    padding: .32rem .6rem; font-size: .76rem; font-weight: 500; cursor: pointer;
    color: var(--ink); background: var(--panel);
    border: 1px solid var(--line); border-radius: 999px;
    box-shadow: 0 2px 10px rgba(20, 24, 30, .16); }
.ai-sel-btn:hover { border-color: var(--accent); color: var(--accent); }
.ai-sel-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.ai-x { margin-left: auto; background: none; border: 0; font-size: 1.4rem; line-height: 1;
    color: var(--muted); cursor: pointer; }
.ai-x:hover { color: var(--ink); }

.ai-body { flex: 1; overflow-y: auto; padding: 1rem; font-size: .88rem; }
.ai-hint { margin: 0 0 .7rem; }
/* The five examples, as the buttons that send them. They were bullets you read
   and then retyped; on a phone that was the whole cost of asking anything. */
.ai-starters { display: flex; flex-direction: column; align-items: flex-start; gap: .35rem; }
.ai-starter { cursor: pointer; font: inherit; font-size: .82rem; text-align: left;
    max-width: 100%; padding: .35rem .7rem; border: 1px solid var(--line);
    background: var(--panel); color: var(--ink-soft); }
.ai-starter:hover { border-color: var(--accent); color: var(--accent); }

/* One exchange: what you asked, then what came back. A follow-up keeps the same
   thread, so the panel keeps the finished ones on screen above the live one and
   the whole thing reads as a conversation instead of a screen that blanks every
   time you say something. A hairline between turns, no dimming — an answer three
   turns up is still an answer you might be reading. */
.ai-turn + .ai-turn { margin-top: 1rem; padding-top: 1rem; border-top: 1px solid var(--line); }

/* What you asked, set apart from the reply the way a sent message is anywhere
   else — so on a small screen you can see at a glance which words are yours. */
.ai-you { margin: 0 0 .7rem auto; width: fit-content; max-width: 88%;
    padding: .4rem .65rem; font-weight: 500; font-size: .86rem;
    background: color-mix(in srgb, var(--accent) 10%, var(--panel));
    border: 1px solid color-mix(in srgb, var(--accent) 26%, var(--line)); }

/* The activity trail: what it actually did, one line each, as it happens.
   Wrapped in a details so a phone can fold it once the answer has landed. On a
   wide screen the script always leaves it open and the summary is hidden, so it
   reads exactly as it did before. */
/* --ink-soft, not --muted: this is a control you tap, and muted at .76rem sits
   at about 3:1 on the panel — fine for a caption, thin for a button. */
.ai-steps > summary { display: none; cursor: pointer; font-size: .76rem;
    color: var(--ink-soft); padding: .15rem 0; margin-bottom: .3rem; list-style: none; }
.ai-steps > summary::-webkit-details-marker { display: none; }
.ai-steps > summary::before { content: '▸ '; }
.ai-steps[open] > summary::before { content: '▾ '; }
.ai-steps-n { font-family: var(--mono); font-size: .7rem; margin-left: .3rem;
    border: 1px solid var(--line); padding: 0 .28rem; }
/* A turn further up the thread always folds, so it always needs the line to
   unfold it — even on a wide screen, where the live trail shows uncovered. */
.ai-turn--past .ai-steps > summary { display: block; }
.ai-steps[open] { margin-bottom: .2rem; }
.ai-trail { list-style: none; margin: 0 0 .8rem; padding: 0;
    border-left: 2px solid color-mix(in srgb, var(--accent) 30%, var(--line)); }
.ai-trail li { position: relative; padding: .15rem 0 .15rem .8rem;
    font-size: .8rem; color: var(--ink-soft); }
.ai-trail li.is-error { color: var(--danger); }

.ai-working { font-size: .8rem; color: var(--accent); }
.ai-working::after { content: ''; animation: ai-dots 1.4s steps(4, end) infinite; }
@keyframes ai-dots { 0% { content: ''; } 25% { content: '.'; }
                     50% { content: '..'; } 75% { content: '...'; } }

/* A question parks the whole request until you answer, so it gets the loudest
   treatment in the panel. */
.ai-question { border: 1px solid color-mix(in srgb, var(--accent) 35%, var(--line));
    background: color-mix(in srgb, var(--accent) 7%, var(--panel));
    padding: .7rem .8rem; margin: .6rem 0; }
.ai-options { display: flex; flex-wrap: wrap; gap: .4rem; margin: .55rem 0 .4rem; }
.ai-opt { font-size: .8rem; padding: .25rem .7rem; border: 1px solid var(--accent);
    background: var(--panel); color: var(--accent); cursor: pointer; text-align: left; }
.ai-opt:hover { background: var(--accent); color: #fff; }

.ai-answer { line-height: 1.6; }
.ai-answer p { margin: 0 0 .6rem; }
.ai-answer p:last-child { margin-bottom: 0; }
/* Answers arrive as markdown, so they carry lists and headings. Left at browser
   defaults those came in at h1 size inside a 430px column. */
.ai-answer ul, .ai-answer ol { margin: 0 0 .6rem; padding-left: 1.2rem; }
.ai-answer li { margin-bottom: .25rem; }
.ai-answer h1, .ai-answer h2, .ai-answer h3 {
    font-size: .9rem; margin: .8rem 0 .35rem; line-height: 1.3; }
.ai-answer h1:first-child, .ai-answer h2:first-child, .ai-answer h3:first-child { margin-top: 0; }
.ai-answer table { width: 100%; border-collapse: collapse; font-size: .8rem; margin: 0 0 .6rem; }
.ai-answer th, .ai-answer td { border: 1px solid var(--line); padding: .25rem .4rem; text-align: left; }
/* A long link or a pasted path must not widen the sheet it sits in. */
.ai-answer { overflow-wrap: anywhere; }

/* Something it wrote, offered where you are rather than filed away silently.
   Plum, matching drafts everywhere else in the app. */
.ai-draft { display: block; margin-top: .7rem; padding: .6rem .7rem; text-decoration: none;
    border: 1px solid color-mix(in srgb, var(--c-out) 30%, var(--line));
    border-left: 4px solid var(--c-out);
    background: color-mix(in srgb, var(--c-out) 6%, var(--panel)); color: var(--ink); }
.ai-draft b { display: block; font-size: .84rem; }
.ai-draft span { display: block; font-size: .78rem; color: var(--ink-soft); }
.ai-draft-go { color: var(--c-out) !important; font-weight: 600; margin-top: .25rem; }

.ai-error { color: var(--danger); background: var(--danger-bg); padding: .55rem .7rem;
    font-size: .82rem; margin-top: .6rem; }

.ai-ask { display: flex; gap: .5rem; align-items: flex-end;
    padding: .7rem 1rem; border-top: 1px solid var(--line); }
.ai-input { flex: 1; min-width: 0; resize: none; font: inherit; font-size: .86rem;
    padding: .45rem .6rem; border: 1px solid var(--line); background: var(--panel);
    color: var(--ink); }
.ai-input:focus { outline: none; border-color: var(--accent);
    box-shadow: 0 0 0 3px rgba(238,123,60,.15); }
.ai-foot { display: flex; justify-content: space-between; gap: 1rem;
    padding: 0 1rem .7rem; }

@media (max-width: 640px) {
    .ai-drawer { width: 100vw; border-left: 0; }
    .ai-fab { right: .8rem; bottom: .8rem; }
}
.ai-new { background: none; border: 0; cursor: pointer; padding: 0 .2rem;
    text-decoration: underline; text-underline-offset: 2px; }
.ai-new:hover { color: var(--accent); }

/* Nothing has come back for a while — the request is safe, it just isn't
   running. Says so, and offers the one thing that helps. */
.ai-stuck { margin-top: .5rem; padding: .55rem .65rem; font-size: .78rem;
    color: var(--muted); background: color-mix(in srgb, var(--accent) 5%, transparent);
    border: 1px dashed var(--line); }
.ai-nudge { display: block; margin-top: .45rem; cursor: pointer; font: inherit;
    font-size: .78rem; background: none; border: 1px solid var(--line);
    color: var(--ink); padding: .32rem .7rem; }
.ai-nudge:hover:not(:disabled) { border-color: var(--accent); color: var(--accent); }
.ai-nudge:disabled { cursor: default; opacity: .6; }

/* Assistant history — the panel's list of past requests, and the log page. */
.ai-hist-head { padding: 0 0 .5rem; }
.ai-hist { display: block; width: 100%; text-align: left; cursor: pointer;
    background: none; border: 0; border-bottom: 1px solid var(--line);
    padding: .5rem 0; font: inherit; color: var(--ink); }
.ai-hist:hover { background: color-mix(in srgb, var(--accent) 6%, transparent); }
.ai-hist-ask { display: block; font-size: .84rem; }
.ai-hist-meta { display: block; font-size: .74rem; color: var(--muted); margin-top: .1rem; }
/* Day headings and the way back through older pages. */
.ai-hist-day { position: sticky; top: 0; z-index: 1; background: var(--panel);
    font-family: var(--mono); font-size: .66rem; text-transform: uppercase;
    letter-spacing: .08em; color: var(--muted); padding: .7rem 0 .3rem; }
.ai-hist-day:first-child { padding-top: 0; }
.ai-hist-older { display: block; width: 100%; margin: .7rem 0 .2rem; cursor: pointer;
    background: none; border: 1px dashed var(--line); color: var(--muted);
    padding: .45rem 0; font: inherit; font-size: .78rem; }
.ai-hist-older:hover:not(:disabled) { border-color: var(--accent); color: var(--accent); }
.ai-hist-older:disabled { cursor: default; opacity: .6; }
.ai-hist-end { padding: .7rem 0 .2rem; text-align: center; }

.ailog { margin-bottom: .6rem; }
.ailog summary { cursor: pointer; }
.ailog summary b { font-weight: 600; font-size: .9rem; }
.ailog summary .muted { display: block; margin-top: .15rem; }
.ailog-steps { list-style: none; margin: .8rem 0; padding: 0 0 0 .8rem;
    border-left: 2px solid color-mix(in srgb, var(--accent) 30%, var(--line)); }
.ailog-steps li { font-size: .82rem; color: var(--ink-soft); padding: .12rem 0; }
.ailog-steps li.is-error { color: var(--danger); }
.ailog-steps li .muted { margin-left: .4rem; font-family: var(--mono); }
.ailog-answer { font-size: .88rem; margin: .6rem 0; }
.ailog-answer p { margin: 0 0 .5rem; }
/* A parked request, and the box that unsticks it. Ember down the left edge —
   the same "your turn" the dashboard row uses, so the thing you clicked and the
   thing you landed on are visibly the same thing. */
.ailog--waiting { border-left: 2px solid var(--accent); }
.ailog-ask { margin: .7rem 0 .2rem; }
.ailog-q { font-size: .9rem; font-weight: 500; margin: 0 0 .5rem; }
.ailog-ask textarea { width: 100%; font: inherit; font-size: .88rem; padding: .5rem .6rem;
    border: 1px solid var(--line); border-radius: 0; background: var(--panel); color: var(--ink);
    resize: vertical; }
.ailog-ask textarea:focus { outline: none; border-color: var(--gold);
    box-shadow: 0 0 0 3px rgba(238,123,60,.15); }
.ailog-ask-go { display: flex; align-items: center; gap: .6rem; flex-wrap: wrap; margin-top: .5rem; }
/* The way out, kept visibly quieter than the way on. Same row shape, ghost
   button — closing a request is a fine thing to do, just never the default. */
.ailog-ask-no { display: flex; align-items: center; gap: .6rem; flex-wrap: wrap; margin-top: .5rem;
    padding-top: .5rem; border-top: 1px solid var(--line); }
.ailog-err { color: var(--danger); font-size: .84rem; }

/* The brief has just been written onto the page behind the panel. */
.ai-saved { margin-top: .8rem; padding-top: .7rem; border-top: 1px solid var(--line);
    font-size: .8rem; color: var(--muted); }

/* ============================================================
   "Catch me up" — the brief card on a client and on a job.

   EMBER IS THE TELL. Everything the assistant wrote, or reads
   before writing, carries the accent: the spark, the section
   labels, the standing facts, the ask chips. On a page that is
   otherwise ink-on-paper ledger rows, colour is how you know at a
   glance which words came from a model.

   Shut by default (see the partial). Open, four bands run the
   opposite way to the trust: the COUNTED strip is small, mono and
   exact; the written sections below it are the biggest thing on the
   card but wear a date and a source list, so they never read as
   live fact. Keep that order if this is restyled — it's the
   argument, not a layout preference.
   ============================================================ */
/* THE EMBER EDGE NOW BELONGS TO THE FOLD, NOT TO THE CARD.
   The panel holds two halves that must not be confused: a counted strip that is
   plain ink, because arithmetic has no author, and the assistant's reading of it
   underneath, which keeps the accent every AI-written thing in this app wears.
   One border around both would have said a model wrote the numbers.

   The long selectors are here to out-specify the detail pages' own
   `.cd-main > .panel` sheet rule, which sets the whole border — a bare class
   loses to it and the card comes out looking like every other panel. */
.brf { border-left: 2px solid var(--accent); padding-left: .8rem; }

/* No facts to show — a client with no paid invoice and no back-and-forth. The
   panel is then only the fold, so the edge goes back on the outside: an indented
   ember bar with nothing above it reads as a stray quote block. */
.brf-wrap--alone .brf { border-left: 0; padding-left: 0; }
.brf-wrap--alone,
.client-detail .cd-main > .brf-wrap--alone,
.project-detail .cd-main > .brf-wrap--alone { border-left: 2px solid var(--accent); }

/* The counted band's own heading. Deliberately NOT given a `--sec` hue like the
   sections around it: those colours name a kind of record, and this one names
   the absence of a source. Ink is the point. */
.brf-counted-h { font-size: 1.24rem; }
.brf-wrap .brf-facts { padding-top: .9rem; margin-bottom: .95rem; }
.brf > .brf-sum { display: flex; align-items: center; gap: .55rem; cursor: pointer;
    list-style: none; padding: .1rem 0; }
.brf > .brf-sum::-webkit-details-marker { display: none; }
.brf-spark { display: inline-flex; color: var(--accent); }
.brf-icon { width: 17px; height: 17px; }
.brf[open] .brf-spark { opacity: .75; }
.brf-sum-t { font-weight: 600; font-size: .95rem; }
.brf-sum-when { flex: none; }
/* Something has happened since it was written. Now printed inside, next to the
   date it qualifies, where "written Aug 12 · 3 emails since" reads as one fact. */
.brf-sum-stale { font-size: .74rem; color: var(--accent); }
.brf-when { padding-bottom: .55rem; }
/* "Rewritten on its own" — the app admitting it spent your money while you were
   asleep. Ember, like everything else here that came out of a model, and the only
   emphasised words on the line. */
.brf-auto { color: var(--accent); font-weight: 600; }

/* Since YOU last looked. This is the one that took the shut line, so it is
   louder than the stale note it replaced: a lit dot, the accent, and enough
   weight to read as news rather than as metadata. It never hides on a phone —
   being seen without opening the card is the entire job. */
.brf-sum-news { font-size: .76rem; font-weight: 500; color: var(--accent);
    display: inline-flex; align-items: center; gap: .38rem; min-width: 0;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.brf-sum-news::before { content: ""; flex: none; width: 6px; height: 6px;
    border-radius: 50%; background: var(--accent); }
.brf-sum-chev { margin-left: auto; color: var(--muted); font-size: .8rem;
    transition: transform .12s ease; }
.brf[open] .brf-sum-chev { transform: rotate(180deg); }
.brf-sum:hover .brf-sum-t { color: var(--accent); }

.brf-in { padding-top: .9rem; }

.brf-facts { display: flex; flex-wrap: wrap; gap: .1rem 2rem;
    padding-bottom: .9rem; border-bottom: 1px solid var(--line); }
.brf-fact { display: flex; align-items: baseline; gap: .45rem; padding: .18rem 0; }
.brf-k { font-size: .72rem; text-transform: uppercase; letter-spacing: .06em;
    color: var(--muted); }
.brf-v { font-family: var(--mono); font-size: .86rem; color: var(--ink); }
.brf-v.is-good { color: var(--ok); }
.brf-v.is-warn { color: var(--accent); }
.brf-n { font-size: .74rem; color: var(--muted); }
/* The baseline. Italic and quiet — it's an aside about the number beside it,
   not a second number competing with it. */
.brf-vs { font-size: .74rem; color: var(--ink-soft); font-style: italic; }

/* The written sections. A fixed shape you learn once: the gist reads as the
   answer, the labelled rows sit in the same places every time, so you can find
   "how they book" by eye rather than by reading a paragraph to see if it's in
   there. Labels are a left column on desktop and stack on a phone. */
.brf-body { padding: .9rem 0 .3rem; }
.brf-sec { display: flex; gap: 1rem; align-items: baseline; padding: .3rem 0; }
.brf-sec-k { flex: 0 0 7.5rem; font-size: .7rem; text-transform: uppercase;
    letter-spacing: .07em; color: var(--accent); }
.brf-sec-v { flex: 1; min-width: 0; font-size: .88rem; line-height: 1.5; }
.brf-sec-v p { margin: 0; }
.brf-sec-v ul { margin: .15rem 0 0; padding-left: 1rem; }
.brf-sec-v li { margin-bottom: .1rem; }
/* The gist is the answer, not a section: bigger, no label, and given room. */
.brf-sec--gist { padding-bottom: .75rem; margin-bottom: .35rem;
    border-bottom: 1px solid var(--line); }
.brf-sec--gist .brf-sec-v { font-size: .96rem; line-height: 1.5; color: var(--ink); }

.brf-meta { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem 1rem;
    padding-top: .6rem; font-size: .76rem; color: var(--muted); }
.brf-src summary { cursor: pointer; color: var(--muted); }
.brf-src ul { list-style: none; margin: .4rem 0 0; padding: 0 0 0 .8rem;
    border-left: 2px solid color-mix(in srgb, var(--accent) 30%, var(--line)); }
.brf-src li { padding: .1rem 0; color: var(--ink-soft); }
.brf-meta-acts { margin-left: auto; display: flex; align-items: center; gap: .6rem; }

.brf-empty { padding: .9rem 0 .2rem; display: flex; flex-wrap: wrap;
    align-items: center; gap: .6rem 1rem; }
.brf-empty p { flex: 1 1 100%; margin: 0; font-size: .84rem; color: var(--ink-soft); }
/* A finished job nobody has written up — the one moment the empty state is
   making a case rather than describing a feature. */
.brf-empty--wrap { padding: .8rem .85rem; margin-top: .8rem;
    border-left: 2px solid var(--accent);
    background: color-mix(in srgb, var(--accent) 5%, transparent); }
.brf-empty--wrap p { color: var(--ink); }

/* What you've told it. Deliberately plain rows rather than badges — these are
   sentences you wrote, and they get read as often as they get added. */
.brf-known { margin-top: .9rem; padding-top: .8rem; border-top: 1px solid var(--line); }
.brf-known-head { font-size: .72rem; text-transform: uppercase; letter-spacing: .06em;
    color: var(--accent); margin-bottom: .4rem; }
.brf-known-head .muted { text-transform: none; letter-spacing: 0; }
.brf-known-list { list-style: none; margin: 0 0 .5rem; padding: 0; }
.brf-known-list li { display: flex; align-items: baseline; gap: .5rem;
    padding: .25rem 0; border-top: 1px solid var(--line); font-size: .84rem; }
.brf-known-list li:first-child { border-top: 0; }
.brf-known-list li span { flex: 1; min-width: 0; }
/* A fact the app noticed for itself, with the message behind it. The receipt is
   quiet on purpose — the fact is what you read, the source is what you check —
   but it is a real link, because a provenance you can't follow isn't one. */
.brf-known-src { font-size: .74rem; color: var(--ink-soft);
    white-space: nowrap; }
a.brf-known-src { border-bottom: 1px dotted currentColor; text-decoration: none; }
a.brf-known-src:hover { color: var(--accent); }
/* Proposals: offers, not statements. Dashed edge is the app's standing signal
   for "nothing has happened yet" — same as the ask chips and the older-history
   button — so a suggestion never reads as a fact you've already kept. */
.brf-prop { margin: 0 0 .6rem; padding: .5rem .65rem;
    border: 1px dashed color-mix(in srgb, var(--accent) 40%, var(--line));
    background: color-mix(in srgb, var(--accent) 4%, transparent); }
.brf-prop-head { font-size: .72rem; text-transform: uppercase; letter-spacing: .06em;
    color: var(--accent); margin-bottom: .3rem; }
.brf-prop-row { display: flex; align-items: baseline; gap: .6rem; padding: .22rem 0;
    font-size: .84rem; }
.brf-prop-row span { flex: 1; min-width: 0; color: var(--ink-soft); }

.brf-known-add { display: flex; gap: .5rem; align-items: center; }
.brf-known-add input { flex: 1; min-width: 0; font: inherit; font-size: .82rem;
    padding: .35rem .5rem; border: 1px solid var(--line); background: var(--panel);
    color: var(--ink); }
.brf-known-add input:focus { outline: none; border-color: var(--accent); }

.brf-ask { display: flex; flex-wrap: wrap; gap: .4rem;
    margin-top: .9rem; padding-top: .8rem; border-top: 1px solid var(--line); }
.brf-chip { cursor: pointer; font: inherit; font-size: .78rem; text-align: left;
    padding: .3rem .65rem; border: 1px solid var(--line); background: none;
    color: var(--ink-soft); border-radius: 999px; }
.brf-chip:hover { border-color: var(--accent); color: var(--accent); }
.brf-chip--open { border-style: dashed; color: var(--muted); }


@media (max-width: 768px) {
    /* The assistant sits in the bottom bar on mobile, so the floating button
       would be a second door to the same room — and it landed right on top of
       the bar. */
    .ai-fab { display: none; }

    /* An action among places to go, so it gets the accent rather than the same
       grey as the sections. Slightly narrower: six items have to fit a 360px
       phone without the bar wrapping. */
    .mnav-item--ai { color: var(--accent-glow); min-width: 48px; }
    .mnav-item--ai:hover { color: #fff; background: rgba(238,123,60,.22); }
    .mnav-bar { gap: 0; }

    /* On a phone the drawer was a full-height slab, and what you actually do in
       it is ask one thing and read one reply. So it becomes a sheet: the same
       hairline card the command palette uses, sitting above the nav bar with
       about two-thirds of the screen. Bottom-anchored rather than centred like
       the palette, because the box you type into is at the bottom of it — that
       is where your thumb is, and where the keyboard pushes from.

       --ai-kb is how much of the screen the keyboard is covering (assistant.js
       reads the visual viewport for it, 0 when there's no keyboard). Taking the
       larger of that and the nav bar means the sheet clears whichever is in the
       way — with the keyboard up the bar is behind it anyway. */
    .ai-drawer {
        top: auto; left: .55rem; right: .55rem; width: auto;
        bottom: calc(env(safe-area-inset-bottom, 0px) + max(4.6rem, var(--ai-kb, 0px)));
        height: min(72vh, 30rem);
        max-height: calc(100vh - max(4.6rem, var(--ai-kb, 0px)) - 1.2rem);
        border: 1px solid var(--line-strong);
        box-shadow: 0 14px 44px rgba(20,24,30,.3);
    }

    /* Five things in a 360px head. The title shortens, and what it can see gets
       the slack — it's the half that changes. The chip's own label does the
       truncating, so the wrapper only has to stop it pushing the buttons off. */
    .ai-head { gap: .45rem; padding: .6rem .7rem; }
    .ai-where { flex: 1 1 auto; min-width: 0; overflow: hidden; }
    .ai-see-label { max-width: 9rem; }

    /* The reply is the reason the sheet is open, so it gets the bigger type and
       the looser line. */
    .ai-body { padding: .8rem; font-size: .92rem; }
    .ai-answer { font-size: .92rem; line-height: 1.62; }
    .ai-you { max-width: 80%; }

    /* Folded, the trail is one tappable line instead of eight. */
    .ai-steps > summary { display: block; }

    /* One line each, wrapping, so a chip doesn't get clipped mid-word. */
    .ai-starters { gap: .3rem; }
    .ai-starter { width: 100%; }

    /* A 16px field is the one that doesn't make iOS zoom the page on focus. */
    .ai-ask { padding: .6rem .7rem; }
    .ai-input { font-size: 16px; }
    .ai-foot { padding: 0 .7rem .6rem; }
}

/* Six items instead of five: on the smallest phones the bar would otherwise be
   wider than the screen it floats on. Tighten rather than drop an item. */
@media (max-width: 380px) {
    .mnav-item { min-width: 46px; padding: .4rem .32rem; }
    .mnav-item--ai { min-width: 42px; }
    .mnav-label { font-size: .58rem; }
    .mnav-ico { width: 20px; height: 20px; }
}


/* ============================================================
   STATUS REPORT (sr-)  —  the table-driven client update.

   One set of rules dresses two surfaces: the in-app editor and
   the read-only page the client opens at /v/{token}. The document
   itself is identical in both; `.sr--edit` only adds the
   affordances (typeable cells, row tools, add buttons), so what
   you edit is exactly what they see.

   Language is the redesign standard: page ground behind white
   panels, hairline rules, square corners, mono for every numeral
   / label / tag, Space Grotesk for prose. Tone colours follow the
   app's semantics — blue = new work, green = in hand, red =
   coming off the books, ember = needs attention.
   ============================================================ */

.sr { --sr-blue: #2F6FB0; --sr-green: #00A884; --sr-red: var(--danger); --sr-ember: var(--accent); --sr-grey: var(--muted); }

/* ---- masthead ---------------------------------------------- */
.sr-head { margin-bottom: 1.6rem; }
.sr-eyebrow { font-family: var(--mono); font-size: .64rem; text-transform: uppercase;
    letter-spacing: .16em; font-weight: 700; color: var(--accent); }
.sr-title { font-size: 1.9rem; font-weight: 700; letter-spacing: -.015em; margin: .35rem 0 .4rem; }
.sr-sub { color: var(--ink-soft); font-size: .92rem; margin: 0; max-width: 62ch; }

/* ---- summary tiles ------------------------------------------
   Counted from the rows, never typed. Same hairline grid + 3px
   ribbon as the Dashboard's stat tiles. */
.sr-tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
    gap: 0; border-left: 1px solid var(--line); margin-bottom: 2.2rem; }
.sr-tile { background: var(--panel); border-top: 3px solid var(--sr-tone, var(--line-strong));
    border-right: 1px solid var(--line); border-bottom: 1px solid var(--line);
    padding: 1rem 1.1rem 1.1rem; min-width: 0; }
.sr-tile--blue  { --sr-tone: #2F6FB0; }
.sr-tile--green { --sr-tone: #00A884; }
.sr-tile--red   { --sr-tone: var(--danger); }
.sr-tile--ember { --sr-tone: var(--accent-glow); }
.sr-tile--grey  { --sr-tone: var(--line-strong); }
.sr-tile-n { font-family: var(--mono); font-size: 1.75rem; font-weight: 500; line-height: 1;
    letter-spacing: -.02em; font-variant-numeric: tabular-nums; color: var(--sr-tone, var(--ink)); }
.sr-tile-l { font-family: var(--mono); font-size: .62rem; text-transform: uppercase;
    letter-spacing: .12em; font-weight: 700; color: var(--ink-soft); margin-top: .6rem; }
.sr-tile-s { font-size: .72rem; color: var(--muted); margin-top: .4rem; min-height: 1em; }

/* ---- a table ------------------------------------------------ */
.sr-section { margin-bottom: 2.4rem; }
.sr-sechead { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem;
    padding-bottom: .5rem; border-bottom: 1px solid var(--line); margin-bottom: .1rem; }
.sr-h { font-size: .92rem; font-weight: 600; letter-spacing: -.005em; margin: 0; }
.sr-tablewrap { overflow-x: auto; }
.sr-table { width: 100%; border-collapse: collapse; }
.sr-th { font-family: var(--mono); font-size: .6rem; text-transform: uppercase; letter-spacing: .1em;
    font-weight: 700; color: var(--muted); text-align: left; padding: .7rem .7rem .55rem;
    border-bottom: 1px solid var(--line); white-space: nowrap; vertical-align: top; }
.sr-th--n, .sr-td--n { width: 2.2rem; }
.sr-th--action { width: 1%; }
.sr-td { padding: .55rem .7rem; border-bottom: 1px solid var(--line); font-size: .88rem;
    vertical-align: middle; }
.sr-table tbody tr:last-child .sr-td { border-bottom: 0; }
.sr-td--n { font-family: var(--mono); font-size: .72rem; color: var(--muted);
    font-variant-numeric: tabular-nums; }
/* The subject of the row shouldn't wrap into two lines when a later column is
   wide — the table scrolls in its own wrapper instead. */
.sr-td--name { font-weight: 500; white-space: nowrap; }
.sr-td--mono { font-family: var(--mono); font-size: .76rem; color: var(--ink-soft); }
.sr-td--yesno, .sr-td--action { white-space: nowrap; }

/* Yes / No cells read as state, not prose. */
.sr-yn { font-family: var(--mono); font-size: .74rem; font-weight: 600; letter-spacing: .02em;
    background: none; border: 0; padding: 0; color: var(--ink-soft); }
.sr-yn--ok  { color: var(--c-in); }
.sr-yn--bad { color: var(--danger); }
.sr-yn--nil { color: var(--muted); }

/* ---- the action pill ---------------------------------------- */
.sr-actioncell { display: inline-flex; align-items: center; gap: .4rem; position: relative; }
.sr-pill { display: inline-block; font-family: var(--mono); font-size: .62rem; font-weight: 700;
    text-transform: uppercase; letter-spacing: .09em; padding: .2rem .5rem; white-space: nowrap;
    border: 1px solid color-mix(in srgb, var(--sr-tone, var(--muted)) 40%, var(--line));
    background: color-mix(in srgb, var(--sr-tone, var(--muted)) 11%, var(--panel));
    color: var(--sr-tone, var(--ink-soft)); }
.sr-pill--blue  { --sr-tone: #2F6FB0; }
.sr-pill--green { --sr-tone: var(--c-in); }
.sr-pill--red   { --sr-tone: var(--danger); }
.sr-pill--ember { --sr-tone: var(--accent); }
.sr-pill--grey  { --sr-tone: var(--muted); }

/* "waiting" is a second axis, not a fifth state: a row can be a new build AND
   be stuck. Drawn small and ember so it reads as a flag on the action. */
.sr-wait { font-family: var(--mono); font-size: .58rem; text-transform: uppercase;
    letter-spacing: .1em; font-weight: 700; color: var(--accent); }
.sr-wait::before { content: "\25C6"; font-size: .8em; margin-right: .3em; }

/* ---- the note + footer -------------------------------------- */
.sr-note { background: var(--panel); border: 1px solid var(--line); border-left: 3px solid var(--accent);
    padding: 1.1rem 1.25rem; margin: 2.4rem 0 0; }
.sr-note-h { font-family: var(--mono); font-size: .64rem; text-transform: uppercase;
    letter-spacing: .12em; font-weight: 700; color: var(--accent); margin-bottom: .5rem; }
.sr-note-b { font-size: .88rem; color: var(--ink-soft); margin: 0; }
.sr-note-list { margin: .7rem 0 0; padding-left: 1.1rem; font-size: .85rem; color: var(--ink-soft); }
.sr-note-list li { margin: .28rem 0; }
.sr-foot { display: block; margin-top: 2rem; padding-top: .9rem; border-top: 1px solid var(--line);
    font-size: .76rem; color: var(--muted); }

/* ============================================================
   EDIT MODE — the affordances, and nothing else.
   ============================================================ */

.sr-editor { position: relative; }
.sr-canvas { background: var(--panel); border: 1px solid var(--line); padding: 2rem 2.1rem 2.4rem; }

/* Typeable text: invisible until you reach for it. */
.sr-ce { outline: none; cursor: text; transition: background .12s, box-shadow .12s; }
.sr-ce:hover  { background: color-mix(in srgb, var(--accent) 6%, transparent); }
.sr-ce:focus  { background: color-mix(in srgb, var(--accent) 9%, transparent);
    box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent) 45%, transparent); }
.sr-ce:empty::before { content: attr(data-ph); color: var(--muted); font-weight: 400; }
.sr-note-list.sr-ce:has(li:only-child:empty)::before { content: attr(data-ph); color: var(--muted); }

/* The action picker. */
.sr-pillbtn, .sr-waitbtn { background: none; border: 0; padding: 0; cursor: pointer; font: inherit; }
/* "waiting" is off on most rows, and 26 pale copies of the word is noise in a
   document you're reading as much as editing. Off rows show it on hover; a row
   that IS waiting always shows it, because that's part of the document. */
.sr-waitbtn { font-family: var(--mono); font-size: .58rem; text-transform: uppercase; letter-spacing: .1em;
    font-weight: 700; color: var(--line-strong); padding: .1rem .2rem;
    opacity: 0; transition: opacity .12s, color .12s; }
.sr-table tr:hover .sr-waitbtn, .sr-waitbtn:focus, .sr-waitbtn.is-on { opacity: 1; }
.sr-waitbtn:hover { color: var(--accent); }
.sr-waitbtn.is-on { color: var(--accent); }
.sr-waitbtn.is-on::before { content: "\25C6"; font-size: .8em; margin-right: .3em; }
.sr-menu { position: absolute; top: calc(100% + .3rem); left: 0; z-index: 20; display: flex;
    flex-direction: column; min-width: 8rem; background: var(--panel);
    border: 1px solid var(--line-strong); box-shadow: 0 6px 20px rgba(20,24,30,.14); }
/* A class rule outranks the browser's [hidden]{display:none}, so say it here or
   every row's picker hangs open at once. */
.sr-menu[hidden] { display: none; }
.sr-opt { text-align: left; background: none; border: 0; cursor: pointer; padding: .4rem .7rem;
    font-family: var(--mono); font-size: .64rem; text-transform: uppercase; letter-spacing: .09em;
    font-weight: 700; color: var(--sr-tone, var(--ink-soft)); }
.sr-opt--blue { --sr-tone: #2F6FB0; } .sr-opt--green { --sr-tone: var(--c-in); }
.sr-opt--red  { --sr-tone: var(--danger); }  .sr-opt--ember { --sr-tone: var(--accent); }
.sr-opt--grey { --sr-tone: var(--muted); }
.sr-opt:hover { background: var(--wash); }
.sr-opt.is-on { background: color-mix(in srgb, var(--sr-tone) 12%, transparent); }

/* Row + column tools stay out of the way until the row is under the cursor. */
.sr-th--tools, .sr-td--tools { width: 1%; white-space: nowrap; text-align: right; }
.sr-rowbtn { background: none; border: 0; cursor: pointer; font: inherit; font-size: .8rem;
    color: var(--line-strong); padding: 0 .15rem; opacity: 0; transition: opacity .12s, color .12s; }
.sr-table tr:hover .sr-rowbtn, .sr-rowbtn:focus { opacity: 1; }
.sr-rowbtn:hover { color: var(--accent); }
.sr-rowbtn--del:hover { color: var(--danger); }
/* The column label owns its own line; what the column HOLDS sits under it, with
   the delete only surfacing when you're actually in that header. */
.sr-thlabel { display: block; }
.sr-coltype { display: inline-block; margin: .25rem .4rem 0 0; background: none; border: 0; cursor: pointer;
    font-family: var(--mono); font-size: .52rem; text-transform: uppercase; letter-spacing: .1em;
    font-weight: 700; color: var(--line-strong); padding: 0; }
.sr-coltype:hover { color: var(--accent); }
.sr-coldel, .sr-coladd { background: none; border: 0; cursor: pointer; font: inherit;
    color: var(--line-strong); padding: 0 .2rem; }
.sr-coldel { display: inline-block; opacity: 0; transition: opacity .12s, color .12s; }
.sr-th:hover .sr-coldel, .sr-coldel:focus { opacity: 1; }
.sr-coldel:hover { color: var(--danger); }
.sr-coladd:hover { color: var(--accent); }
/* Removing a whole table is a big, rare action — it shouldn't sit in the
   document's eyeline while you're writing it. */
.sr-secdel { background: none; border: 0; cursor: pointer; font-family: var(--mono); font-size: .58rem;
    text-transform: uppercase; letter-spacing: .1em; font-weight: 700; color: var(--line-strong);
    opacity: 0; transition: opacity .12s, color .12s; }
.sr-section:hover .sr-secdel, .sr-secdel:focus { opacity: 1; }
.sr-secdel:hover { color: var(--danger); }

.sr-add { margin-top: .7rem; background: none; border: 1px dashed var(--line-strong); cursor: pointer;
    font-family: var(--mono); font-size: .62rem; text-transform: uppercase; letter-spacing: .1em;
    font-weight: 700; color: var(--muted); padding: .42rem .8rem; }
.sr-add:hover { color: var(--accent); border-color: var(--accent); border-style: solid; }
.sr-addsection { margin: 1rem 0 0; }
.sr-note--empty { border-left-color: var(--line-strong); }

/* ---- the save bar ------------------------------------------- */
.sr-bar { position: sticky; top: 0; z-index: 30; display: flex; align-items: center; gap: .7rem;
    background: var(--ink); color: #fff; padding: .55rem .9rem; margin-bottom: 1.1rem; }
.sr-bar-back { color: var(--accent-glow); font-family: var(--mono); font-size: .72rem; }
.sr-bar-back:hover { color: #fff; }
.sr-bar-mid { flex: 1; min-width: 0; display: flex; align-items: center; gap: .8rem; }
.sr-bar-hint { font-family: var(--mono); font-size: .62rem; letter-spacing: .06em;
    color: rgba(255,255,255,.45); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.sr-bar-dirty { display: none; font-family: var(--mono); font-size: .62rem; text-transform: uppercase;
    letter-spacing: .1em; font-weight: 700; color: var(--accent-glow); }
.sr-bar.is-dirty .sr-bar-dirty { display: inline; }
.sr-bar.is-dirty .sr-bar-hint { display: none; }
.sr-bar-saved { font-family: var(--mono); font-size: .62rem; text-transform: uppercase;
    letter-spacing: .1em; font-weight: 700; color: #7FD1A8; }
.sr-bar-link { color: #fff; font-family: var(--mono); font-size: .68rem; white-space: nowrap; }
.sr-bar-link:hover { color: var(--accent-glow); }
.sr-bar-btn { cursor: pointer; font: inherit; font-family: var(--mono); font-size: .68rem;
    font-weight: 600; padding: .38rem .8rem; white-space: nowrap;
    background: transparent; color: #fff; border: 1px solid rgba(255,255,255,.35); }
.sr-bar-btn:hover { border-color: #fff; }
.sr-bar-btn--go { background: var(--accent); border-color: var(--accent); }
.sr-bar-btn--go:hover { background: var(--accent-glow); border-color: var(--accent-glow); }

.sr-sharenote { display: flex; align-items: center; gap: .6rem; flex-wrap: wrap; margin-top: 1rem;
    padding: .7rem .9rem; border: 1px solid var(--line); background: var(--panel); font-size: .78rem; }
.sr-sharenote-l { font-family: var(--mono); font-size: .6rem; text-transform: uppercase;
    letter-spacing: .12em; font-weight: 700; color: var(--accent); }
.sr-sharenote-url { font-family: var(--mono); font-size: .72rem; color: var(--ink-soft);
    word-break: break-all; }

/* ============================================================
   THE CLIENT'S PAGE — the same document, no app around it.
   ============================================================ */
.sr-page { font-family: var(--font); color: var(--ink); }
.sr-shell { max-width: 1060px; margin: 0 auto; padding: 2.5rem 1.5rem 4rem; }
.sr-brand { display: flex; align-items: center; gap: .7rem; margin-bottom: 2.4rem;
    padding-bottom: 1rem; border-bottom: 1px solid var(--line); }
.sr-brand-mark { width: 34px; height: 34px; flex: none; overflow: hidden; background: var(--ink); }
.sr-brand-mark img, .sr-brand-mark svg { width: 100%; height: 100%; object-fit: cover; display: block; }
.sr-brand-name { font-weight: 600; font-size: .92rem; }
.sr-brand-date { margin-left: auto; font-family: var(--mono); font-size: .68rem;
    letter-spacing: .06em; color: var(--muted); }
.sr-sent { margin-top: 2.6rem; font-family: var(--mono); font-size: .64rem; text-transform: uppercase;
    letter-spacing: .12em; color: var(--muted); text-align: center; }

/* ---- phones -------------------------------------------------
   The tables scroll inside their own wrapper rather than pushing
   the page sideways; the save bar keeps only what you'd tap. */
@media (max-width: 760px) {
    .sr-canvas { padding: 1.1rem .9rem 1.6rem; }
    .sr-shell { padding: 1.6rem 1rem 3rem; }
    .sr-title { font-size: 1.45rem; }
    .sr-tiles { grid-template-columns: repeat(2, 1fr); }
    .sr-tile { padding: .8rem .85rem .9rem; }
    .sr-tile-n { font-size: 1.4rem; }
    .sr-bar { flex-wrap: wrap; gap: .5rem; padding: .5rem .7rem; }
    .sr-bar-mid { order: 3; flex-basis: 100%; }
    .sr-bar-hint { display: none; }
    .sr-td, .sr-th { padding-left: .45rem; padding-right: .45rem; }
    .sr-td { font-size: .82rem; }
    .sr-rowbtn { opacity: 1; }     /* no hover on touch — the tools have to be there */
}


/* ============================================================
   SHEET (sh-)  —  the general table document.

   Same two-surface arrangement as the status report: one set of
   rules dresses the in-app editor and the client's read-only
   page, and `.sh--edit` adds only the affordances. Where the two
   differ on purpose: the client's copy can still SORT, so the
   header stays a button there.

   Redesign language throughout — page ground behind white
   panels, hairlines, square corners, mono for numerals, labels
   and tags, Space Grotesk for prose.
   ============================================================ */

/* Tag tones. Named rather than positional so a category keeps its colour
   wherever it moves in the list. */
.sh-pill--grey  { --sh-tone: var(--muted); }
.sh-pill--blue  { --sh-tone: #2F6FB0; }
.sh-pill--green { --sh-tone: var(--c-in); }
.sh-pill--ember { --sh-tone: var(--accent); }
.sh-pill--red   { --sh-tone: var(--danger); }
.sh-pill--plum  { --sh-tone: var(--c-out); }

.sh-swatch--grey  { --sh-tone: var(--muted); }
.sh-swatch--blue  { --sh-tone: #2F6FB0; }
.sh-swatch--green { --sh-tone: var(--c-in); }
.sh-swatch--ember { --sh-tone: var(--accent); }
.sh-swatch--red   { --sh-tone: var(--danger); }
.sh-swatch--plum  { --sh-tone: var(--c-out); }

/* ---- masthead + blurb --------------------------------------
   No stat cards and no footer card by design: a sheet is the
   table, with just enough words above it to say what it is. */
.sh-head { margin-bottom: 1.5rem; }
.sh-title { font-size: 1.8rem; font-weight: 700; letter-spacing: -.015em; margin: 0 0 .45rem; }
.sh-blurb { color: var(--ink-soft); font-size: .92rem; margin: 0; max-width: 68ch; }

/* ---- the table ---------------------------------------------- */
.sh-tablewrap { overflow-x: auto; }
/* SEPARATE, NOT COLLAPSE, AND IT HAS TO STAY THAT WAY.
   `position: sticky` on a <th> is broken under `border-collapse: collapse`: the
   cell is painted against the table's collapsed border grid rather than as a box
   of its own, so a row scrolling underneath the header is not cleanly clipped by
   it — it bleeds through, which reads as the header cropping into the first row.
   Firefox is where it shows worst.

   The header's disappearing bottom border (worked around below with an inset
   box-shadow) was the same bug's other face and was fixed one symptom at a time.

   Safe to switch because no edge in this table is drawn twice: the vertical
   hairline is `border-right` on the cell, the horizontal one is `border-bottom`
   on .sh-td only, and the header's rule is a shadow. With border-spacing: 0 the
   grid renders pixel-identically — go back to `collapse` and the overlap returns. */
.sh-table { width: 100%; border-collapse: separate; border-spacing: 0; }
.sh-th { font-family: var(--mono); font-size: .6rem; text-transform: uppercase; letter-spacing: .1em;
    font-weight: 700; color: var(--muted); text-align: left; padding: .6rem .7rem .5rem;
    border-bottom: 1px solid var(--line); white-space: nowrap; vertical-align: top; position: relative; }
.sh-th--n, .sh-td--n { width: 2.2rem; }
.sh-th--grip, .sh-td--grip { width: 1.6rem; }
.sh-th--number, .sh-td--number { text-align: right; }
.sh-th--tools, .sh-td--tools { width: 1%; white-space: nowrap; text-align: right; }
.sh-th.is-sorted { color: var(--accent); }

.sh-td { padding: .5rem .7rem; border-bottom: 1px solid var(--line); font-size: .88rem;
    vertical-align: middle; }
.sh-table tbody tr:last-child .sh-td { border-bottom: 0; }
.sh-td--n { font-family: var(--mono); font-size: .72rem; color: var(--muted);
    font-variant-numeric: tabular-nums; }
.sh-td--name { font-weight: 500; white-space: nowrap; }
.sh-td--mono { font-family: var(--mono); font-size: .76rem; color: var(--ink-soft); }
.sh-td--number { font-family: var(--mono); font-size: .82rem; font-variant-numeric: tabular-nums; }
.sh-td--tag, .sh-td--yesno, .sh-td--image { white-space: nowrap; }

/* Sorting. On the client's page the whole header is the button; in the editor
   the words are for renaming and only the arrow sorts. */
.sh-sort { background: none; border: 0; cursor: pointer; font: inherit; color: inherit;
    padding: 0; letter-spacing: inherit; }
.sh-sort--head { display: inline-flex; align-items: center; gap: .4rem; text-transform: inherit; }
.sh-sortmark, .sh-sort { color: var(--line-strong); }
.sh-th.is-sorted .sh-sortmark, .sh-th.is-sorted .sh-sort { color: var(--accent); }
.sh-sort:hover, .sh-sort--head:hover { color: var(--accent); }
.sh-sort--head:hover .sh-sortmark { color: var(--accent); }

/* ---- tags --------------------------------------------------- */
.sh-pill { display: inline-block; font-family: var(--mono); font-size: .62rem; font-weight: 700;
    text-transform: uppercase; letter-spacing: .09em; padding: .2rem .5rem; white-space: nowrap;
    border: 1px solid color-mix(in srgb, var(--sh-tone, var(--muted)) 40%, var(--line));
    background: color-mix(in srgb, var(--sh-tone, var(--muted)) 11%, var(--panel));
    color: var(--sh-tone, var(--ink-soft)); }
.sh-pill--empty { border-color: transparent; background: none; color: var(--line-strong); }

.sh-yn { font-family: var(--mono); font-size: .74rem; font-weight: 600;
    background: none; border: 0; padding: 0; }
.sh-yn--ok { color: var(--c-in); } .sh-yn--bad { color: var(--danger); } .sh-yn--nil { color: var(--line-strong); }

/* ---- pictures ----------------------------------------------- */
.sh-img { display: block; max-height: 34px; max-width: 120px; width: auto; height: auto;
    object-fit: contain; }
.sh-imglink { display: inline-block; }
.sh-td--image { padding-top: .35rem; padding-bottom: .35rem; }

/* ---- links -------------------------------------------------- */
.sh-link { color: var(--accent); text-decoration: underline; text-underline-offset: 2px; }
.sh-link:hover { color: var(--accent-glow); }

/* ---- highlighted rows ---------------------------------------
   A wash across the whole row and a mark down its left edge,
   drawn from the same tone palette the tags use so a sheet only
   ever has one set of colours in it. Kept deliberately light:
   the row still has to be read, and every cell inside it keeps
   its own type's colouring on top. Shown on the client's copy
   too — a highlight is something you are saying to them. */
.sh-mark--grey  { --sh-mark: var(--muted); }
.sh-mark--blue  { --sh-mark: #2F6FB0; }
.sh-mark--green { --sh-mark: var(--c-in); }
.sh-mark--ember { --sh-mark: var(--accent); }
.sh-mark--red   { --sh-mark: var(--danger); }
.sh-mark--plum  { --sh-mark: var(--c-out); }

.sh-row--marked > .sh-td { background: color-mix(in srgb, var(--sh-mark) 11%, transparent); }
.sh-row--marked > .sh-td:first-child { box-shadow: inset 2px 0 0 var(--sh-mark); }
.sh-row--marked > .sh-td--n { color: var(--sh-mark); }

/* ============================================================
   EDIT MODE
   ============================================================ */

.sh-editor { position: relative; }
.sh-canvas { background: var(--panel); border: 1px solid var(--line); padding: 1.8rem 2rem 2.2rem; }

.sh-ce { outline: none; cursor: text; transition: background .12s, box-shadow .12s; }
.sh-ce:hover { background: color-mix(in srgb, var(--accent) 6%, transparent); }
.sh-ce:focus { background: color-mix(in srgb, var(--accent) 9%, transparent);
    box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent) 45%, transparent); }
.sh-ce:empty::before { content: attr(data-ph); color: var(--muted); font-weight: 400; }

/* Column header controls: the name on one line, what the column holds and the
   move/remove controls under it — the second line only surfacing on hover, so a
   sheet you are reading isn't covered in machinery. */
.sh-thline { display: flex; align-items: center; gap: .35rem; }
.sh-thlabel { display: inline-block; min-width: 2rem; }
.sh-thtools { display: flex; align-items: center; gap: .3rem; margin-top: .25rem;
    opacity: 0; transition: opacity .12s; }
.sh-th:hover .sh-thtools, .sh-th:focus-within .sh-thtools { opacity: 1; }
.sh-coltype, .sh-colopts { background: none; border: 0; cursor: pointer; padding: 0;
    font-family: var(--mono); font-size: .52rem; text-transform: uppercase; letter-spacing: .1em;
    font-weight: 700; color: var(--line-strong); }
.sh-coltype:hover, .sh-colopts:hover { color: var(--accent); }
.sh-colopts { color: var(--muted); }
.sh-thmove { display: inline-flex; gap: .1rem; margin-left: .2rem; }
.sh-colmove, .sh-coldel, .sh-coladd { background: none; border: 0; cursor: pointer; font: inherit;
    font-size: .78rem; color: var(--line-strong); padding: 0 .12rem; }
.sh-colmove:hover, .sh-coladd:hover { color: var(--accent); }
.sh-coldel:hover { color: var(--danger); }

/* The categories panel — a tag column's own vocabulary, editable in place.
   Fixed, not absolute: the table scrolls sideways inside .sh-tablewrap, and an
   absolutely-positioned panel would be clipped by that scroll box the moment the
   column it belongs to is anywhere near the edge. JS sets top/left on open. */
.sh-optpanel { position: fixed; z-index: 45; min-width: 12rem;
    background: var(--panel); border: 1px solid var(--line-strong);
    box-shadow: 0 6px 20px rgba(20,24,30,.14); padding: .5rem; }
.sh-optpanel[hidden] { display: none; }
.sh-optrow { display: flex; align-items: center; gap: .4rem; padding: .18rem 0; }
.sh-swatch { width: 13px; height: 13px; flex: none; cursor: pointer; padding: 0;
    border: 1px solid color-mix(in srgb, var(--sh-tone, var(--muted)) 55%, var(--line));
    background: color-mix(in srgb, var(--sh-tone, var(--muted)) 55%, var(--panel)); }
.sh-optlabel { flex: 1; min-width: 3rem; font-family: var(--font); font-size: .78rem;
    text-transform: none; letter-spacing: 0; font-weight: 400; color: var(--ink); }
.sh-optdel { background: none; border: 0; cursor: pointer; color: var(--line-strong); font-size: .8rem; padding: 0 .1rem; }
.sh-optdel:hover { color: var(--danger); }
.sh-optadd { display: block; width: 100%; text-align: left; margin-top: .3rem; padding: .3rem .1rem 0;
    background: none; border: 0; border-top: 1px solid var(--line); cursor: pointer;
    font-family: var(--mono); font-size: .58rem; text-transform: uppercase; letter-spacing: .1em;
    font-weight: 700; color: var(--muted); }
.sh-optadd:hover { color: var(--accent); }

/* The tag picker, floated to whichever cell was clicked. Fixed for the same
   reason as the panel above — and so its coordinates are viewport coordinates
   rather than "wherever .sh-editor happens to start". */
.sh-tagmenu { position: fixed; z-index: 50; min-width: 9rem; background: var(--panel);
    border: 1px solid var(--line-strong); box-shadow: 0 6px 20px rgba(20,24,30,.14); padding: .3rem; }
.sh-tagmenu[hidden] { display: none; }
.sh-opt { display: block; width: 100%; text-align: left; background: none; border: 0;
    cursor: pointer; padding: .22rem .3rem; }
.sh-opt:hover { background: var(--wash); }
.sh-opt--clear { font-family: var(--mono); font-size: .58rem; text-transform: uppercase;
    letter-spacing: .1em; font-weight: 700; color: var(--muted); border-top: 1px solid var(--line);
    margin-top: .2rem; padding-top: .35rem; }
.sh-tagmenu-new { border-top: 1px solid var(--line); margin-top: .2rem; padding-top: .3rem; }
.sh-tagmenu-input { width: 100%; font: inherit; font-size: .78rem; padding: .25rem .3rem;
    border: 1px solid var(--line); background: var(--panel); color: var(--ink); }
.sh-tagmenu-input:focus { outline: none; border-color: var(--accent); }

.sh-pillbtn, .sh-ynbtn, .sh-imgbtn { background: none; border: 0; padding: 0; cursor: pointer; font: inherit; }
.sh-imgbtn { display: block; min-width: 2rem; }
.sh-imgadd { display: inline-flex; align-items: center; justify-content: center;
    width: 30px; height: 30px; border: 1px dashed var(--line-strong); color: var(--line-strong);
    font-size: .8rem; }
.sh-imgbtn:hover .sh-imgadd { border-color: var(--accent); color: var(--accent); }
.sh-linklabel { display: inline-block; min-width: 3rem; }
.sh-linkedit { background: none; border: 0; cursor: pointer; margin-left: .3rem;
    font-family: var(--mono); font-size: .56rem; text-transform: uppercase; letter-spacing: .08em;
    font-weight: 700; color: var(--line-strong); }
.sh-linkedit:hover { color: var(--accent); }

/* Row handle + tools. */
.sh-grip { display: inline-block; cursor: grab; color: var(--line-strong); font-size: .8rem;
    line-height: 1; user-select: none; touch-action: none; opacity: 0; transition: opacity .12s; }
.sh-table tr:hover .sh-grip { opacity: 1; }
.sh-grip:active { cursor: grabbing; }
.sh-table tr.is-dragging { background: color-mix(in srgb, var(--accent) 8%, var(--panel));
    outline: 1px solid color-mix(in srgb, var(--accent) 40%, transparent); }
.sh-rowbtn { background: none; border: 0; cursor: pointer; font: inherit; font-size: .8rem;
    color: var(--line-strong); padding: 0 .15rem; opacity: 0; transition: opacity .12s, color .12s; }
.sh-table tr:hover .sh-rowbtn, .sh-rowbtn:focus { opacity: 1; }
.sh-rowbtn:hover { color: var(--accent); }
.sh-rowbtn--del:hover { color: var(--danger); }
/* The highlight dot stays lit on a row that has one — the only row tool that
   reports a state rather than just doing a thing. */
.sh-rowbtn--mark { font-size: .58rem; vertical-align: middle; }
.sh-rowbtn--mark.is-on { opacity: 1; color: var(--sh-mark, var(--accent)); }

/* The highlight picker: swatches only. What a colour MEANS is the sheet's
   business (say it in the blurb, or use a tag column) — this only says which
   rows to look at first. */
.sh-markmenu { min-width: 8.4rem; }
.sh-markmenu-h { font-family: var(--mono); font-size: .55rem; text-transform: uppercase;
    letter-spacing: .1em; font-weight: 700; color: var(--muted); padding: .15rem .3rem .3rem; }
.sh-markmenu-row { display: flex; gap: .3rem; padding: 0 .3rem .3rem; }
.sh-markopt { width: 18px; height: 18px; flex: none; cursor: pointer; padding: 0;
    border: 1px solid color-mix(in srgb, var(--sh-tone, var(--muted)) 55%, var(--line));
    background: color-mix(in srgb, var(--sh-tone, var(--muted)) 45%, var(--panel)); }
.sh-markopt:hover { background: color-mix(in srgb, var(--sh-tone, var(--muted)) 70%, var(--panel)); }
.sh-markopt.is-on { outline: 1px solid var(--ink); outline-offset: 2px; }

.sh-adds { display: flex; gap: .5rem; flex-wrap: wrap; margin-top: .8rem; }
.sh-add { background: none; border: 1px dashed var(--line-strong); cursor: pointer;
    font-family: var(--mono); font-size: .62rem; text-transform: uppercase; letter-spacing: .1em;
    font-weight: 700; color: var(--muted); padding: .42rem .8rem; }
.sh-add:hover { color: var(--accent); border-color: var(--accent); border-style: solid; }
/* A setting, not an action — so it stands at the far end of the row, away from
   the two buttons that DO something, and shows its state rather than a verb. */
.sh-numlock { margin-left: auto; border-style: solid; border-color: var(--line); }
.sh-numlock.is-on { color: var(--accent); border-color: var(--accent);
    background: color-mix(in srgb, var(--accent) 7%, transparent); }

/* ---- paste a table in --------------------------------------- */
.sh-paste { margin-top: 1rem; border: 1px solid var(--line); background: var(--bg); padding: 1rem 1.1rem; }
.sh-paste[hidden] { display: none; }
.sh-paste-h { font-family: var(--mono); font-size: .62rem; text-transform: uppercase;
    letter-spacing: .12em; font-weight: 700; color: var(--accent); margin-bottom: .4rem; }
.sh-paste-p { font-size: .82rem; color: var(--ink-soft); margin: 0 0 .6rem; max-width: 62ch; }
.sh-paste-t { width: 100%; font-family: var(--mono); font-size: .76rem; padding: .6rem;
    border: 1px solid var(--line-strong); background: var(--panel); color: var(--ink); resize: vertical; }
.sh-paste-t:focus { outline: none; border-color: var(--accent); }
.sh-paste-a { display: flex; gap: .5rem; flex-wrap: wrap; margin-top: .6rem; }
.sh-btn { cursor: pointer; font: inherit; font-family: var(--mono); font-size: .66rem;
    font-weight: 600; padding: .38rem .8rem; background: var(--panel); color: var(--ink);
    border: 1px solid var(--line-strong); }
.sh-btn:hover { border-color: var(--accent); color: var(--accent); }
.sh-btn--go { background: var(--accent); border-color: var(--accent); color: #fff; }
.sh-btn--go:hover { background: var(--accent-glow); border-color: var(--accent-glow); color: #fff; }

/* ---- the save bar ------------------------------------------- */
.sh-bar { position: sticky; top: 0; z-index: 30; display: flex; align-items: center; gap: .7rem;
    background: var(--ink); color: #fff; padding: .55rem .9rem; margin-bottom: 1.1rem; }
.sh-bar-back { color: var(--accent-glow); font-family: var(--mono); font-size: .72rem; }
.sh-bar-back:hover { color: #fff; }
.sh-bar-mid { flex: 1; min-width: 0; display: flex; align-items: center; gap: .8rem; }
.sh-bar-hint { font-family: var(--mono); font-size: .62rem; letter-spacing: .06em;
    color: rgba(255,255,255,.45); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.sh-bar-dirty { display: none; font-family: var(--mono); font-size: .62rem; text-transform: uppercase;
    letter-spacing: .1em; font-weight: 700; color: var(--accent-glow); }
.sh-bar.is-dirty .sh-bar-dirty { display: inline; }
.sh-bar.is-dirty .sh-bar-hint { display: none; }
.sh-bar-saved { font-family: var(--mono); font-size: .62rem; text-transform: uppercase;
    letter-spacing: .1em; font-weight: 700; color: #7FD1A8; }
/* Starring the sheet for the client whose page it belongs on. Sits with the
   document's own marks rather than the actions — it says what this sheet IS to
   that client, not what to do with it now. */
.sh-bar-star { background: none; border: 0; cursor: pointer; padding: 0 .1rem; line-height: 1;
    font-size: 1rem; color: rgba(255,255,255,.4); transition: color .12s; }
.sh-bar-star:hover { color: var(--gold); }
.sh-bar-star.is-on { color: var(--gold); }
/* Shown only while a sheet belongs to nobody — the star stands where it was. */
.sh-bar-file { font-family: var(--mono); font-size: .62rem; max-width: 11rem;
    padding: .3rem .4rem; background: transparent; color: rgba(255,255,255,.8);
    border: 1px solid rgba(255,255,255,.25); }
.sh-bar-file:hover, .sh-bar-file:focus { border-color: #fff; color: #fff; }
.sh-bar-file option { color: var(--ink); background: #fff; }
.sh-bar-link { color: #fff; font-family: var(--mono); font-size: .68rem; white-space: nowrap; }
.sh-bar-link:hover { color: var(--accent-glow); }
.sh-bar-btn { cursor: pointer; font: inherit; font-family: var(--mono); font-size: .68rem;
    font-weight: 600; padding: .38rem .8rem; white-space: nowrap;
    background: transparent; color: #fff; border: 1px solid rgba(255,255,255,.35); }
.sh-bar-btn:hover { border-color: #fff; }
.sh-bar-btn--go { background: var(--accent); border-color: var(--accent); }
.sh-bar-btn--go:hover { background: var(--accent-glow); border-color: var(--accent-glow); }

.sh-sharenote { display: flex; align-items: center; gap: .6rem; flex-wrap: wrap; margin-top: 1rem;
    padding: .7rem .9rem; border: 1px solid var(--line); background: var(--panel); font-size: .78rem; }
.sh-sharenote-l { font-family: var(--mono); font-size: .6rem; text-transform: uppercase;
    letter-spacing: .12em; font-weight: 700; color: var(--accent); }
.sh-sharenote-url { font-family: var(--mono); font-size: .72rem; color: var(--ink-soft); word-break: break-all; }

/* ---- preview -------------------------------------------------
   The client's page, still standing in the editor. Nothing is
   re-rendered: the same DOM loses its scaffolding, so a status
   you change while reading is a change to the real document and
   Save behaves exactly as it does on the other side of the toggle.

   What goes: everything that is a tool rather than the page —
   grips, column tools, the add row/column buttons, the row's
   duplicate/delete/highlight. What stays: the cells, because
   being unable to fix the thing you just spotted is the reason
   you'd close the tab.                                        */
/* Scoped to .sh-editor so these outrank the client-page rules further down the
   file, which set .sh-brand to flex at the same specificity and would otherwise
   win on source order alone. */
.sh-editor .sh-brand--preview, .sh-editor .sh-sent--preview { display: none; }
.sh-bar-hint--preview { display: none; }

.sh-editor.is-preview .sh-brand--preview { display: flex; }
.sh-editor.is-preview .sh-sent--preview  { display: block; }
.sh-editor.is-preview .sh-bar-hint       { display: none; }
.sh-editor.is-preview .sh-bar-hint--preview { display: inline; }
.sh-bar.is-dirty .sh-bar-hint--preview   { display: none; }
.sh-bar-preview.is-on { background: #fff; border-color: #fff; color: var(--ink); }

.sh-editor.is-preview .sh-canvas { max-width: 1100px; margin: 0 auto; padding: 1.4rem 1.5rem 3rem; }

/* The scaffolding doesn't go away — it stops taking up room and stops
   advertising itself. The grip and the row tools keep their cells but collapse
   to nothing and spill into the page margin, so the table measures exactly as
   the client's copy does and NOTHING SHIFTS when they appear on hover. That
   matters more than it sounds: a preview whose columns jump the moment you
   reach for a row is not showing you the page. */
.sh--preview .sh-th--grip,  .sh--preview .sh-td--grip,
.sh--preview .sh-th--tools, .sh--preview .sh-td--tools {
    width: 0; padding-left: 0; padding-right: 0; position: relative; }
/* Out of flow, both sides: a table cell sizes itself to its contents, so
   width:0 only bites once nothing is left inside to measure. */
.sh--preview .sh-td--grip .sh-grip {
    position: absolute; right: .25rem; top: 50%; transform: translateY(-50%); }
/* Anchored to the INSIDE of the table's right edge, not hanging off it. An
   absolutely-positioned box still counts toward its scroll container's width,
   so tools sitting past the last column gave the table a sideways scrollbar it
   had no other reason for — under a table that fits the window. They overlay
   the last column on hover instead, which is where row actions live anyway. */
.sh--preview .sh-td--tools .sh-rowtools {
    position: absolute; right: .15rem; top: 50%; transform: translateY(-50%);
    display: flex; align-items: center; white-space: nowrap; }
.sh--preview .sh-th--tools .sh-coladd {
    position: absolute; right: .15rem; bottom: .35rem; opacity: 0; transition: opacity .12s; }
.sh--preview .sh-table:hover .sh-th--tools .sh-coladd { opacity: 1; }

/* A highlighted row keeps its dot lit in the editor, to report the state. In
   preview the wash across the row already says that, so the dot stays with the
   rest of the tools until you reach for it. */
.sh--preview .sh-rowbtn--mark.is-on { opacity: 0; }
.sh--preview .sh-table tr:hover .sh-rowbtn--mark.is-on,
.sh--preview .sh-table tr:focus-within .sh-rowbtn--mark.is-on,
.sh--preview .sh-table tr.is-open .sh-rowbtn--mark.is-on { opacity: 1; }

/* Touch has no hover, so a tapped row opens its own tools (see sheet.js). */
.sh--preview .sh-table tr:focus-within .sh-grip,
.sh--preview .sh-table tr.is-open .sh-grip,
.sh--preview .sh-table tr:focus-within .sh-rowbtn,
.sh--preview .sh-table tr.is-open .sh-rowbtn { opacity: 1; }

/* Building the table is still edit mode's job — what a column HOLDS and which
   categories it allows are decisions you make while building, not while
   reading. The column's name is not: renaming a field is reading work. */
.sh--preview .sh-thtools, .sh--preview .sh-optpanel { display: none; }

/* Add a row, paste a table, set a link: there when you're working in the page,
   gone when you're looking at it. */
.sh--preview .sh-adds { opacity: 0; transition: opacity .12s; }
.sh--preview .sh:hover .sh-adds, .sh--preview.sh:hover .sh-adds,
.sh--preview .sh-adds:focus-within { opacity: 1; }
.sh--preview .sh-linkedit { opacity: 0; transition: opacity .12s; }
.sh--preview .sh-td--link:hover .sh-linkedit,
.sh--preview .sh-td--link:focus-within .sh-linkedit { opacity: 1; }
.sh--preview .sh-td--link:hover::after,
.sh--preview .sh-td--link:focus-within::after { opacity: .5; }

/* an empty title or blurb is a prompt to you, not a line for them */
.sh--preview .sh-title:empty, .sh--preview .sh-blurb:empty { display: none; }
.sh--preview .sh-ce:empty::before { content: none; }

/* A link with no label reads as its address on the client's copy. Fed by an
   attribute rather than written into the span, so previewing and then saving
   can't quietly turn a stand-in into a label you never typed. */
.sh--preview .sh-td--link[data-fallback]:not([data-fallback=""])::after {
    content: attr(data-fallback); color: var(--accent); }
/* With no label of its own that span is empty, and its 3rem click-target pushed
   the address a good inch right of the heading it sits under — the one column in
   the table that didn't line up. Reading doesn't need the target: in preview the
   label isn't editable and the whole cell is the link. So it collapses, and the
   address starts where every other column starts. */
.sh--preview .sh-td--link[data-fallback]:not([data-fallback=""]) .sh-linklabel { min-width: 0; }

/* cells keep working — the hover just stops shouting */
.sh--preview .sh-ce:hover { background: color-mix(in srgb, var(--accent) 3%, transparent); }
.sh--preview .sh-ce:focus { background: color-mix(in srgb, var(--accent) 6%, transparent); box-shadow: none; }
/* Preview reads the sheet as the client does, and the client can
   open a picture — so the click stays live here. It was killed
   back when clicking one opened the file PICKER, which is not
   something their copy has. The ＋ on an empty cell still goes:
   there is nothing there for them to look at. */
.sh--preview .sh-imgbtn { border: 0; background: transparent; padding: 0; }
.sh--preview .sh-imgbtn:has(.sh-imgadd) { pointer-events: none; }
.sh--preview .sh-imgadd { display: none; }

/* ============================================================
   THE CLIENT'S PAGE
   ============================================================ */
.sh-page { font-family: var(--font); color: var(--ink); }
.sh-shell { max-width: 1100px; margin: 0 auto; padding: 2.5rem 1.5rem 4rem; }
.sh-brand { display: flex; align-items: center; gap: .7rem; margin-bottom: 2.2rem;
    padding-bottom: 1rem; border-bottom: 1px solid var(--line); }
.sh-brand-mark { width: 34px; height: 34px; flex: none; overflow: hidden; background: var(--ink); }
.sh-brand-mark img, .sh-brand-mark svg { width: 100%; height: 100%; object-fit: cover; display: block; }
.sh-brand-name { font-weight: 600; font-size: .92rem; }
.sh-brand-date { margin-left: auto; font-family: var(--mono); font-size: .68rem;
    letter-spacing: .06em; color: var(--muted); }
.sh-sent { margin-top: 2.6rem; font-family: var(--mono); font-size: .64rem; text-transform: uppercase;
    letter-spacing: .12em; color: var(--muted); text-align: center; }

/* ---- phones ------------------------------------------------- */
@media (max-width: 760px) {
    .sh-canvas { padding: 1.1rem .9rem 1.6rem; }
    .sh-shell { padding: 1.6rem 1rem 3rem; }
    .sh-title { font-size: 1.4rem; }
    .sh-bar { flex-wrap: wrap; gap: .5rem; padding: .5rem .7rem; }
    .sh-bar-mid { order: 3; flex-basis: 100%; }
    .sh-bar-hint { display: none; }
    .sh-td, .sh-th { padding-left: .45rem; padding-right: .45rem; }
    .sh-td { font-size: .82rem; }
    /* No hover on touch — the handle and row tools have to be reachable. */
    .sh-grip, .sh-rowbtn { opacity: 1; }
    .sh-thtools { opacity: 1; }
    /* …except in preview, where always-on tools would be the whole point
       missed. Tap a row and that row opens its own (sheet.js sets .is-open). */
    .sh--preview .sh-grip, .sh--preview .sh-rowbtn { opacity: 0; }
    .sh--preview .sh-table tr.is-open .sh-grip,
    .sh--preview .sh-table tr.is-open .sh-rowbtn { opacity: 1; }
    .sh--preview .sh-adds { opacity: 1; }
}

/* "New sheet" doors — the client's Documents panel, and the Documents hub.
   A sheet is the only document started from nothing rather than generated off a
   project or a posting, so it is the only one that needs a button. */

.dh-new .dh-newform { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; }
.dh-new .dh-newlbl { font-family: var(--mono); font-size: .6rem; text-transform: uppercase;
    letter-spacing: .1em; font-weight: 700; color: var(--muted); }
.dh-new .dh-newsel { flex: 1; min-width: 9rem; font: inherit; font-size: .84rem;
    padding: .4rem .5rem; border: 1px solid var(--line); border-radius: 0;
    background: var(--panel); color: var(--ink); }
.dh-new .dh-newsel:focus { outline: none; border-color: var(--accent); }

/* ---- What a column holds: a readable list, not a guessing game -------
   This control used to cycle on click, which meant the only way to find
   out a column could hold a PICTURE was to click "Text" seven times and
   watch what happened. It's a menu now, and the badge showing the current
   type stays visible rather than waiting for a hover — it is the label
   that tells you the control is there at all. */
.sh-coltype { opacity: 1; border-bottom: 1px dotted var(--line-strong); }
.sh-th .sh-thtools { opacity: 1; }
.sh-thmove { opacity: 0; transition: opacity .12s; }
.sh-th:hover .sh-thmove, .sh-th:focus-within .sh-thmove { opacity: 1; }

.sh-typemenu { position: fixed; z-index: 55; min-width: 15rem; background: var(--panel);
    border: 1px solid var(--line-strong); box-shadow: 0 6px 20px rgba(20,24,30,.14); padding: .3rem; }
.sh-typemenu[hidden] { display: none; }
.sh-typemenu-h { font-family: var(--mono); font-size: .56rem; text-transform: uppercase;
    letter-spacing: .12em; font-weight: 700; color: var(--muted); padding: .3rem .5rem .35rem; }
.sh-typeopt { display: flex; align-items: baseline; gap: .5rem; width: 100%; text-align: left;
    background: none; border: 0; cursor: pointer; padding: .34rem .5rem; font: inherit; }
.sh-typeopt:hover { background: var(--wash); }
.sh-typeopt.is-on { background: color-mix(in srgb, var(--accent) 10%, transparent); }
.sh-typeopt.is-on .sh-typeopt-n { color: var(--accent); }
.sh-typeopt-n { flex: none; min-width: 4.2rem; font-size: .82rem; font-weight: 600;
    text-transform: none; letter-spacing: 0; color: var(--ink); }
.sh-typeopt-d { font-size: .74rem; color: var(--muted); text-transform: none; letter-spacing: 0;
    font-weight: 400; }

/* An empty picture cell should look like somewhere a picture goes. */
.sh-imgadd { width: 34px; height: 26px; font-size: .9rem; }


/* ============================================================
   SHARED NOTE (snote-)  —  a published note on its own page.

   Same arrangement as the client's status report and sheet: the
   app's own ground, hairlines and type, with none of the chrome.
   Every colour reads from the palette tokens, so this page moves
   with the app instead of staying behind on the old document
   cream-and-gold. Its own prefix, because .sh- is the Sheet's
   (and the nav shelf's) namespace.
   ============================================================ */
.snote-page { font-family: var(--font); color: var(--ink); background: var(--bg); }
.snote-shell { max-width: 760px; margin: 0 auto; padding: 2.5rem 1.5rem 4rem; }
.snote-brand { display: flex; align-items: center; gap: .7rem; margin-bottom: 2.2rem;
    padding-bottom: 1rem; border-bottom: 1px solid var(--line); }
.snote-brand-mark { width: 34px; height: 34px; flex: none; overflow: hidden; background: var(--ink); }
.snote-brand-mark img, .snote-brand-mark svg { width: 100%; height: 100%; object-fit: cover; display: block; }
.snote-brand-name { font-weight: 600; font-size: .92rem; }
.snote-brand-date { margin-left: auto; font-family: var(--mono); font-size: .68rem;
    letter-spacing: .06em; color: var(--muted); }

.snote-eyebrow { font-family: var(--mono); font-size: .6rem; text-transform: uppercase;
    letter-spacing: .12em; font-weight: 700; color: var(--accent); }
.snote-title { font-size: 1.8rem; font-weight: 700; letter-spacing: -.015em; line-height: 1.2;
    margin: .35rem 0 1.4rem; }

.snote-body { background: var(--panel); border: 1px solid var(--line); padding: 1.7rem 1.9rem 1.9rem; }
.snote-body > :first-child { margin-top: 0; }
.snote-body p { margin: .65rem 0; color: var(--ink-soft); }
.snote-body img { max-width: 100%; height: auto; border: 1px solid var(--line); }
.snote-body code { background: var(--wash); color: var(--ink); font-family: var(--mono);
    font-size: .86em; padding: .1em .4em; }
.snote-body pre.md-code code { background: none; color: #e8e6df; padding: 0; }
.snote-body a { color: var(--accent); text-decoration: underline; text-underline-offset: 2px; }
.snote-body a:hover { color: var(--accent-glow); }

.snote-sent { margin-top: 2.6rem; font-family: var(--mono); font-size: .64rem; text-transform: uppercase;
    letter-spacing: .12em; color: var(--muted); text-align: center; }

@media (max-width: 760px) {
    .snote-shell { padding: 1.6rem 1rem 3rem; }
    .snote-body { padding: 1.1rem .95rem 1.5rem; }
    .snote-title { font-size: 1.4rem; }
}

/* "Catch me up" on a phone. The facts strip goes to two columns rather than
   wrapping into a ragged list, and the ask chips stop trying to be one line. */
@media (max-width: 620px) {
    .brf-facts { display: grid; grid-template-columns: 1fr 1fr; gap: .35rem .8rem; }
    .brf-fact { display: block; }
    .brf-n { display: block; }
    .brf-meta-acts { margin-left: 0; }
    .brf-chip { flex: 1 1 100%; }
    /* Section labels stack above their line — a 7.5rem left column on a 375px
       screen leaves the text in a gutter. */
    .brf-sec { display: block; padding: .45rem 0; }
    .brf-sec-k { display: block; margin-bottom: .1rem; }
    /* The shut line drops the date rather than wrapping to two rows; what
       matters small is the spark, the name, and whether anything has moved. */
    .brf-sum-when { display: none; }
}

/* Contacts on a phone. Four columns won't fit 375px, so the roster row folds to
   the block it always wanted to be small: who, then email, then phone, with the
   actions parked at the right of the whole thing rather than under it. */
@media (max-width: 620px) {
    .cc-list .cc-view {
        grid-template-columns: minmax(0, 1fr) 3.2rem;
        grid-template-areas: "who act" "mail act" "tel act";
        align-items: start; padding: .55rem 0;
    }
    .cc-email { grid-area: mail; }
    .cc-phone { grid-area: tel; }
    .cc-actions { grid-area: act; align-self: center; }
    .cc-who { grid-area: who; flex-wrap: wrap; }
    .cc-role { flex-basis: 100%; }
    .cc-role::before { content: none; }
    .cc-filter-in { font-size: 16px; }   /* below 16px iOS zooms the page on focus */
}

/* ============================================================
   Client habits — the same people, by how they behave.
   A ledger table: hairlines, mono numerals, no boxes. Green and
   ember carry the same meaning they do on the brief card, so a
   fast payer looks the same here as on their own page.
   ============================================================ */
.hab-lede { max-width: 62ch; color: var(--ink-soft); font-size: .88rem;
    margin: 0 0 1.2rem; }
.hab-wrap { overflow-x: auto; }
.hab { width: 100%; border-collapse: collapse; font-size: .86rem; }
.hab th { text-align: left; font-size: .68rem; text-transform: uppercase;
    letter-spacing: .07em; color: var(--muted); font-weight: 600;
    padding: 0 0 .5rem; border-bottom: 1px solid var(--ink); white-space: nowrap; }
.hab th.hab-num, .hab td.hab-num { text-align: right; }
.hab td { padding: .55rem 0; border-bottom: 1px solid var(--line); vertical-align: baseline; }
.hab tbody tr:hover { background: color-mix(in srgb, var(--accent) 4%, transparent); }
.hab th + th, .hab td + td { padding-left: 1.4rem; }

.hab-who a { display: inline-flex; align-items: center; gap: .5rem; color: var(--ink); }
.hab-who a:hover .hab-name { color: var(--accent); }
.hab-logo { width: 20px; height: 20px; flex: none; object-fit: contain; }
.hab-logo--mono { display: inline-flex; align-items: center; justify-content: center;
    background: var(--wash); color: var(--ink-soft); font-size: .68rem; font-weight: 600; }
.hab-name { font-weight: 500; }
.hab-tag { margin-left: .4rem; font-size: .68rem; text-transform: uppercase;
    letter-spacing: .06em; color: var(--muted); }

.hab-v { font-family: var(--mono); display: block; }
.hab-v.is-good { color: var(--ok); }
.hab-v.is-warn { color: var(--accent); }
.hab-sub { display: block; font-size: .7rem; color: var(--muted); white-space: nowrap; }
.hab-sub.hab-good { color: var(--ok); }
.hab-sub.hab-warn { color: var(--accent); }
.hab-none { color: var(--line-strong); }
/* Whose move it is. "Them" is the one that might mean you owe a reply. */
.hab-dir { font-family: var(--mono); display: block; color: var(--ink-soft); }
.hab-dir.is-them { color: var(--accent); }
.hab-foot { margin-top: 1rem; }

/* What to remember about this client, above the compose box. Ember like every
   other assistant-fed surface, and deliberately small — it's a reminder beside a
   text box, not a second copy of the brief card. */
.em-note { display: flex; gap: .7rem; align-items: baseline; margin: 0 0 .5rem;
    padding: .5rem .65rem; font-size: .82rem;
    border-left: 2px solid var(--accent);
    background: color-mix(in srgb, var(--accent) 4%, transparent); }
.em-note-k { flex: none; font-size: .66rem; text-transform: uppercase;
    letter-spacing: .07em; color: var(--accent); }
.em-note-v { flex: 1; min-width: 0; }
.em-note-v ul { list-style: none; margin: 0; padding: 0; }
.em-note-v li { padding: .1rem 0; color: var(--ink); }
.em-note-gist { margin: .25rem 0 0; color: var(--ink-soft); font-style: italic; }

/* A finished job nobody has written up. Keeps the bar's default ember, because
   ember is what everything assistant-fed wears — the spark in the icon slot says
   the same thing again for anyone scanning by shape rather than colour. */
.needs-ic-svg { width: 15px; height: 15px; display: block; }
.ob-stage--wrapup { color: var(--accent); border-color: color-mix(in srgb, var(--accent) 40%, var(--line));
    background: color-mix(in srgb, var(--accent) 10%, var(--panel)); }
/* The tick here STARTS the write-up rather than marking something done, so it
   hovers ember like the other assistant controls instead of the green that means
   "completed" everywhere else on this feed. */
.needs-bar--wrapup .needs-done-btn:hover {
    border-color: var(--accent); color: var(--accent);
    background: color-mix(in srgb, var(--accent) 10%, var(--panel)); }

/* ============================================================
   THE TODAY PANEL
   ------------------------------------------------------------
   One panel replacing five readouts (Ahead, Came back, Needs you, New client
   mail, Recent activity). Every row uses the same skeleton, which is what lets a
   single swipe mean one thing everywhere — see app/partials/feed_row.php.

   Colour still carries the meaning it carries everywhere else in the app: ember
   is your move, and it is never spent on good news. A payment RECEIVED wearing
   ember would invert the one colour this app is most consistent about, so the
   landed band takes the income green and meetings take the data blue.
   ============================================================ */
.feed { margin: 0 0 2.2rem; border: 1px solid var(--line); background: var(--panel); }
.feed-head { display: flex; align-items: baseline; justify-content: space-between;
    gap: .8rem; padding: .85rem 1rem .6rem; }
.feed-title { margin: 0; font-size: .92rem; display: flex; align-items: baseline; gap: .5rem; }
.feed-date { font-family: var(--mono); font-size: .68rem; letter-spacing: .08em;
    text-transform: uppercase; color: var(--muted); font-weight: 500; }
/* The headline number counts only what wants a decision. Landed rows and meetings
   are awareness — counting them would make a good day read as a busy one. */
.feed-count { font-family: var(--mono); font-size: .68rem; letter-spacing: .06em;
    text-transform: uppercase; color: var(--muted); white-space: nowrap; }

.feed-chips { display: flex; gap: .35rem; padding: 0 1rem .7rem; flex-wrap: wrap; }
.feed-chip { font-family: var(--mono); font-size: .66rem; letter-spacing: .06em;
    text-transform: uppercase; font-weight: 600; padding: .26rem .6rem; border-radius: 2px;
    border: 1px solid var(--line); background: none; color: var(--muted); cursor: pointer;
    display: inline-flex; align-items: center; gap: .35rem; transition: color .12s ease, border-color .12s ease; }
.feed-chip:hover { color: var(--ink); border-color: var(--muted); }
.feed-chip.on { background: var(--ink); color: #fff; border-color: var(--ink); }
.feed-chip-n { font-size: .62rem; opacity: .7; }

.feed-band-head { font-family: var(--mono); font-size: .62rem; letter-spacing: .1em;
    text-transform: uppercase; color: var(--muted); padding: .5rem 1rem .35rem;
    border-top: 1px solid var(--line); background: var(--bg); }

/* One row, one colour, held in --bar. A band recolours its rows by setting a
   single value rather than restating the rules. */
.feed-row { --bar: var(--accent);
    display: grid; grid-template-columns: minmax(0, 1fr) auto auto 24px 24px;
    align-items: center; gap: .3rem .7rem;
    padding: .5rem 1rem; border-top: 1px solid var(--line);
    box-shadow: inset 2px 0 0 var(--bar); background: var(--panel); }
.feed-row:hover { background: color-mix(in srgb, var(--bar) 5%, var(--panel)); }

/* Landed: it happened, and it's good. Green is income everywhere else here. */
.feed-row--approved, .feed-row--signed, .feed-row--paid { --bar: var(--ok); }
.feed-row--paid { --bar: var(--c-in-fill); }        /* brightest: money actually in */
.feed-row--opened { --bar: var(--ink-soft); }       /* mildest: they came and looked */
.feed-row--mail { --bar: var(--c-in); }             /* arrived, wants reading */
.feed-row--draft { --bar: var(--c-out); }           /* written, wants sending */
.feed-row--triage { --bar: var(--c-in); }
/* A meeting is the only row you can't act on, and it wears the data blue rather
   than ember to say so before you've read a word of it. */
.feed-row--meeting { --bar: #2F6FB0; }   /* the blue this app uses for capacity / data */
/* A client waiting on an answer. Ember, the same as the assistant parked on a
   question: both mean a person is stopped until you say one sentence. */
.feed-row--question { --bar: var(--accent); }

/* The answer box: the only place in the feed you type. It spans the whole grid
   as a second line rather than squeezing into the title's track — a box you
   have to aim at is a box you use once. */
.feed-answer { grid-column: 1 / -1; display: flex; gap: .4rem; align-items: center;
    margin: .35rem 0 .1rem; padding-left: calc(20px + .6rem); }
.feed-answer-in { flex: 1; min-width: 0; font: inherit; font-size: .8rem;
    padding: .32rem .5rem; border: 1px solid var(--line); background: var(--bg);
    color: var(--ink); }
.feed-answer-in:focus { outline: none; border-color: var(--bar); }
.feed-answer-go { flex: none; font: inherit; font-size: .76rem; cursor: pointer;
    padding: .32rem .7rem; border: 1px solid var(--line); background: none;
    color: var(--ink-soft); }
.feed-answer-go:hover { color: var(--bar); border-color: color-mix(in srgb, var(--bar) 45%, var(--line)); }
/* The quote is the row here, so it gets the room the one-line meta doesn't. */
.feed-row--question .feed-meta { white-space: normal; color: var(--ink-soft); }

.feed-main { display: flex; align-items: center; gap: .6rem; min-width: 0;
    text-decoration: none; color: var(--ink); }
.feed-mark { flex: none; width: 20px; height: 20px; display: flex; align-items: center;
    justify-content: center; color: var(--bar); font-weight: 700; font-size: .9rem; }
.feed-mark img { width: 20px; height: 20px; object-fit: contain; display: block; }
.feed-ico { width: 15px; height: 15px; display: block; }
.feed-body { min-width: 0; }
.feed-row-title { display: block; font-weight: 600; font-size: .86rem;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.feed-main:hover .feed-row-title { text-decoration: underline; }
.feed-meta { display: block; font-size: .75rem; color: var(--muted);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Your move states the move in the row's own colour; everything else is context. */
.feed-row--pipeline .feed-meta, .feed-row--wrapup .feed-meta,
.feed-row--triage .feed-meta, .feed-row--draft .feed-meta { color: var(--bar); font-weight: 500; }

.feed-piece { display: inline-block; margin-left: .45rem; padding: .06rem .32rem;
    font-family: var(--mono); font-size: .58rem; letter-spacing: .07em; text-transform: uppercase;
    font-weight: 600; color: var(--muted); border: 1px solid var(--line-strong); border-radius: 3px;
    white-space: nowrap; vertical-align: 1px; }

.feed-tag { flex: none; font-family: var(--mono); font-size: .58rem; letter-spacing: .08em;
    text-transform: uppercase; font-weight: 600; padding: .1rem .45rem; border-radius: 2px;
    white-space: nowrap; color: var(--bar);
    /* One width for every tag, right-aligned, so the stage of each row reads
       down a single edge instead of starting at a different x on every line. */
    box-sizing: border-box; min-width: 6.2rem; text-align: center; justify-self: end;
    background: color-mix(in srgb, var(--bar) 12%, transparent);
    border: 1px solid color-mix(in srgb, var(--bar) 30%, transparent); }

/* Empty stand-ins for the cells a given row has nothing to put in. Without
   them the grid closed up and the tag slid right on any row lacking a tick or
   an x, which is exactly the stepping the tags used to do. */
.feed-dots-slot, .feed-tag-slot, .feed-act-slot { display: block; }
.feed-tag-slot { min-width: 6.2rem; }
.feed-act-slot { width: 24px; height: 24px; }
.feed-actform { margin: 0; flex: none; }
.feed-act { width: 24px; height: 24px; border: 1px solid transparent; background: none;
    cursor: pointer; color: var(--muted); font-size: .95rem; line-height: 1;
    display: flex; align-items: center; justify-content: center; border-radius: 2px;
    opacity: 0; transition: opacity .12s ease, color .12s ease, border-color .12s ease; }
.feed-row:hover .feed-act, .feed-act:focus-visible { opacity: 1; }
.feed-act--do:hover { color: var(--ok); border-color: color-mix(in srgb, var(--ok) 45%, var(--line)); }
.feed-act--off:hover { color: var(--ink); border-color: var(--line-strong); }
/* The wrap-up tick starts the assistant rather than marking anything done, so it
   hovers ember like every other assistant control instead of the green that means
   "completed" on every other row here. */
.feed-row--wrapup .feed-act--do:hover { color: var(--accent);
    border-color: color-mix(in srgb, var(--accent) 45%, var(--line)); }

.feed-more { display: block; padding: .5rem 1rem; font-size: .75rem; color: var(--muted);
    border-top: 1px solid var(--line); }
.feed-empty { margin: 0; padding: .9rem 1rem 1.1rem; font-size: .8rem; color: var(--muted);
    border-top: 1px solid var(--line); }

/* Lately: the log, inside the same panel. Keeps the .da-* row styling it already
   had — only the card chrome around it went away. */
.lately { padding: .2rem 0 0; }
.lately-cats { display: flex; gap: .35rem; flex-wrap: wrap; padding: .55rem 1rem; }
.lately .da-list { padding: 0 1rem .4rem; }
.lately .da-empty { padding: .3rem 1rem 1rem; }
/* Each band states its own count, so "one look" really is one look — no chip to
   press to find out whether anything landed. */
.feed-band-n { margin-left: .4rem; opacity: .75; }

/* ---- The gesture ------------------------------------------------------------
   The row slides under the thumb; the coloured edge widens on the side that will
   act, so the direction is legible before you let go. A side with no action gets
   a quarter of the travel (see dash-swipe.js) and no colour, which is what makes
   a refused swipe read as refused rather than as one that didn't take. */
.feed-row { transition: transform .18s ease, background-color .12s ease, box-shadow .12s ease; }
.feed-row.feed-swiping { transition: none; user-select: none; }
.feed-swiping--right { box-shadow: inset 4px 0 0 var(--ink-soft); }
.feed-swiping--left  { box-shadow: inset 4px 0 0 var(--ok); }

/* In flight. Dimmed rather than removed: the action hasn't been confirmed by the
   server yet, and a row that vanishes before then is a row that lied. */
.feed-row--going { opacity: .45; pointer-events: none; }

/* It didn't go through. Saying so matters more here than anywhere else on the
   page — silence would mean believing an invoice was marked sent when it wasn't. */
.feed-row--failed { box-shadow: inset 3px 0 0 var(--danger);
    background: color-mix(in srgb, var(--danger) 8%, var(--panel)); }
.feed-row--failed::after { content: "Didn't save — try again"; font-family: var(--mono);
    font-size: .62rem; letter-spacing: .06em; text-transform: uppercase;
    color: var(--danger); grid-column: 1 / -1; padding-top: .2rem; }

/* Gone, but not yet away. Undo lives IN the row: no thumb travel to a corner
   toast, and no confirm dialog in front of an action that was always reversible. */
.feed-row--gone { display: flex; align-items: center; box-shadow: inset 2px 0 0 var(--line-strong);
    background: var(--bg); }
.feed-undo { display: flex; align-items: center; justify-content: space-between;
    gap: .8rem; width: 100%; }
.feed-undo-txt { font-size: .75rem; color: var(--muted); }
.feed-undo-btn { font-family: var(--mono); font-size: .64rem; letter-spacing: .08em;
    text-transform: uppercase; font-weight: 600; padding: .2rem .55rem; border-radius: 2px;
    border: 1px solid var(--line-strong); background: var(--panel); color: var(--ink);
    cursor: pointer; }
.feed-undo-btn:hover { border-color: var(--ink); }

/* A heavy tick that a swipe asked for but must not press. The button is held
   visible so the tap is one move away, and it wears ember to say "this one is
   real" — see the data-weight rail in dash-swipe.js. */
.feed-row--revealed .feed-act--do { opacity: 1; border-color: var(--accent); color: var(--accent); }

/* ---- The Today panel on a phone ---------------------------------------------
   The gesture REPLACES the buttons rather than sitting beside them: dropping the
   two 24px controls buys back about a third of the row width, which is what lets
   the title and its context line stay on one line each at 375px.

   Scoped to a coarse pointer, not a narrow window — a small desktop window still
   has a mouse and still needs something to click. It's the same test dash-swipe.js
   uses to decide whether to listen at all, so the controls and the gesture can
   never disagree about which one is in charge. */
@media (pointer: coarse) {
    .feed-row { grid-template-columns: minmax(0, 1fr) 0 0; gap: .25rem .55rem;
        padding: .6rem .85rem; }
    .feed-actform, .feed-act, .feed-act-slot { display: none; }
    /* Except the one a swipe deliberately surfaced: a heavy tick is never fired by
       the gesture, so it has to be tappable the moment the gesture asks for it. */
    .feed-row--revealed .feed-actform, .feed-row--revealed .feed-act { display: flex; }
    .feed-row .obdots, .feed-dots-slot { display: none; }   /* the stage tag already says where it is */

    /* Band headers stay put while you scroll past them, so a long list never
       leaves you wondering which kind of thing you're looking at. */
    .feed-band-head { position: sticky; top: 0; z-index: 2; }

    /* Chips scroll sideways rather than wrapping to a second and third row and
       eating the fold. */
    .feed-chips { flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none;
        -webkit-overflow-scrolling: touch; }
    .feed-chips::-webkit-scrollbar { display: none; }
    .feed-chip { flex: none; }

    /* A row is a tap target before it's anything else. */
    .feed-main { min-height: 44px; }
}

/* ---- Meetings on the calendar ------------------------------------------------
   Read from Google Calendar; never written back, and never in the .ics feed.

   A meeting gets a DOT, not a shaded cell. The cell's colour is the app's most
   load-bearing signal — it means "this day is sold" — and a 30-minute call is
   not a booked day. Anything that shaded the cell would quietly inflate how full
   the month looks, which is the one thing this calendar exists to tell you. */
.cal-meets { position: absolute; left: .3rem; bottom: .28rem; display: flex; gap: 2px; }
.cal-meet { width: 4px; height: 4px; border-radius: 50%; background: #2F6FB0; display: block; }
.cal-cell.has-meet { cursor: help; }
.cal-legend .sw.meet { border-radius: 50%; background: #2F6FB0; width: 6px; height: 6px;
    align-self: center; }

/* Leads the rail, so the spacing hangs below it rather than above. */
.cal-meet-rail { margin: 0 0 1.4rem; border: 1px solid var(--line); background: var(--panel); }
.cal-meet-head { display: flex; align-items: baseline; justify-content: space-between;
    gap: .6rem; padding: .7rem .85rem .5rem; border-bottom: 1px solid var(--line); }
.cal-meet-head h2 { margin: 0; font-size: .92rem; }
.cal-meet-n { font-family: var(--mono); font-size: .68rem; color: var(--muted); }
.cal-meet-empty { margin: 0; padding: .8rem .85rem 1rem; font-size: .78rem; color: var(--muted); }
.cal-meet-day + .cal-meet-day { border-top: 1px solid var(--line); }
.cal-meet-date { font-family: var(--mono); font-size: .62rem; letter-spacing: .1em;
    text-transform: uppercase; color: var(--muted); padding: .5rem .85rem .2rem; }
.cal-meet-date.is-today { color: #2F6FB0; font-weight: 600; }
.cal-meet-row { display: flex; align-items: baseline; gap: .6rem; padding: .3rem .85rem .45rem; }
.cal-meet-time { font-family: var(--mono); font-size: .68rem; color: var(--ink-soft);
    flex: none; min-width: 3.6rem; }
.cal-meet-body { min-width: 0; flex: 1; }
.cal-meet-title { display: block; font-size: .8rem; overflow: hidden;
    text-overflow: ellipsis; white-space: nowrap; }
.cal-meet-client { display: block; font-size: .7rem; color: var(--muted); }
.cal-meet-client:hover { color: var(--accent); }
.cal-meet-join { flex: none; font-family: var(--mono); font-size: .6rem; letter-spacing: .08em;
    text-transform: uppercase; font-weight: 600; padding: .12rem .45rem; border-radius: 2px;
    color: #2F6FB0; border: 1px solid color-mix(in srgb, #2F6FB0 35%, transparent); }
.cal-meet-join:hover { background: color-mix(in srgb, #2F6FB0 12%, transparent); }

/* ---- Time off rail (Calendar) ----------------------------------------------
   Sits directly under the meetings rail and borrows its measurements on purpose:
   they answer the two halves of one question — what is on, and what is off — and
   reading them as one column is the point. */
.cal-off { margin: 0 0 1.4rem; border: 1px solid var(--line); background: var(--panel); }
.cal-off-head { display: flex; align-items: baseline; justify-content: space-between;
    gap: .6rem; padding: .7rem .85rem .5rem; border-bottom: 1px solid var(--line); }
.cal-off-head h2 { margin: 0; font-size: .92rem; }
.cal-off-n { font-family: var(--mono); font-size: .68rem; color: var(--muted); }
.cal-off-empty { margin: 0; padding: .8rem .85rem 1rem; font-size: .78rem; color: var(--muted); }
.cal-off-empty code { font-family: var(--mono); font-size: .72rem; }
.cal-off-list { list-style: none; margin: 0; padding: 0; }
.cal-off-row { display: flex; align-items: baseline; gap: .6rem; padding: .45rem .85rem; }
.cal-off-row + .cal-off-row { border-top: 1px solid var(--line); }
.cal-off-row.is-now { background: color-mix(in srgb, var(--gold) 8%, transparent); }
.cal-off-when { font-family: var(--mono); font-size: .68rem; color: var(--ink-soft);
    flex: none; min-width: 6.2rem; }
.cal-off-what { flex: 1; min-width: 0; font-size: .8rem; overflow: hidden;
    text-overflow: ellipsis; white-space: nowrap; }
.cal-off-days { font-family: var(--mono); font-size: .62rem; color: var(--muted); margin-left: .35rem; }
.cal-off-clear { flex: none; font-family: var(--mono); font-size: .6rem; letter-spacing: .08em;
    text-transform: uppercase; color: var(--muted); }
.cal-off-clear:hover { color: var(--accent); }
.cal-off-add { border-top: 1px solid var(--line); }
.cal-off-add summary { cursor: pointer; padding: .55rem .85rem; font-size: .78rem; color: var(--accent); }
.cal-off-form { padding: 0 .85rem .9rem; display: grid; gap: .5rem; }
.cal-off-form label { display: block; font-size: .7rem; color: var(--muted); }
.cal-off-form input, .cal-off-form select { width: 100%; margin-top: .15rem; }
.cal-off-dates { display: grid; grid-template-columns: 1fr 1fr; gap: .5rem; }
.cal-off-form .btn { justify-self: start; }
.cal-off-form p { margin: 0; }

/* ---- The Today panel at phone widths ----------------------------------------
   Width, not pointer, decides these: they're about how much room there is, not
   about how you're touching it. (The gesture rules above are pointer-based for
   the opposite reason — a narrow desktop window still has a mouse.) */
@media (max-width: 620px) {
    /* The panel keeps the page gutter. It used to bleed past it (-.85rem against
       a .6rem wrap), so its border hung off both edges of the screen and its text
       sat closer to the glass than every other page's — the dashboard read as a
       different app. One gutter, every page. */
    .feed-head { padding: .75rem .85rem .5rem; }
    .feed-title { font-size: .88rem; }
    .feed-chips { padding: 0 .85rem .6rem; }
    .feed-band-head { padding: .45rem .85rem .3rem; }
    .feed-more, .feed-empty { padding-left: .85rem; padding-right: .85rem; }

    /* Two rows, placed explicitly. Letting the row wrap on its own put the dot
       strip, the tag AND both buttons together on a third line, with the tick
       stranded mid-row and the x hard right -- the odd stacking at small window
       sizes. Buttons belong beside the title they act on.

         row 1:  [mark] title / subtitle        [check] [x]
         row 2:  tag, indented to the title

       The dot strip goes: it is the one thing here that cannot survive the width,
       and the stage tag on row 2 already says where the job is. */
    .feed-row { grid-template-columns: minmax(0, 1fr) 24px 24px; row-gap: .2rem;
        column-gap: .35rem; }
    .feed-main { grid-column: 1; grid-row: 1; }
    .feed-actform--do, .feed-act-slot--do   { grid-column: 2; grid-row: 1; }
    .feed-actform--off, .feed-act-slot--off { grid-column: 3; grid-row: 1; }
    .feed-row .obdots, .feed-dots-slot { display: none; }
    .feed-tag { grid-column: 1 / -1; grid-row: 2; justify-self: start;
        /* Lines up with the title above it, not with the row edge: the mark is
           20px and .feed-main puts a .6rem gap after it. */
        margin-left: calc(20px + .6rem); }
    .feed-tag-slot { display: none; }
    .feed-row-title { white-space: normal; }
    .feed-meta { white-space: normal; }

    .feed-row--gone { padding: .55rem .85rem; }
    .feed-undo-txt { font-size: .72rem; }
}
/* The Join tag is a link, not a label — the only tag in the panel that DOES
   something. It keeps the tag's exact size and slot so the column of stages
   still reads down one edge; only the hover tells you it's live. */
.feed-tag--go { text-decoration: none; cursor: pointer; }
.feed-tag--go:hover { color: var(--bar);
    background: color-mix(in srgb, var(--bar) 22%, transparent);
    border-color: color-mix(in srgb, var(--bar) 55%, transparent); }
/* An overdue invoice is the one money row that IS your move, so it keeps ember —
   the colour this app reserves for money owed and your turn. */
.feed-row--payout { --bar: var(--accent); }
.feed-tag--overdue { color: var(--danger); background: color-mix(in srgb, var(--danger) 12%, transparent);
    border-color: color-mix(in srgb, var(--danger) 32%, transparent); }

/* A request that stopped to ask you something. Ember, like the overdue invoice
   above and for the same reason: it is genuinely your turn, and nothing moves
   until you take it. The work is already done and paid for — this is the row
   with the shortest distance between one sentence from you and a finished
   draft, which is why it sits at the top of the band. */
.feed-row--asked { --bar: var(--accent); }
.feed-tag--asked { color: var(--accent); background: color-mix(in srgb, var(--accent) 12%, transparent);
    border-color: color-mix(in srgb, var(--accent) 34%, transparent); }
/* A client waiting on an answer — the same ember, and the same argument. The
   only difference from the row above is who is stopped: there the assistant,
   here a person who wrote to you and hasn't heard back. */
.feed-tag--question { color: var(--accent); background: color-mix(in srgb, var(--accent) 12%, transparent);
    border-color: color-mix(in srgb, var(--accent) 34%, transparent); }
/* A promise with a day on it, landing today or tomorrow. Sits in the clock band,
   so it takes that band's quieter treatment rather than the ember of a decision:
   time running is not the same as somebody waiting on you. */
.feed-tag--date { color: var(--ink-soft); background: var(--wash); border-color: var(--line-strong); }
/* Money released but not yet in the bank. The one row in the panel that wears
   --ok: it is the only good news the dashboard reports BEFORE it is true, and
   nothing else in the app means "this is going your way, and here is when".
   Deliberately not ember — ember is money owed and your move, and a payment
   arriving is the opposite of both. Quieter than a Paid row in the landed band,
   because that one is a fact and this one is still a promise. */
.feed-tag--incoming { color: var(--ok); background: color-mix(in srgb, var(--ok) 12%, transparent);
    border-color: color-mix(in srgb, var(--ok) 32%, transparent); }
.feed-row--incoming { --bar: var(--ok); }/* A round of notes waiting to become a board. Ember like the two above — it is
   work a client has handed you and none of it is written down yet — but this row
   sends you somewhere to READ before anything happens, which is why it carries no
   tick of its own. */
.feed-tag--notes { color: var(--accent); background: color-mix(in srgb, var(--accent) 12%, transparent);
    border-color: color-mix(in srgb, var(--accent) 34%, transparent); }
/* A client asking for days of your time. Ember, and the strongest case for it in
   the band: this is the only row that goes stale on its own — the days get taken
   by whoever answers first — and the only one that can end in a job. */
.feed-tag--hold { color: var(--accent); background: color-mix(in srgb, var(--accent) 12%, transparent);
    border-color: color-mix(in srgb, var(--accent) 34%, transparent); }
/* Clients worth a note. Not ember: nothing here is owed, late, or costing you
   anything — it is an opportunity you can walk past, and it must not compete
   with the rows that genuinely can't wait. */
.feed-tag--checkin { color: var(--ink-soft); background: var(--wash); border-color: var(--line-strong); }
/* And one that's simply running. Deliberately the quietest row in the panel —
   it is a status, not a task, and it removes itself when the work lands. It
   exists only because saying nothing made queued work look like broken work. */
.feed-row--working { --bar: var(--line-strong); }
.feed-tag--working { color: var(--muted); background: var(--wash); border-color: var(--line); }
.feed-row--working .feed-row-title { font-weight: 500; color: var(--ink-soft); }

/* ============================================================
   PICK UP WHERE YOU LEFT OFF  —  the jump strip under the dashboard head
   ------------------------------------------------------------
   Four cards, and deliberately NOT a third panel. The two panels below are
   readouts — things to take in — and this is navigation, the same job the shelf
   on the left does. Giving it a panel's border and band head would dress a set
   of links up as a fifth thing to read every morning.

   So: no container, no title bar, no rule above. Just cards sitting in the gap
   the page head already leaves, quiet until you look for them.

   The marks are the Workspace card's, not a new set: the same .dk tinted tile a
   document row wears on the client and project pages, in that kind's hue, with
   the written kind under it in the same colour. Both channels carry the same
   fact, so the strip survives a colour-blind reader — and a card recolours by
   setting one class, .dk-sheet or .dk-client, exactly as a document row does.
   The colour also earns the hover: the card washes in its OWN hue rather than a
   house grey, so the thing you're about to open answers before you click.
   ============================================================ */
.jump { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 1px; background: var(--line); border: 1px solid var(--line);
    margin: 0 0 1.6rem; }
.jump-card { display: flex; flex-direction: column; gap: .3rem; min-width: 0;
    padding: .65rem .75rem .7rem; background: var(--panel); color: var(--ink);
    text-decoration: none; transition: background .12s, box-shadow .12s; }
.jump-card:hover { background: color-mix(in srgb, var(--dk, var(--accent)) 6%, var(--panel));
    box-shadow: inset 2px 0 0 var(--dk, var(--accent)); }
.jump-card:hover .jump-label { color: var(--dk, var(--accent)); }

/* Tile and kind on one line, so the label below gets the card's full width — the
   name is the thing you're aiming at, and it's the longest string here. */
.jump-head { display: flex; align-items: center; gap: .45rem; min-width: 0; }
/* The kind reads as a label, not as content: the same mono eyebrow every band
   head on this page wears, tinted to its tile so the pair reads as one mark. */
.jump-kind { font-family: var(--mono); font-size: .6rem; letter-spacing: .1em;
    text-transform: uppercase; font-weight: 600; color: var(--dk, var(--muted));
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.jump .dk { width: 1.35rem; height: 1.35rem; margin-top: 0; }
.jump .dk-ico { width: 12px; height: 12px; }
.jump .dk-mono { font-size: .62rem; }

/* One line, clipped. A sheet called "Monark — AE roster, Q3 revision 2" wrapping
   to three lines would make four shortcuts taller than the panel they sit on. */
.jump-label { font-size: .84rem; font-weight: 600; line-height: 1.25;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* The visit count is the strip explaining itself: it says WHY this card earned
   a place, which is the one question a list of shortcuts always provokes. */
.jump-meta { font-family: var(--mono); font-size: .62rem; color: var(--muted); }

@media (max-width: 760px) {
    /* Two across. Four columns on a phone gives each label about nine characters,
       which is not a shortcut to anything. */
    .jump { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* ============================================================
   WHERE YOU STAND  —  the dashboard's second panel
   ------------------------------------------------------------
   Deliberately the same container as .feed: hairline border, band heads, mono
   numerals. The two panels are the dashboard now, and the only difference
   between them is what they answer — what wants me, versus how am I doing.

   Everything here was already on the page; it was just in four boxes that had no
   relationship to each other, one of them pinned above the fold as a banner.
   ============================================================ */
.stand { margin: 0 0 2.2rem; border: 1px solid var(--line); background: var(--panel); }
.stand-head { display: flex; align-items: baseline; justify-content: space-between;
    gap: .8rem; padding: .85rem 1rem .6rem; }
.stand-title { margin: 0; font-size: .92rem; }
.stand-sub { font-family: var(--mono); font-size: .68rem; letter-spacing: .08em;
    text-transform: uppercase; color: var(--muted); }
.stand-band-head { font-family: var(--mono); font-size: .62rem; letter-spacing: .1em;
    text-transform: uppercase; color: var(--muted); padding: .5rem 1rem .35rem;
    border-top: 1px solid var(--line); background: var(--bg); }
.stand-band-n { margin-left: .4rem; opacity: .75; }

/* ---- Money ---------------------------------------------------------------- */
.stand-money { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
    gap: 1px; background: var(--line); border-top: 1px solid var(--line); }
.stand-tile, .stand-hero { display: flex; flex-direction: column; gap: .15rem;
    padding: .7rem 1rem .8rem; background: var(--panel); text-decoration: none; color: var(--ink);
    border-top: 3px solid var(--ribbon, var(--line-strong)); }
a.stand-tile:hover, a.stand-hero:hover { background: color-mix(in srgb, var(--ribbon, var(--accent)) 6%, var(--panel)); }
.stand-tile.k-in   { --ribbon: #00A884; }
.stand-tile.k-cap  { --ribbon: #2F6FB0; }
.stand-tile.k-cost { --ribbon: #8E3A7E; }
.st-label { font-family: var(--mono); font-size: .6rem; letter-spacing: .1em;
    text-transform: uppercase; color: var(--muted); }
.st-value { font-family: var(--mono); font-size: 1.15rem; font-weight: 600; letter-spacing: -.01em; }
.st-value.pos { color: var(--ok); }
.st-value.danger { color: var(--danger); }
.st-of { font-size: .8rem; color: var(--muted); font-weight: 400; }
.st-sub { font-size: .68rem; color: var(--muted); }

/* The payout leads, in the colour this app reserves for money owed. It used to be
   a full-width banner above everything, which made it the loudest thing on the
   page on the many days when it was only a date in the future. */
/* These are spelled out rather than abbreviated because .sh-label was already
   taken: the left nav shelf uses it for its own item labels. Reusing it turned
   every nav label ember and uppercase, since this rule loads later. app.css is
   one global sheet -- new component classes get a component prefix. */
.stand-hero { --ribbon: var(--accent); }
.stand-hero.is-overdue { --ribbon: var(--danger); }
.stand-hero.is-overdue .stand-hero-value { color: var(--danger); }
.stand-hero.is-empty { --ribbon: var(--line-strong); }
.stand-hero-label { font-family: var(--mono); font-size: .6rem; letter-spacing: .1em;
    text-transform: uppercase; color: var(--accent); }
.stand-hero.is-overdue .stand-hero-label { color: var(--danger); }
.stand-hero.is-empty .stand-hero-label { color: var(--muted); }
.stand-hero-value { font-family: var(--mono); font-size: 1.35rem; font-weight: 700;
    letter-spacing: -.015em; color: var(--accent); }
.stand-hero.is-empty .stand-hero-value { color: var(--muted); }
.stand-hero-sub { font-size: .68rem; color: var(--muted); }

/* ---- This month ----------------------------------------------------------- */
/* The calendar wants a SIZE, not a share. Split half and half and the month grid
   sat in the left of its own cell with a hand's width of nothing beside it — and
   at a wide window the alternative was worse, a month stretched to 900px of
   pale squares. So the calendar takes as much as it needs up to a readable
   maximum and the job list, whose titles were being cut with an ellipsis, takes
   everything that is left. */
.stand-month { display: grid; grid-template-columns: minmax(0, 420px) minmax(0, 1fr);
    gap: 1px; background: var(--line); border-top: 1px solid var(--line); }
.stand-cal, .stand-jobs { background: var(--panel); padding: .7rem 1rem .9rem; min-width: 0; }

/* One month, one column.
   .sheet-cal is a wrap-around grid built for the three-month sheet view, and on
   its own that was fine here: auto-fit collapsed the three empty tracks and the
   single month took the width. But the pay-day legend spans `1 / -1`, so in a
   month that HAS a pay day no track is empty, nothing collapses, and the month
   shrinks to one track of four — the calendar changing size depending on whether
   you happened to be paid that month. Saying "one column" here settles it. */
.stand-cal .sheet-cal { grid-template-columns: minmax(0, 1fr); margin-bottom: 0; }
.stand-jobs { display: flex; flex-direction: column; }

/* Hairline rows, not the chip strip this was. A chip strip scrolled sideways and
   truncated the job title, which is the one part you're looking for. */
.stand-job { display: grid; grid-template-columns: auto minmax(0, 1fr) auto auto;
    align-items: center; gap: .3rem .6rem; padding: .4rem 0;
    border-top: 1px solid var(--line); text-decoration: none; color: var(--ink); }
.stand-job:first-child { border-top: 0; }
.stand-job:hover .sj-title { color: var(--accent); }
.sj-logo { width: 20px; height: 20px; flex: none; display: flex; align-items: center; justify-content: center; }
.sj-logo img { width: 20px; height: 20px; object-fit: contain; display: block; }
.sj-logo-mono { font-family: var(--mono); font-size: .62rem; font-weight: 700; color: var(--muted);
    width: 20px; height: 20px; display: flex; align-items: center; justify-content: center;
    border: 1px solid var(--line-strong); }
.sj-body { min-width: 0; }
.sj-title { display: block; font-size: .82rem; font-weight: 600; overflow: hidden;
    text-overflow: ellipsis; white-space: nowrap; }
.sj-client { display: block; font-size: .68rem; color: var(--muted); overflow: hidden;
    text-overflow: ellipsis; white-space: nowrap; }
.sj-when { font-family: var(--mono); font-size: .68rem; color: var(--ink-soft); white-space: nowrap; }
.sj-days { font-family: var(--mono); font-size: .6rem; letter-spacing: .06em; color: var(--muted);
    min-width: 2.2rem; text-align: right; }
.stand-jobs-more { margin-top: auto; padding-top: .5rem; font-size: .72rem; color: var(--muted); }
.stand-jobs-empty { margin: 0; font-size: .78rem; color: var(--muted); }

@media (max-width: 760px) {
    /* The calendar and the job list stop competing for a half-width column. */
    .stand-month { grid-template-columns: minmax(0, 1fr); }
}
@media (max-width: 620px) {
    .stand-head { padding: .75rem .85rem .5rem; }
    .stand-band-head { padding: .45rem .85rem .3rem; }
    .stand-tile, .stand-hero, .stand-cal, .stand-jobs { padding-left: .85rem; padding-right: .85rem; }
    /* Two across rather than one: these are short numbers, and a single column
       turns six of them into a page of scrolling. */
    .stand-money { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .stand-hero { grid-column: 1 / -1; }
}

/* The value-booked forecast, folded. Moved off the dashboard: a twelve-month
   projection is a question you ask, not a number you check, and it was being
   scrolled past daily. It folds on /projects and sits open under /calendar. */
.graph-fold { margin: 0 0 1.2rem; border: 1px solid var(--line); background: var(--panel); }
.graph-fold > summary { list-style: none; cursor: pointer; }
.graph-fold > summary::-webkit-details-marker { display: none; }
.graph-fold-head { display: flex; align-items: baseline; gap: .6rem; padding: .6rem .9rem; }
.graph-fold-title { font-family: var(--mono); font-size: .66rem; letter-spacing: .1em;
    text-transform: uppercase; font-weight: 600; }
.graph-fold-chev { margin-left: auto; color: var(--muted); transition: transform .15s ease; }
.graph-fold[open] .graph-fold-chev { transform: rotate(180deg); }
.graph-fold[open] .graph-fold-head { border-bottom: 1px solid var(--line); }
.graph-fold-body { padding: .8rem .9rem 1rem; }
.graph-fold-body .year-graph, .cal-graph .year-graph { margin: 0; }
.cal-graph { margin-top: 1.6rem; }

/* Lately keeps the typography it had. These rules used to hang off .dash-feed,
   the wrapper the old activity card sat in; moving the list into the Today panel
   dropped them silently, which cost the mono row titles that made the log scan
   as a ledger rather than as prose. */
.lately .da-title { font-family: var(--mono); font-weight: 500; font-size: .82rem; letter-spacing: -.01em; }
.lately .da-act { font-family: var(--mono); font-size: .58rem; text-transform: uppercase; letter-spacing: .08em; }
.lately .kb-kind { font-family: var(--mono); font-size: .54rem; text-transform: uppercase;
    letter-spacing: .08em; font-weight: 600; padding: .12rem .34rem; }
/* A band that failed to load. Stated, not hidden — an absent band reads as a
   quiet day, and "nothing is waiting on you" is a dangerous thing to say by
   accident. */
.feed-broke { margin: 0; padding: .6rem 1rem; font-size: .75rem; color: var(--danger);
    background: color-mix(in srgb, var(--danger) 6%, transparent);
    border-top: 1px solid color-mix(in srgb, var(--danger) 25%, var(--line)); }
/* "Not set up yet" reads differently from "nothing today" — quieter than an
   error, but it must not look like the good-news empty state either. */
.feed-empty--setup { color: var(--ink-soft); }

/* A link cell while reading. Dressed as the client's copy dresses it — the same
   ember underline as .sh-link — because preview is meant to BE that page, not an
   impression of it. The pencil that sets the address still appears on hover. */
.sh--preview .sh-td--link.is-live { cursor: pointer; }
.sh--preview .sh-td--link.is-live .sh-linklabel,
.sh--preview .sh-td--link.is-live::after {
    color: var(--accent); text-decoration: underline; text-underline-offset: 2px; }
.sh--preview .sh-td--link.is-live:hover .sh-linklabel,
.sh--preview .sh-td--link.is-live:hover::after { color: var(--accent-glow); }
/* The label is not an input while reading, so it must not look like one. */
.sh--preview .sh-td--link.is-live .sh-linklabel:hover { background: none; }

/* The row-number column sorts too, and that is the way back: sorting rewrites row
   order, so before this the only route to the order rows arrived in was a second
   column of numbers typed by hand beside the real one. Faint until you go looking,
   like every other sort control here. */
.sh-th--n .sh-sort--n { padding: 0; width: 100%; text-align: center; opacity: 0; transition: opacity .12s; }
.sh-table:hover .sh-th--n .sh-sort--n,
.sh-th--n.is-sorted .sh-sort--n,
.sh-th--n .sh-sort--n:focus-visible { opacity: 1; }
.sh-th--n.is-sorted .sh-sort--n { color: var(--accent); }

/* ============================================================
   SHEET — ROOM TO WORK

   Six settings that don't change what a sheet SAYS, only how much
   of it you can see at once: a width you set yourself, a row
   height, a zoom, a frozen first column, a tint down a column and
   a filter. The first three are the difference between a table of
   paragraphs and a table you can read.
   ============================================================ */

/* ---- the view bar ------------------------------------------- */
.sh-view { display: flex; align-items: center; gap: 1.2rem; flex-wrap: wrap;
    margin: 0 0 .55rem; }
.sh-vgroup { display: inline-flex; align-items: center; gap: .25rem; }
.sh-vgroup[hidden] { display: none; }
.sh-vlabel { font-family: var(--mono); font-size: .55rem; text-transform: uppercase;
    letter-spacing: .1em; font-weight: 700; color: var(--muted); margin-right: .15rem; }
.sh-vbtn { font-family: var(--mono); font-size: .56rem; text-transform: uppercase;
    letter-spacing: .09em; font-weight: 700; color: var(--muted); cursor: pointer;
    background: none; border: 1px solid var(--line); padding: .22rem .45rem; }
.sh-vbtn:hover { color: var(--accent); border-color: color-mix(in srgb, var(--accent) 45%, var(--line)); }
.sh-vbtn.is-on { color: var(--accent); border-color: var(--accent);
    background: color-mix(in srgb, var(--accent) 7%, transparent); }
/* .sh-vbtn--step, .sh-vbtn--reset and .sh-vnow stood here: the − 100% + reset
   of the Zoom control. Zoom is gone (see the fourth pass at the end of this
   file); undo and redo are .sh-vbtn--icon now. */
.sh-vcount { font-family: var(--mono); font-size: .58rem; text-transform: uppercase;
    letter-spacing: .09em; font-weight: 700; color: var(--accent); }
.sh-vchips { display: inline-flex; gap: .25rem; flex-wrap: wrap; }
.sh-vchip { font-family: var(--mono); font-size: .55rem; text-transform: uppercase;
    letter-spacing: .08em; font-weight: 700; cursor: pointer; color: var(--ink-soft);
    background: var(--wash); border: 1px dashed var(--line-strong); padding: .18rem .42rem; }
.sh-vchip:hover { color: var(--accent); border-color: var(--accent); }

/* ---- the box inside a cell ----------------------------------
   Height on a <td> is a minimum, never a maximum, so a table cell
   cannot be clamped. The block inside it can — which is the whole
   reason it exists. It also carries the padding, so the typing
   highlight fills the cell rather than sitting inset inside it. */
.sh-td--body { padding: 0; }
.sh-cellin { padding: .5rem .7rem; overflow-wrap: anywhere; outline: none; }
.sh-td--name .sh-cellin,
.sh-td--mono .sh-cellin { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* Editing wants the whole value, however the column is normally drawn. */
.sh-td--name .sh-cellin:focus,
.sh-td--mono .sh-cellin:focus { white-space: normal; overflow: visible; }

/* ---- row height ---------------------------------------------
   Clamped, not truncated: the text is all still there, and the
   cell opens to its full height the moment you put a cursor in it
   or click the chevron. The client's page inherits the clamp, and
   the same click opens it for them. */
.sh-table[data-density="normal"] .sh-cellin,
.sh-table[data-density="compact"] .sh-cellin {
    display: -webkit-box; -webkit-box-orient: vertical; overflow: hidden; }
.sh-table[data-density="normal"] .sh-cellin  { -webkit-line-clamp: 3; }
.sh-table[data-density="compact"] .sh-cellin { -webkit-line-clamp: 1; }
/* Not the body cells: their padding lives on the box inside them, and
   setting both would make a compact row taller than a normal one. */
.sh-table[data-density="compact"] .sh-td:not(.sh-td--body) { padding-top: .28rem; padding-bottom: .28rem; }
.sh-table[data-density="compact"] .sh-cellin { padding-top: .3rem; padding-bottom: .3rem; }
.sh-table[data-density="compact"] .sh-img { max-height: 24px; }
/* The empty picture slot sets the floor for a whole row of them, so it
   comes down with the pictures rather than holding the row open. */
.sh-table[data-density="compact"] .sh-imgadd { width: 22px; height: 22px; }
/* "Set link" wraps to two lines in a narrow column and holds the whole
   row open on its own — the one place an empty cell was the tallest
   thing on the line. */
/* A link cell is a label and a control, and they belong on one line. In a
   narrow column the control was dropping to a second one, which made a
   column of EMPTY link cells the tallest thing in a compact table. */
.sh-td--link { white-space: nowrap; }
.sh-linkedit { white-space: nowrap; }
.sh-td--link .sh-linklabel { min-width: 2rem; max-width: calc(100% - 3.6rem);
    overflow: hidden; text-overflow: ellipsis; vertical-align: middle; }
.sh-table .sh-cellin:focus,
.sh-table .sh-td.is-open .sh-cellin {
    display: block; -webkit-line-clamp: unset; overflow: visible; white-space: normal; }

/* The chevron on a cell with more to say. Only drawn on cells JS
   has measured as clipped, so it never promises something that
   isn't there — and it is the one editor control that also works
   on the client's copy. */
.sh-td.is-clipped { position: relative; }
.sh-more { position: absolute; right: .1rem; bottom: .04rem; z-index: 1;
    background: none; border: 0; cursor: pointer; padding: 0 .15rem; line-height: 1;
    font-size: .6rem; color: var(--line-strong); }
.sh-more:hover { color: var(--accent); }
.sh-td.is-open .sh-more { color: var(--accent); }

/* ---- a column clipped to one line --------------------------- */
.sh-td[data-wrap="clip"] .sh-cellin {
    display: block; -webkit-line-clamp: unset; white-space: nowrap;
    overflow: hidden; text-overflow: ellipsis; }
.sh-td[data-wrap="clip"] .sh-cellin:focus { white-space: normal; overflow: visible; }

/* ---- a width you set yourself -------------------------------
   Dragging any edge freezes every column at what it currently
   measures and switches the table to fixed layout. Without that
   the browser re-guesses all of them on the next keystroke, and
   the width you just set is gone. */
.sh-table--fixed { table-layout: fixed; }
.sh-resize { position: absolute; top: 0; right: -3px; width: 7px; height: 100%;
    cursor: col-resize; z-index: 3; touch-action: none; }
.sh-resize::after { content: ''; position: absolute; left: 3px; top: .35rem; bottom: .35rem;
    width: 1px; background: transparent; transition: background .12s; }
.sh-th:hover .sh-resize::after { background: var(--line-strong); }
.sh-resize:hover::after, .sh-resize.is-live::after { background: var(--accent); width: 2px; }
.sh--resizing, .sh--resizing * { cursor: col-resize !important; user-select: none; }

/* ---- dragging a column -------------------------------------- */
.sh-colgrip { cursor: grab; color: var(--line-strong); font-size: .7rem; line-height: 1;
    user-select: none; touch-action: none; padding: 0 .1rem; }
.sh-colgrip:hover { color: var(--accent); }
.sh-colgrip:active { cursor: grabbing; }
.sh-th.is-coldrag { background: color-mix(in srgb, var(--accent) 10%, transparent); }
.sh-colmore { background: none; border: 0; cursor: pointer; padding: 0 .1rem;
    font-family: var(--mono); font-size: .6rem; font-weight: 700; color: var(--line-strong);
    letter-spacing: .05em; }
.sh-colmore:hover { color: var(--accent); }

/* ---- the column menu ----------------------------------------
   Everything about a column that isn't what it holds. One menu
   rather than six more buttons in a header already carrying a
   name, a sort arrow, a type, its categories and three move
   controls — and each setting gets room to say what it does,
   which matters more than the click it costs: nobody guesses
   that a column can be tinted. */
.sh-colmenu { position: fixed; z-index: 55; min-width: 13.5rem; max-width: 17rem;
    background: var(--panel); border: 1px solid var(--line-strong);
    box-shadow: 0 6px 20px rgba(20,24,30,.14); padding: .35rem; }
.sh-colmenu[hidden] { display: none; }
.sh-colmenu-h { font-family: var(--mono); font-size: .55rem; text-transform: uppercase;
    letter-spacing: .1em; font-weight: 700; color: var(--muted); padding: .15rem .4rem .35rem;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.sh-colmenu-s { font-family: var(--mono); font-size: .52rem; text-transform: uppercase;
    letter-spacing: .1em; font-weight: 700; color: var(--muted);
    border-top: 1px solid var(--line); margin-top: .3rem; padding: .35rem .4rem .25rem; }
.sh-colact { display: block; width: 100%; text-align: left; background: none; border: 0;
    cursor: pointer; padding: .3rem .4rem; font-family: var(--font); font-size: .78rem;
    text-transform: none; letter-spacing: 0; font-weight: 400; color: var(--ink); }
.sh-colact:hover { background: var(--wash); color: var(--accent); }
/* Same trap as .sh-colmenu-row below: `display: block` above outranks the
   browser's [hidden] rule, so "Let the client set this column" was showing on
   every column type — including the ones that cannot be handed over. */
.sh-colact[hidden] { display: none; }
.sh-markopt--off { display: inline-flex; align-items: center; justify-content: center;
    background: none; border: 1px solid var(--line); color: var(--line-strong);
    font-size: .7rem; line-height: 1; }
.sh-markopt--off:hover { color: var(--danger); border-color: var(--danger); background: none; }

/* ---- filtering ----------------------------------------------
   A reading state, not a document one: it never reaches the save,
   because the rows a client is sent are not the rows you happened
   to be looking at. */
.sh-colfilter { max-height: 13rem; overflow: auto; padding: 0 .1rem .1rem; }
.sh-filteropt { display: flex; align-items: center; gap: .4rem; width: 100%; text-align: left;
    background: none; border: 0; cursor: pointer; padding: .18rem .3rem; }
.sh-filteropt:hover { background: var(--wash); }
.sh-filterbox { font-family: var(--mono); font-size: .68rem; color: var(--line-strong); width: .9rem; }
.sh-filteropt.is-on .sh-filterbox { color: var(--accent); }
.sh-filtertext { width: 100%; font: inherit; font-family: var(--font); font-size: .78rem;
    padding: .28rem .35rem; border: 1px solid var(--line); background: var(--panel); color: var(--ink); }
.sh-filtertext:focus { outline: none; border-color: var(--accent); }
.sh-table tr.is-filtered { display: none; }
.sh-th.is-filtering .sh-colmore { color: var(--accent); }

/* ---- a tint down a column -----------------------------------
   The same six tones a row highlight uses, and lighter than one,
   because a whole column of colour is a lot of colour. A row
   highlight still wins where they cross: it is the thing that
   says "this one", and a column cannot say that. */
[data-tint="grey"]  { --sh-tint: var(--muted); }
[data-tint="blue"]  { --sh-tint: #2F6FB0; }
[data-tint="green"] { --sh-tint: var(--c-in); }
[data-tint="ember"] { --sh-tint: var(--accent); }
[data-tint="red"]   { --sh-tint: var(--danger); }
[data-tint="plum"]  { --sh-tint: var(--c-out); }
.sh-td[data-tint] { background: color-mix(in srgb, var(--sh-tint) 8%, transparent); }
.sh-th[data-tint] { background: color-mix(in srgb, var(--sh-tint) 12%, transparent);
    color: color-mix(in srgb, var(--sh-tint) 75%, var(--ink)); }

/* ---- hidden columns -----------------------------------------
   Gone from the view on both surfaces, and struck out of the
   client's markup entirely (see render_sheet_body) — a working
   column shouldn't be one View Source away. */
.sh-th[data-hidden], .sh-td[data-hidden] { display: none; }

/* ---- zoom, removed ------------------------------------------
   `.sh-tablewrap { zoom: var(--sh-zoom) }` used to live here.
   CSS zoom scales a box AND its contents, so every rectangle
   sheet.js measured came back in viewport pixels and had to be
   divided back into layout pixels — which Chromium and Firefox
   round differently, so a dragged column landed a pixel out on
   one of them and the frozen column drifted as you scrolled.
   Type size does the job it was doing, scales font-size rather
   than the box, and travels to the client with the document.
   See "TYPE SIZE" in the fourth pass at the end of this file. */

/* The table had its own scroll pane here, with a header that stayed
   put. It cost a second vertical scrollbar inside a page that already
   had one, which is a worse trade than it sounds: two scrollbars mean
   guessing which one you are in. The page scrolls, and only the page. */

/* ---- the first column, frozen -------------------------------
   Only when the table is actually wider than its box. JS writes
   the offsets, because they are whatever the columns to the left
   happen to measure. A sticky cell needs an opaque background or
   the row it is floating over shows through it. */
.sh-freeze { position: sticky; z-index: 2; background: var(--panel); }
.sh-th.sh-freeze { z-index: 6; }
tr.sh-row--marked .sh-freeze { background: color-mix(in srgb, var(--sh-mark) 11%, var(--panel)); }
.sh-freeze[data-tint] { background: color-mix(in srgb, var(--sh-tint) 8%, var(--panel)); }
.sh-freeze--last { box-shadow: 1px 0 0 var(--line); }
.sh--scrolled .sh-freeze--last { box-shadow: 1px 0 0 var(--line), 7px 0 12px -7px rgba(20,24,30,.3); }

/* Reading it as the client does: the machinery in the header
   stands down with everything else, but the widths, the tint and
   the row height stay — those are the document. */
.sh--preview .sh-resize,
.sh--preview .sh-colgrip,
.sh--preview .sh-colmore,
.sh--preview .sh-view { display: none; }

@media (max-width: 760px) {
    .sh-view { gap: .7rem; }
    .sh-vlabel { display: none; }
}

/* A picture arriving from the desktop. The cell says where it will land
   before you let go, because a drop on a table is otherwise a guess. */
.sh-td--image.is-dropping { outline: 2px dashed var(--accent); outline-offset: -2px;
    background: color-mix(in srgb, var(--accent) 8%, transparent); }

/* ============================================================
   SHEET — SECOND PASS
   ============================================================ */

/* ---- the column grip ----------------------------------------
   At the head of the line rather than the end of the tools under
   it. A column can be 40 pixels wide — an image column usually
   is — and anything at the end of that header is off the end of
   it, which is why an image column could not be dragged. At the
   front it is always exactly where the column starts. */
.sh-thline .sh-colgrip { opacity: 0; margin-left: -.15rem; transition: opacity .12s; }
.sh-th:hover .sh-thline .sh-colgrip,
.sh-th:focus-within .sh-thline .sh-colgrip { opacity: 1; }
/* A picture is natively draggable, and a native drag steals the pointer out
   from under a column drag passing over it. */
.sh-img { -webkit-user-drag: none; user-select: none; }

/* ---- link cells ---------------------------------------------
   The address is the cell; the label is the optional extra that
   appears when you reach for it. It used to be the other way
   round — an empty "Label" on the page and the address behind a
   prompt — so a column of links read as a column of nothing. */
.sh-td--link { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.sh-linkurl { display: block; font-family: var(--mono); font-size: .72rem;
    color: var(--accent); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.sh-td--link .sh-linklabel { display: block; min-width: 0; max-width: 100%;
    font-size: .8rem; color: var(--ink); white-space: nowrap; overflow: hidden;
    text-overflow: ellipsis; }
/* An empty label takes no room until you hover the cell — otherwise every link
   cell in the table would be two lines tall to hold a field nobody filled in. */
.sh-td--link .sh-linklabel:empty:not(:focus) { display: none; }
.sh-td--link:hover .sh-linklabel:empty,
.sh-td--link:focus-within .sh-linklabel:empty { display: block; }
.sh-linkurl:empty::before, .sh-td--link .sh-linklabel:empty::before {
    content: attr(data-ph); color: var(--muted); font-weight: 400; }

/* Reading it as the client does — and on their own page. They see the label if
   there is one and the shortened address if there isn't, never both, and never
   the raw address under a label that was written to replace it. */
.sh--preview .sh-td--link .sh-linkurl { display: none; }
.sh--preview .sh-td--link.has-label .sh-linklabel { display: block; }
.sh--preview .sh-td--link:not(.has-label) .sh-linklabel:empty { display: none; }

/* A truncated address should look truncated. Cut mid-word with no ellipsis it
   reads as a broken domain rather than a narrow column. */
.sh-td--link[data-fallback]:not([data-fallback=""]),
.sh--preview .sh-td--link { overflow: hidden; text-overflow: ellipsis; }
.sh-td--link .sh-link { display: inline-block; max-width: 100%; overflow: hidden;
    text-overflow: ellipsis; vertical-align: bottom; }

/* ---- undo, on the page --------------------------------------
   ⌘Z is the way you'll use it and the way nobody discovers it. */
.sh-vbtn[disabled] { opacity: .35; cursor: default; }
.sh-vbtn[disabled]:hover { color: var(--muted); border-color: var(--line); }

/* ---- preview and the client's page take the width too --------
   Both were capped at 1100px, which is a DOCUMENT measure — right
   for a letter or an estimate, wrong for the one document that is
   a table. A sheet read through an 1100px slot is the same sheet
   you couldn't read before, and the client has even less recourse
   than you do: they can't widen a column. The blurb keeps its own
   68ch measure, because the blurb is the part that is prose. */
.sh-editor.is-preview .sh-canvas { max-width: none; }
.sh-shell { max-width: none; }

/* The letterhead, its rule, the title and the sign-off run the full
   width with the table.

   They were held to 1100px on the theory that a logo at one end of a
   wide screen and a date at the other is not a letterhead. True in
   isolation, wrong on the page: a header that stops short of a table
   that doesn't reads as a header belonging to some narrower page, and
   the rule beneath it ends in mid-air. A sheet is one object, and its
   edges should be one edge.

   The blurb still keeps its own 68ch measure. That is the one part of
   this page that is prose, and prose has a measure whatever the window
   is doing. */
.sh-page .sh-brand, .sh-page .sh-sent, .sh-page .sh-head,
.sh-editor.is-preview .sh-brand--preview,
.sh-editor.is-preview .sh-sent--preview,
.sh-editor.is-preview .sh-head { max-width: none; }

/* ---- how big a picture is drawn ------------------------------
   A height, with the width following the picture's own shape, so
   one setting suits a 16:9 frame and a square logo in the same
   column. Small is what every image column was before this
   existed and stays the default — a logo beside a client name
   should not become the client name's neighbour. */
.sh-img { max-width: 100%; }
.sh-td[data-img="medium"] .sh-img { max-height: 90px; max-width: 100%; }
.sh-td[data-img="large"]  .sh-img { max-height: 200px; max-width: 100%; }
.sh-td[data-img="medium"], .sh-td[data-img="large"] { white-space: normal; }
/* Compact overrides all three. A row you asked to be one line tall does not
   get a 200px picture in it — that is the whole of what compact means. */
.sh-table[data-density="compact"] .sh-td[data-img] .sh-img { max-height: 24px; }
.sh-colmenu-row { display: flex; gap: .25rem; padding: .1rem .4rem .2rem; }
/* A class that sets `display` outranks the browser's own [hidden] rule, so
   without this the row cannot hide itself — which is why Picture size showed on
   every column, under whatever heading happened to be above it. */
.sh-colmenu-row[hidden] { display: none; }
/* A bigger picture needs a wider column. An un-frozen table takes the hint from
   here; a frozen one has a width you set by hand, so sheet.js pushes that up to
   the minimum instead (min-width is ignored under fixed layout). */
.sh-th[data-img="medium"], .sh-td[data-img="medium"] { min-width: 170px; }
.sh-th[data-img="large"],  .sh-td[data-img="large"]  { min-width: 330px; }

/* ============================================================
   SHEET — THIRD PASS
   ============================================================ */

/* ---- the column's own controls, legible ---------------------
   These were drawn in --line-strong, which is the colour of a
   hairline. A control you have to hunt for on a header you are
   already hovering is a control that reads as decoration. */
.sh-coltype, .sh-colopts, .sh-colmore, .sh-coldel, .sh-colmove {
    color: var(--ink-soft); }
.sh-coltype { font-size: .56rem; }
.sh-colopts { font-size: .56rem; color: var(--muted); }
.sh-colmore { font-size: .68rem; letter-spacing: .08em; }
.sh-coldel  { font-size: .82rem; }
.sh-coltype:hover, .sh-colopts:hover, .sh-colmore:hover { color: var(--accent); }
.sh-coldel:hover { color: var(--danger); }
.sh-thtools { opacity: .55; gap: .4rem; }
.sh-th:hover .sh-thtools, .sh-th:focus-within .sh-thtools { opacity: 1; }
.sh-colgrip { font-size: .74rem; color: var(--muted); }
.sh-thline .sh-colgrip { opacity: .5; }
.sh-th:hover .sh-thline .sh-colgrip { opacity: 1; }

/* ---- the size of the words ----------------------------------
   One scale on the whole table rather than a size per column: a
   table set in two sizes stops reading as rows. Distinct from
   Zoom, which is about your screen — this is the document, and
   it goes to the client with it. */
.sh-table { --sh-fs: 1; }
.sh-table[data-text="small"] { --sh-fs: .88; }
.sh-table[data-text="large"] { --sh-fs: 1.16; }
.sh-td            { font-size: calc(.88rem * var(--sh-fs)); }
.sh-td--mono      { font-size: calc(.76rem * var(--sh-fs)); }
.sh-td--number    { font-size: calc(.82rem * var(--sh-fs)); }
.sh-td--n         { font-size: calc(.72rem * var(--sh-fs)); }
.sh-pill          { font-size: calc(.62rem * var(--sh-fs)); }
.sh-yn            { font-size: calc(.74rem * var(--sh-fs)); }
.sh-linkurl       { font-size: calc(.72rem * var(--sh-fs)); }
.sh-td--link .sh-linklabel { font-size: calc(.8rem * var(--sh-fs)); }

/* ---- emphasis, by the column --------------------------------
   The one piece of weight a column can carry. Not per cell: the
   thing worth emphasising in a table is a whole column of it. */
.sh-td[data-emph="bold"]  { font-weight: 600; color: var(--ink); }
.sh-td[data-emph="quiet"] { color: var(--muted); }
.sh-th[data-emph="bold"]  { color: var(--ink-soft); }
.sh-vbtn--bold  { font-weight: 800; }
.sh-vbtn--quiet { opacity: .7; }

/* ---- a number you can correct -------------------------------
   Only where the number is the row's NAME. A refused number goes
   red for a moment and puts the old one back: a number twice
   over is not a name, so a duplicate is refused rather than
   quietly moved somewhere else. */
.sh-td--n[contenteditable="true"] { cursor: text; }
.sh-td--n[contenteditable="true"]:hover {
    background: color-mix(in srgb, var(--accent) 8%, transparent); }
.sh-td--n[contenteditable="true"]:focus {
    outline: none; color: var(--ink);
    background: color-mix(in srgb, var(--accent) 12%, transparent);
    box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent) 45%, transparent); }
.sh-td--n.is-bad {
    color: var(--danger);
    background: color-mix(in srgb, var(--danger) 14%, transparent); }

/* ---- a row held back from the client -------------------------
   It stays in your table and is struck out of theirs. Greyed and
   ruled through rather than hidden, because the whole point is
   that YOU still see it. */
.sh-rowbtn--held.is-on { opacity: 1; color: var(--muted); }
.sh-row--held > .sh-td { opacity: .5; }
.sh-row--held > .sh-td:first-child { box-shadow: inset 2px 0 0 var(--line-strong); }
.sh-row--held .sh-cellin { text-decoration: line-through;
    text-decoration-color: color-mix(in srgb, var(--muted) 55%, transparent); }
.sh--preview .sh-row--held { display: none; }

/* ---- the whole table stays responsive ------------------------
   Widths are shares of the room the table has, not the pixels it
   had the day you set them, so it fits the window it is opened
   in — the client's page especially, where nobody can drag a
   column. Below this the table scrolls instead of crushing. */
   The floor itself arrives as --sh-min, written by the renderer
   and rewritten by layOut() in sheet.js. It used to be an inline
   min-width, which beats every rule in this file — including the
   one that has to take it back off on a phone, where the table is
   drawn as cards and an 1800px floor is a page scrolling sideways
   for no reason. */
.sh-table--fixed { min-width: var(--sh-min, 46rem); }
@media (max-width: 620px) { .sh-table--fixed { min-width: var(--sh-min, 34rem); } }

/* ============================================================
   SHEET — THE CLIENT'S HALF
   A note against a row, and one column they are allowed to set.
   ============================================================ */

/* ---- the control on the row ---------------------------------
   Quiet until there is something to read. A row nobody has said
   anything about should not look like a row with an unread
   message on it. */
.sh-say { position: relative; display: inline-flex; align-items: center; gap: .12rem; }
.sh-say .sh-say-mark { font-size: .72rem; line-height: 1; }
.sh-say-n { font-family: var(--mono); font-size: .54rem; font-weight: 700; }
.sh-say.has-say { opacity: 1; color: var(--muted); }
.sh-say.is-open { opacity: 1; color: var(--accent); }
.sh-th-say { font-family: var(--mono); font-size: .6rem; text-transform: uppercase;
    letter-spacing: .1em; font-weight: 700; color: var(--muted); }
.sh-vcount--say { color: var(--accent); }
.sh-vcount--say[hidden] { display: none; }

/* ---- the thread ---------------------------------------------
   One panel the page moves to whichever row was asked about —
   the same reason the tag picker is one menu, rather than forty
   rows each carrying a hidden form. */
.sh-say-panel { position: fixed; z-index: 60; width: min(22rem, 92vw);
    background: var(--panel); border: 1px solid var(--line-strong);
    box-shadow: 0 10px 30px rgba(20,24,30,.18); }
.sh-say-panel[hidden] { display: none; }
.sh-say-head { display: flex; align-items: center; gap: .5rem;
    padding: .5rem .6rem; border-bottom: 1px solid var(--line); }
.sh-say-title { flex: 1; font-weight: 600; font-size: .84rem; color: var(--ink);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.sh-say-tick, .sh-say-x { background: none; border: 0; cursor: pointer; padding: 0 .15rem;
    font-family: var(--mono); font-size: .56rem; text-transform: uppercase;
    letter-spacing: .09em; font-weight: 700; color: var(--muted); }
.sh-say-tick:hover { color: var(--c-in); }
.sh-say-x { font-family: var(--font); font-size: .95rem; letter-spacing: 0; }
.sh-say-x:hover { color: var(--danger); }

.sh-say-list { max-height: 15rem; overflow-y: auto; padding: .5rem .6rem; }
.sh-say-none { font-size: .78rem; color: var(--muted); padding: .3rem 0; }
.sh-say-item { padding: .4rem 0; border-bottom: 1px solid var(--line); }
.sh-say-item:last-child { border-bottom: 0; }
.sh-say-item.is-done { opacity: .55; }
.sh-say-who { font-family: var(--mono); font-size: .56rem; text-transform: uppercase;
    letter-spacing: .09em; font-weight: 700; color: var(--muted);
    display: flex; justify-content: space-between; gap: .5rem; }
.sh-say-item--mine .sh-say-who { color: var(--accent); }
.sh-say-when { font-weight: 400; color: var(--line-strong); }
.sh-say-body { font-size: .82rem; color: var(--ink); margin-top: .15rem;
    overflow-wrap: anywhere; }

.sh-say-form { border-top: 1px solid var(--line); padding: .5rem .6rem;
    display: flex; flex-direction: column; gap: .35rem; }
.sh-say-name, .sh-say-text { font: inherit; font-size: .82rem; padding: .35rem .4rem;
    border: 1px solid var(--line); background: var(--panel); color: var(--ink); }
.sh-say-text { resize: vertical; min-height: 3.4rem; }
.sh-say-name:focus, .sh-say-text:focus { outline: none; border-color: var(--accent); }
.sh-say-form .sh-btn { align-self: flex-end; }

/* ---- the one column they can set ----------------------------
   Marked on both surfaces: on yours so you can see what you gave
   away, on theirs so the one thing they can touch looks touchable
   on a page where nothing else is. */
.sh-th.is-theirs-col { box-shadow: inset 0 -2px 0 color-mix(in srgb, var(--c-in) 60%, transparent); }
.sh-th.is-theirs-col::after { content: 'theirs'; display: block;
    font-family: var(--mono); font-size: .48rem; letter-spacing: .1em;
    color: var(--c-in); margin-top: .1rem; }
.sh-td.is-theirs { cursor: pointer; }
.sh-td.is-theirs .sh-pillbtn, .sh-td.is-theirs .sh-ynbtn,
.sh-td.is-theirs .sh-checkbtn { cursor: pointer; }
/* An empty box in a column they may set has to look like an invitation rather
   than like a column heading they have no business in — the same job the dashed
   empty pill does one line below. */
.sh-td--check.is-theirs .sh-check {
    border-style: dashed; border-color: color-mix(in srgb, var(--c-in) 55%, var(--line-strong)); }
.sh-td--check.is-theirs .sh-check.is-on { border-style: solid; }
.sh-td.is-theirs:hover { background: color-mix(in srgb, var(--c-in) 8%, transparent); }
.sh-td.is-theirs .sh-pill--empty { border: 1px dashed var(--line-strong); padding: .2rem .5rem; }

/* ---- telling them they may write on it ----------------------
   A control nobody has been told about is a control nobody uses.
   The mark at the end of a row is legible once you know what it
   is for; this is how you come to know. */
.sh-invite { display: flex; align-items: baseline; gap: .55rem;
    margin: 0 0 1.4rem; padding: .6rem .8rem;
    font-size: .84rem; color: var(--ink-soft);
    background: color-mix(in srgb, var(--accent) 7%, transparent);
    border-left: 2px solid var(--accent); }
.sh-invite b { color: var(--ink); font-weight: 600; white-space: nowrap; }
.sh-invite-mark { color: var(--accent); font-size: .8rem; line-height: 1; }

/* ---- the control on their copy ------------------------------
   Labelled, not a glyph. You know what the mark means because you
   turned it on; they are reading a table and have been given no
   reason to go hunting at the end of a row. */
.sh-saybtn { display: inline-flex; align-items: center; gap: .25rem;
    background: none; cursor: pointer; padding: .22rem .5rem;
    border: 1px dashed var(--line-strong);
    font-family: var(--mono); font-size: .56rem; text-transform: uppercase;
    letter-spacing: .09em; font-weight: 700; color: var(--muted); white-space: nowrap; }
.sh-saybtn:hover { color: var(--accent); border-color: var(--accent);
    background: color-mix(in srgb, var(--accent) 8%, transparent); }
.sh-saybtn.has-say { border-style: solid; border-color: var(--accent);
    color: var(--accent); background: color-mix(in srgb, var(--accent) 10%, transparent); }
.sh-say-plus { font-size: .7rem; line-height: 1; }
.sh-td--say { text-align: right; white-space: nowrap; }
.sh-th--say { text-align: right; }

/* The notes column stays put while the table scrolls sideways. A wide
   sheet otherwise hides the one control on the page they are meant to
   use, off the right edge, behind a scrollbar they may not think to use. */
.sh-page .sh-td--say, .sh-page .sh-th--say {
    position: sticky; right: 0; z-index: 3; background: var(--panel);
    box-shadow: -1px 0 0 var(--line); }
.sh-page tr.sh-row--marked .sh-td--say {
    background: color-mix(in srgb, var(--sh-mark) 11%, var(--panel)); }
.sh-page .sh--scrolled .sh-td--say, .sh-page .sh--scrolled .sh-th--say {
    box-shadow: -1px 0 0 var(--line), -7px 0 12px -7px rgba(20,24,30,.3); }

@media (max-width: 620px) {
    .sh-invite { font-size: .8rem; }
    .sh-saybtn { padding: .2rem .35rem; }
}
/* The notes column has a real width reserved for it in the layout (see
   render_sheet_body), so the cell should use it rather than shrink-wrapping. */
.sh-td--say, .sh-th--say { width: auto; }

/* ---- nothing spills over the column beside it ----------------
   Under fixed layout a cell narrower than its contents does not
   shrink politely — it overflows, and a pill or a timecode lands
   on top of the next column. The table's own floor (see
   render_sheet_body) is what stops columns getting that narrow in
   the first place; this is the guarantee underneath it, for the
   one you drag too far by hand. */
.sh-td--tag, .sh-td--yesno, .sh-td--image, .sh-td--link { overflow: hidden; }
.sh-pill { max-width: 100%; overflow: hidden; text-overflow: ellipsis; vertical-align: bottom; }
.sh-th { overflow: hidden; }
/* The resize handle sits a hair outside its header, so that one keeps its
   overflow — clipping it is clipping the thing you grab. */
.sh--edit .sh-th { overflow: visible; }
.sh--edit .sh-th .sh-thline, .sh--edit .sh-th .sh-thtools {
    overflow: hidden; }

/* ---------------------------------------------------------------------------
   Signature builder (/email/signatures) — fields on the left, every design
   carrying them on the right. Prefixed sig- because .chip/.card and friends
   already mean something else in here.
   ------------------------------------------------------------------------- */
.sig-build { display: grid; grid-template-columns: 20rem 1fr; gap: 1.4rem; align-items: start; }
.sig-fields { position: sticky; top: 1rem; display: flex; flex-direction: column; gap: .7rem; }
.sig-fields label { display: block; margin: 0; }
.sig-fields input[type="text"] { width: 100%; }

.sig-picks { display: flex; flex-direction: column; gap: .45rem; margin: -.2rem 0 .2rem; }
.sig-pick-group { display: flex; flex-wrap: wrap; align-items: center; gap: .3rem; }
.sig-pick-label {
    font-family: var(--mono); font-size: .58rem; text-transform: uppercase;
    letter-spacing: .12em; color: var(--muted); width: 4.2rem; flex: none;
}
.sig-pick {
    font-family: var(--mono); font-size: .64rem; color: var(--ink);
    background: none; border: 1px solid var(--line); padding: .2em .5em; cursor: pointer;
}
.sig-pick:hover, .sig-pick:focus-visible { border-color: var(--accent); color: var(--accent); }

.sig-designs { display: flex; flex-direction: column; gap: 1.4rem; }
.sig-design { border: 1px solid var(--line); background: var(--panel); }
.sig-design.is-picked { border-color: var(--accent); }
.sig-design-head {
    display: flex; justify-content: space-between; align-items: flex-start;
    gap: 1rem; padding: .9rem 1rem; border-bottom: 1px solid var(--line);
}
.sig-design-head h3 { margin: 0; font-size: 1rem; display: flex; align-items: baseline; gap: .5rem; }
.sig-design-head p { margin: .2rem 0 0; }
.sig-drops { color: var(--accent) !important; }
.sig-live {
    font-family: var(--mono); font-size: .55rem; text-transform: uppercase;
    letter-spacing: .12em; color: var(--accent); border: 1px solid var(--accent); padding: .15em .45em;
}
.sig-design-actions { display: flex; gap: .4rem; flex: none; }

.sig-grounds { display: grid; grid-template-columns: 1fr 1fr; gap: 1px; background: var(--line); }
.sig-ground { padding: 1.4rem 1.1rem; overflow-x: auto; }
.sig-light { background: #FFFFFF; }
.sig-dark  { background: #1B1E21; }

@media (max-width: 900px) {
    .sig-build { grid-template-columns: 1fr; }
    .sig-fields { position: static; }
    .sig-grounds { grid-template-columns: 1fr; }
}

/* ============================================================
   SHEET — FOURTH PASS: THE READING PASS

   The table was hard to read, and it was hard to read for three
   reasons that had nothing to do with each other:

   1. It was set in a display face. Space Grotesk is right for a
      heading and wrong for thirty rows of note text, and the
      table carried NINE different font sizes.
   2. It had horizontal rules and no vertical ones, so past about
      six columns the eye stopped tracking the row it was on.
   3. It was a document on a scrolling page that also had a
      scrolling box inside it, so which scrollbar you were in was
      a guess — and a different guess per browser.

   This section answers all three, and it sits at the end of the
   file so it wins on order rather than on specificity.
   See SHEET-REDESIGN.md.
   ============================================================ */

/* ---- 1. THE READING FACE ------------------------------------
   The page keeps the brand voice — the sheet's title, the app
   around it. The DATA is set in the reading face, which is what
   Inter is for: built for small sizes, real tabular figures, open
   apertures. Codes, numbers and the row count stay mono, because
   being mono is the point of those columns. */
.sh-table { --sh-rule: color-mix(in srgb, var(--line) 70%, transparent); }
.sh-td, .sh-cellin, .sh-blurb, .sh-linklabel, .sh-linkurl { font-family: var(--font-read); }
.sh-td--mono, .sh-td--number, .sh-td--n { font-family: var(--mono); }
.sh-td--number, .sh-td--n { font-variant-numeric: tabular-nums; }

/* Three sizes in the table, not nine: a header line, a body line
   and a meta line. Everything scales off --sh-fs together, so the
   ratios between them survive every step of the size control. */
.sh-th { font-size: calc(.6875rem * var(--sh-fs, 1)); letter-spacing: .055em;
    color: var(--ink-soft); padding: .5rem .7rem .45rem; }
.sh-td            { font-size: calc(.8125rem * var(--sh-fs)); }
.sh-td--mono      { font-size: calc(.78rem   * var(--sh-fs)); }
.sh-td--number    { font-size: calc(.8125rem * var(--sh-fs)); }
.sh-td--n         { font-size: calc(.6875rem * var(--sh-fs)); }
.sh-yn            { font-size: calc(.8125rem * var(--sh-fs)); }
.sh-linkurl       { font-size: calc(.75rem   * var(--sh-fs)); }
.sh-td--link .sh-linklabel { font-size: calc(.8125rem * var(--sh-fs)); }
/* Long notes need a line to breathe on. There was no line-height
   declared here at all, so a wrapped cell was a wall. */
.sh-cellin { line-height: 1.42; }
.sh-td--name .sh-cellin { font-weight: 500; color: var(--ink); }
.sh-td--text .sh-cellin { color: var(--ink); }

/* A pill is a STATUS, and a status you have to decode is not one.
   These were 10px mono, uppercase, tracked out a tenth of an em —
   which is a texture, not a word. Sentence case in the reading
   face at a size you can read: "Waiting on client" now looks like
   what it says instead of like a barcode. */
.sh-pill { font-family: var(--font-read); font-size: calc(.6875rem * var(--sh-fs));
    font-weight: 600; text-transform: none; letter-spacing: 0;
    padding: .22rem .5rem; line-height: 1.3; }

/* ---- 2. THE GRID --------------------------------------------
   A table reads as a grid because of the VERTICAL hairlines. It
   had none — only a rule under each row — so on a wide sheet the
   eye lost the row it was tracking. Kept very light: enough to
   guide, not enough to cage. The machinery columns (drag handle,
   row tools) stay open, because they are not data. */
.sh-th, .sh-td { border-right: 1px solid var(--sh-rule); }
.sh-th--grip, .sh-td--grip,
.sh-th--tools, .sh-td--tools,
.sh-th:last-child, .sh-td:last-child { border-right: 0; }

/* One hover, and it is the row rather than the cell: what you are
   checking on a wide table is always a whole line of it. A row
   carrying a highlight keeps its own colour and is left alone. */
.sh-table tbody tr:not(.sh-row--marked):hover > .sh-td {
    background: color-mix(in srgb, var(--accent) 5%, var(--panel)); }

/* The header stays put while the rows go past. border-collapse
   drops a border off a sticky cell — the border belongs to the
   collapsed grid, not to the element — so the rule under the
   header is drawn as an inset shadow, which travels with it. */
.sh-th { position: sticky; top: 0; z-index: 5; background: var(--panel);
    border-bottom: 0; box-shadow: inset 0 -1px 0 var(--line-strong); }
.sh-th[data-tint] { background: color-mix(in srgb, var(--sh-tint) 12%, var(--panel)); }

/* NOTHING MAY COME TO REST UNDER THE HEADER.
   Tab walks across the row and Enter walks down the column, and the browser
   brings the cell it focused into view by scrolling it flush with the top of the
   window — which is under the save bar and under the header, so the cell you
   just moved to is the one cell you cannot see. A floating header that hides the
   row you are typing in is worse than no floating header.

   scroll-margin-top is the whole fix: it tells the browser the cell starts that
   much higher than it does, so every scroll-into-view — Tab, Enter, find, an
   anchor — stops with the row clear of both. Measured in sheet.js rather than
   guessed, because the bar wraps on a narrow window and the header grows with
   the type size. The fallbacks are what the two are at their smallest. */
.sh-td, .sh-th, .sh-table [data-row] {
    scroll-margin-top: calc(var(--sh-bar-h, 41px) + var(--sh-head-h, 3.2rem) + .5rem); }
.sh-th.sh-freeze { z-index: 7; }

/* ---- 3. ONE SCROLL SURFACE ----------------------------------
   The editor is the one page in the app that is a GRID rather
   than a document: it fills the window and scrolls inside itself,
   so there is exactly one vertical scrollbar and one horizontal
   one and both belong to the table. Hiding a scrollbar on a grid
   means nobody can tell there is more to the right, which is what
   made this confusing across browsers — so they are visible, thin
   and styled, and the gutter is reserved so the table doesn't
   twitch sideways when one appears.

   Desktop only. On a phone the sheet stays a page that scrolls,
   because a viewport-height grid inside a page that also has a
   top bar and a floating bottom nav is a grid with no room. */
.sh-tablewrap { scrollbar-width: thin; scrollbar-color: var(--line-strong) transparent; }
.sh-tablewrap::-webkit-scrollbar { width: 13px; height: 13px; }
.sh-tablewrap::-webkit-scrollbar-track { background: var(--bg); }
.sh-tablewrap::-webkit-scrollbar-thumb { background: var(--line-strong);
    border: 3px solid var(--bg); }
.sh-tablewrap::-webkit-scrollbar-thumb:hover { background: var(--muted); }
.sh-tablewrap::-webkit-scrollbar-corner { background: var(--bg); }

/* ============================================================
   THE PAGE SCROLLS, AND THE TABLE IS THE LAST THING ON IT

   This was a viewport-height grid that scrolled inside itself.
   It solved the two-scrollbars problem the honest way — one pane,
   one scrollbar — and it was still wrong to use: a table in a box
   is a table you scroll a box to read, and the wheel stops
   meaning what it means on every other page.

   So the page scrolls, and everything that is NOT the table sits
   above it: the client link, the title, the counted strip, the
   view bar, Add a row and Paste. The table comes last and simply
   keeps going. Nothing below it means nothing you have to reach
   the end of four hundred rows to press.

   The header row still stays put — stuck to the page now, under
   the save bar, rather than to a pane. See .sh--wide below for
   the one case where it cannot.
   ============================================================ */
@media (min-width: 769px) {
    /* margin:0 matters as much as max-width here. .wrap centres itself with
       `margin: 0 auto`, which is right for a document and wrong for a table:
       the sheet takes the window. */
    body.grid-app main.wrap { max-width: none; margin: 0; padding: 1rem 1.1rem 2rem; }
    body.grid-app .sh-canvas { padding: 1.3rem 1.5rem 1.6rem; }
    body.grid-app .sh-head { margin-bottom: .8rem; }
    body.grid-app .sh-title { font-size: 1.4rem; }

    /* The save bar rides at the top of the window, and the table's header row
       stops just under it. --sh-bar-h is what one owes the other; it is the
       only number the two rules share. */
    body.grid-app { --sh-bar-h: 41px; }
    body.grid-app .sh-bar { position: sticky; top: 0; z-index: 30; margin-bottom: .9rem; }
    body.grid-app .sh-th  { top: var(--sh-bar-h); }
}

/* Add a row and Paste, above the table rather than under it. */
.sh-adds { margin: 0 0 .55rem; }

/* ---- the one case the header cannot stay put ----------------
   A table wider than the window has to scroll sideways, and the
   only way to scroll one axis of a box is to make the box a
   scroll container in both — at which point `sticky` inside it
   measures against a box that never scrolls vertically, and the
   header stops sticking. So the wrapper is only made a scroller
   when the table actually needs it (sheet.js sets .sh--wide by
   measuring), and a sheet that fits keeps its header.

   Not solvable in CSS alone, which is why it is a class. */
.sh--wide .sh-tablewrap { overflow-x: auto; }
.sh:not(.sh--wide) .sh-tablewrap { overflow: visible; }

/* AND THE HEADER HAS TO BE TOLD TO STOP TRYING.
   Saying the header "stops sticking" above was half true, and the missing half
   is a bug you can see. overflow-x: auto forces overflow-y from visible to auto,
   so the wrapper becomes a scroll container in a direction it never scrolls —
   and a sticky cell inside it is left HALF ENGAGED. Chromium quietly drops it
   and the header scrolls away. Firefox keeps honouring the `top` offset against
   that dead scrollport and paints the header 41px DOWN, on top of the first
   rows: the header appears to crop into row one, and the sheet is unusable.

   So a wide sheet gives its header's vertical pin up explicitly. `top: auto`
   and not `position: static`, because the same element is still sticky
   HORIZONTALLY — that is what freezes the first column — and static would take
   the freeze with it.

   Specificity is deliberate: this has to out-rank `body.grid-app .sh-th`, which
   is where the pin is set. */
body.grid-app .sh--wide .sh-th { top: auto; }

/* ---- TYPE SIZE — five steps, one knob -----------------------
   There were two controls that both meant "make it smaller": a
   type size on the document and a Zoom in the browser. Zoom is
   gone (see the note where its rule used to be) and its range
   came here, so one control means one thing and the size you set
   is the size the client opens. */
.sh-table[data-text="xs"]     { --sh-fs: .82; }
.sh-table[data-text="small"]  { --sh-fs: .91; }
.sh-table[data-text="normal"] { --sh-fs: 1; }
.sh-table[data-text="large"]  { --sh-fs: 1.12; }
.sh-table[data-text="xl"]     { --sh-fs: 1.26; }

/* ---- THE VIEW BAR, LEGIBLE ----------------------------------
   Fourteen buttons in five groups, every one of them set in 9px
   uppercase mono, became four things in words at a size you can
   hit. Where it all went: row height, type size and the number
   column are behind Display; Zoom is gone; Comments moved up to
   the editor's bar, because it is a decision about the client's
   copy rather than about how this table is drawn. */
.sh-view { gap: .45rem; margin: 0 0 .5rem; }
.sh-vbtn { font-family: var(--font-read); font-size: .75rem; text-transform: none;
    letter-spacing: 0; font-weight: 500; color: var(--ink-soft);
    padding: .34rem .62rem; min-height: 28px; line-height: 1.2; }
.sh-vbtn--icon { min-width: 28px; padding: .28rem .4rem; font-size: .9rem;
    display: inline-flex; align-items: center; justify-content: center; }
.sh-vbtn--menu { display: inline-flex; align-items: center; gap: .35rem; font-weight: 600; }
.sh-vcaret { font-size: .6rem; color: var(--muted); }
.sh-vbtn--menu:hover .sh-vcaret, .sh-vbtn--menu.is-on .sh-vcaret { color: inherit; }
.sh-vlabel { font-size: .58rem; }
.sh-vcount { font-family: var(--font-read); font-size: .72rem; text-transform: none;
    letter-spacing: 0; font-weight: 600; }
.sh-vchip { font-family: var(--font-read); font-size: .72rem; text-transform: none;
    letter-spacing: 0; font-weight: 500; padding: .28rem .55rem; }

/* ---- THE DISPLAY POPOVER ------------------------------------
   Row height and the number column are genuinely a CHOICE, so
   each option gets a line and a reason — nobody was ever going to
   guess what "Fixed" meant from four letters in a toolbar. Type
   size needs no words: each button is drawn at the size it sets. */
.sh-display { position: fixed; z-index: 55; min-width: 17.5rem; max-width: 21rem;
    background: var(--panel); border: 1px solid var(--line-strong);
    box-shadow: 0 6px 20px rgba(20,24,30,.14); padding: .3rem; }
.sh-display[hidden] { display: none; }
.sh-display-s { font-family: var(--mono); font-size: .55rem; text-transform: uppercase;
    letter-spacing: .1em; font-weight: 700; color: var(--muted);
    padding: .45rem .5rem .3rem; }
.sh-display-s:not(:first-child) { border-top: 1px solid var(--line); margin-top: .25rem; }
.sh-dopt { display: block; width: 100%; text-align: left; background: none; border: 0;
    cursor: pointer; padding: .38rem .5rem; font: inherit; }
.sh-dopt:hover { background: var(--wash); }
.sh-dopt-n { display: block; font-family: var(--font-read); font-size: .82rem;
    font-weight: 500; color: var(--ink); }
.sh-dopt-d { display: block; font-family: var(--font-read); font-size: .72rem;
    line-height: 1.35; color: var(--muted); margin-top: .1rem; }
.sh-dopt.is-on { background: color-mix(in srgb, var(--accent) 8%, transparent); }
.sh-dopt.is-on .sh-dopt-n { color: var(--accent); font-weight: 600; }
.sh-dopt.is-on .sh-dopt-n::after { content: " \2713"; }
.sh-dsizes { display: flex; align-items: baseline; gap: .3rem; padding: .1rem .5rem .45rem; }
.sh-dsize { font-family: var(--font-read); font-weight: 600; color: var(--ink-soft);
    cursor: pointer; background: none; border: 1px solid var(--line);
    padding: .2rem .45rem; line-height: 1.1; }
.sh-dsize:hover { color: var(--accent); border-color: color-mix(in srgb, var(--accent) 45%, var(--line)); }
.sh-dsize.is-on { color: var(--accent); border-color: var(--accent);
    background: color-mix(in srgb, var(--accent) 8%, transparent); }
.sh-dsize[data-size="xs"]     { font-size: .62rem; }
.sh-dsize[data-size="small"]  { font-size: .72rem; }
.sh-dsize[data-size="normal"] { font-size: .82rem; }
.sh-dsize[data-size="large"]  { font-size: .94rem; }
.sh-dsize[data-size="xl"]     { font-size: 1.08rem; }

/* ---- COMMENTS, UP IN THE BAR --------------------------------
   On the dark bar, so its "on" state is lit rather than tinted. */
.sh-bar-talk.is-on { background: var(--accent); border-color: var(--accent); color: #fff; }
.sh-bar-talk.is-on:hover { background: var(--accent-glow); border-color: var(--accent-glow); }
.sh-bar .sh-vcount--say { color: var(--accent-glow); white-space: nowrap; }

/* ---- PHONES -------------------------------------------------
   No grid frame down here: the sheet stays a page that scrolls.
   The Display popover becomes a sheet off the bottom, because a
   17rem popover anchored to a button is most of a phone. */
@media (max-width: 768px) {
    .sh-view { gap: .4rem; }
    .sh-display { position: fixed; left: 0 !important; right: 0;
        top: auto !important; bottom: 0; max-width: none; min-width: 0;
        border-left: 0; border-right: 0; border-bottom: 0;
        padding: .3rem .3rem 1.1rem; }
    .sh-dopt { padding: .55rem .6rem; }
    .sh-dsize { padding: .35rem .6rem; }
}

/* ============================================================
   SHEET — FIFTH PASS: A TYPE THAT LOOKS LIKE ITSELF

   The reading pass made the table legible. This one makes each
   COLUMN legible: a timecode should not be able to be mistaken
   for a note, and you should not have to open a menu to find out
   which is which. Two halves —

   1. a glyph at the head of every column, always visible, which
      is also the button that opens the type picker;
   2. a distinct hand for each type that has one to be written in.

   See SHEET-REDESIGN.md, Phase 2.
   ============================================================ */

/* ---- the glyph at the head of a column ----------------------
   It replaced a word ("Text", "Number") that lived in the row of
   tools underneath and so only appeared on hover. A type nobody
   can see is a type nobody knows a column has, which wastes the
   whole point of having types. Quiet until you go near it —
   this is a label most of the time and a button when wanted. */
.sh-colglyph {
    flex: none; display: inline-flex; align-items: center; justify-content: center;
    width: 1.15rem; height: 1.15rem; padding: 0; cursor: pointer;
    background: none; border: 1px solid transparent;
    font-family: var(--mono); font-size: .72rem; line-height: 1;
    color: var(--muted); transition: color .12s, border-color .12s, background .12s;
}
.sh-th:hover .sh-colglyph { border-color: var(--line); }
.sh-colglyph:hover, .sh-colglyph:focus-visible {
    color: var(--accent); border-color: var(--accent);
    background: color-mix(in srgb, var(--accent) 8%, transparent); }
/* The four types whose glyph carries a bit of meaning on its own get
   a touch of their column's colour, so a wide sheet reads as a shape
   before it reads as words. */
.sh-th--timecode .sh-colglyph,
.sh-th--duration .sh-colglyph { color: var(--c-out); }
.sh-th--date .sh-colglyph     { color: #2F6FB0; }
.sh-th--check .sh-colglyph    { color: var(--c-in); }

/* The frame rate, beside the name of a timecode column. A timecode
   is not a number — 00:00:01:00 is 24 frames at 24 and 25 at 25 —
   so the rate is part of what the column MEANS and belongs where
   the column is named, not three clicks away in a menu. */
.sh-thfps {
    flex: none; font-family: var(--mono); font-size: .58rem; font-weight: 600;
    letter-spacing: .04em; color: var(--muted);
    border: 1px solid var(--line); padding: 0 .22rem; line-height: 1.5;
}

/* ---- timecode and duration ---------------------------------
   Mono, tabular, right-aligned and held to one line, on a wash
   the width of the column. The wash is what does the work: it
   says "this column is machine-readable" from across the table,
   before a single figure has been read. */
.sh-td--timecode, .sh-td--duration {
    font-family: var(--mono); font-variant-numeric: tabular-nums;
    text-align: right; white-space: nowrap;
    background: color-mix(in srgb, var(--c-out) 5%, transparent);
}
.sh-td--timecode .sh-cellin, .sh-td--duration .sh-cellin {
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
    letter-spacing: .01em; color: var(--ink);
}
.sh-th--timecode, .sh-th--duration { text-align: right; }
.sh-th--timecode .sh-thline, .sh-th--duration .sh-thline,
.sh-th--number .sh-thline { justify-content: flex-start; }
/* Typing wants the whole value however narrow the column is. */
.sh-td--timecode .sh-cellin:focus,
.sh-td--duration .sh-cellin:focus { overflow: visible; text-overflow: clip; }
/* A row highlight and a column tint both outrank the type wash: one
   is you saying "this row", the other is you saying "this column",
   and the wash is only saying what kind of thing it is. */
.sh-row--marked > .sh-td--timecode,
.sh-row--marked > .sh-td--duration,
.sh-td--timecode[data-tint], .sh-td--duration[data-tint] { background-image: none; }

/* ---- date ---------------------------------------------------
   The day in the reading face, and how far away it is drawn from
   an attribute rather than from a second element — so the cell's
   text stays exactly the date and nothing has to be filtered back
   out of it when it is read. */
.sh-td--date .sh-cellin { white-space: normal; }
/* On its own line, not trailing the date. Inline, "Aug 31, 2026 tomorrow" needs
   a column half again as wide as the date itself, and in a column sized for the
   date the hint is simply clipped off the end — which is the worst of both.
   The \A is a real break because the pseudo-element sets white-space: pre; the
   editable text above it is untouched. A compact sheet clamps to one line and
   loses the hint, which is what compact is for. */
.sh-td--date .sh-cellin[data-rel]::after {
    content: "\A" attr(data-rel);
    white-space: pre;
    font-size: .84em; color: var(--muted); font-variant-numeric: tabular-nums;
}
.sh-td--date .sh-cellin:focus::after { content: none; }   /* out of the way while typing */

/* ---- checkbox -----------------------------------------------
   Two states, one click. A Yes/No is an ANSWER and has three
   states, one of which is "nobody has said"; a checkbox is a job
   done or not, and on a task list that is the whole column. */
.sh-td--check { text-align: center; }
.sh-checkbtn { background: none; border: 0; padding: .1rem; cursor: pointer; line-height: 1;
    display: inline-flex; }
.sh-check {
    display: inline-flex; align-items: center; justify-content: center;
    width: 15px; height: 15px; flex: none;
    border: 1.5px solid var(--line-strong); background: var(--panel);
    font-size: .68rem; line-height: 1; color: transparent;
    transition: background .12s, border-color .12s, color .12s;
}
.sh-checkbtn:hover .sh-check { border-color: var(--accent); }
.sh-checkbtn:focus-visible .sh-check { outline: 2px solid var(--accent); outline-offset: 2px; }
.sh-check.is-on { background: var(--c-in); border-color: var(--c-in); color: #fff; }
/* A ticked row reads as done at a glance, without needing the status
   column that Phase 3 will add. Quiet: it is still a row you can read. */
.sh-td--check[data-value="Yes"] { background: color-mix(in srgb, var(--c-in) 7%, transparent); }

/* ---- person -------------------------------------------------
   A name and its initial, so a column of them reads down the page
   as a column of PEOPLE rather than a column of words — which is
   the whole difference between this and a text column. */
.sh-td--person { white-space: nowrap; }
.sh-face {
    display: inline-flex; align-items: center; justify-content: center;
    width: 18px; height: 18px; flex: none; margin-right: .4rem;
    vertical-align: -.28em;
    background: var(--wash); color: var(--ink-soft);
    font-family: var(--mono); font-size: .6rem; font-weight: 700; line-height: 1;
}
.sh-face:empty { background: none; border: 1px dashed var(--line); }
.sh-facename { display: inline-block; min-width: 3rem; outline: none; }
.sh-td--person:hover .sh-face { background: color-mix(in srgb, var(--accent) 16%, var(--wash)); }

/* ---- the type picker, with its glyphs ----------------------- */
.sh-typeopt { display: grid; grid-template-columns: 1.3rem 1fr; column-gap: .1rem; align-items: baseline; }
.sh-typeopt-g { grid-row: 1 / span 2; font-family: var(--mono); font-size: .8rem;
    color: var(--muted); text-align: center; }
.sh-typeopt:hover .sh-typeopt-g { color: var(--accent); }

/* A row of choices that would otherwise run off the side of the
   menu — eight frame rates do not fit on one line. */
.sh-colmenu-row--wrap { flex-wrap: wrap; gap: .25rem; padding: 0 .4rem .1rem; }

/* ---- phones -------------------------------------------------
   A tick is a tap target before it is a mark. */
@media (max-width: 768px) {
    .sh-checkbtn { padding: .3rem; }
    .sh-check { width: 18px; height: 18px; font-size: .78rem; }
    .sh-colglyph { width: 1.35rem; height: 1.35rem; font-size: .8rem; }
}

/* ============================================================
   SHEET — SIXTH PASS: A STATUS THAT DOES SOMETHING

   One tag column per sheet drives it. Its options carry up to
   three consequences — highlight the row, move it, tick its box —
   applied the moment a status is set and then left alone.
   See SHEET-REDESIGN.md, Phase 3.
   ============================================================ */

/* ---- the counted strip --------------------------------------
   Above the table on both surfaces. It is what makes the rest of
   this legible: "done sinks to the bottom" is a behaviour you
   have to watch happen, "6 done" is a fact you can read. */
.sh-stats { display: flex; flex-wrap: wrap; gap: .35rem; margin: 0 0 .8rem; }
.sh-statchip {
    display: inline-flex; align-items: baseline; gap: .38rem;
    font-family: var(--font-read); font-size: .75rem; font-weight: 500;
    color: var(--ink-soft); cursor: pointer;
    background: none; border: 1px solid var(--line);
    padding: .26rem .55rem .26rem .45rem; line-height: 1.3;
    border-left: 3px solid var(--sh-tone, var(--muted));
    transition: border-color .12s, background .12s, color .12s;
}
.sh-statchip-n { font-family: var(--mono); font-size: .82rem; font-weight: 700;
    font-variant-numeric: tabular-nums; color: var(--ink); }
.sh-statchip:hover { border-color: var(--line-strong); background: var(--panel); }
.sh-statchip[aria-pressed="true"] {
    background: color-mix(in srgb, var(--sh-tone, var(--muted)) 12%, var(--panel));
    border-color: color-mix(in srgb, var(--sh-tone, var(--muted)) 55%, var(--line));
    color: var(--ink); }
/* A status nobody is on stands down rather than disappearing — the
   shape of the strip is part of what it tells you, and a row of
   chips that changes length every time you tick something is a row
   you have to re-read. */
.sh-statchip.is-none { opacity: .45; }
/* display: inline-flex above outranks the browser's [hidden] rule — the same
   trap as .sh-colmenu-row and .sh-colact. */
.sh-statchip[hidden] { display: none; }
.sh-statchip.is-none .sh-statchip-n { color: var(--muted); }
/* The client's copy is the same strip as a readout: filtering runs off
   machinery their page does not load, and a control that does nothing
   when pressed is worse than no control. */
.sh-stats--read .sh-statchip { cursor: default; }
.sh-stats--read .sh-statchip:hover { border-color: var(--line); background: none; }

.sh-statchip--grey  { --sh-tone: var(--muted); }
.sh-statchip--blue  { --sh-tone: #2F6FB0; }
.sh-statchip--green { --sh-tone: var(--c-in); }
.sh-statchip--ember { --sh-tone: var(--accent); }
.sh-statchip--red   { --sh-tone: var(--danger); }
.sh-statchip--plum  { --sh-tone: var(--c-out); }

/* ---- what a status does, in the categories panel ------------
   Words, not glyphs — the lesson of the view bar. "Bottom" says
   what it does; a downward arrow needs a tooltip and a guess.
   Only drawn on the column that drives the sheet, because on any
   other tag column they would do nothing. */
.sh-optpanel.is-status { min-width: 15.5rem; }
.sh-optpanel.is-status .sh-optrow {
    flex-wrap: wrap; padding-bottom: .42rem; }
.sh-optpanel.is-status .sh-optrow + .sh-optrow {
    border-top: 1px solid var(--line); padding-top: .42rem; }
.sh-optdoes { flex: 0 0 100%; display: flex; flex-wrap: wrap; gap: .22rem;
    margin: .3rem 0 0 1.1rem; }
.sh-doeschip {
    font-family: var(--font-read); font-size: .68rem; font-weight: 500;
    color: var(--muted); cursor: pointer;
    background: none; border: 1px solid var(--line);
    padding: .16rem .38rem; line-height: 1.25;
}
.sh-doeschip:hover { color: var(--accent);
    border-color: color-mix(in srgb, var(--accent) 45%, var(--line)); }
.sh-doeschip.is-on { color: var(--accent); border-color: var(--accent);
    background: color-mix(in srgb, var(--accent) 8%, transparent); font-weight: 600; }

/* ---- the column that drives the sheet -----------------------
   Marked in the header the same way a column open to the client
   is, and for the same reason: a column with consequences should
   not look like every other column. */
.sh-th.is-status-col,
.sh-th[data-status-col] {
    box-shadow: inset 0 -1px 0 var(--line-strong),
                inset 0 -3px 0 color-mix(in srgb, var(--accent) 55%, transparent); }
.sh-th[data-status-col] .sh-colglyph { color: var(--accent); }

/* ---- phones ------------------------------------------------- */
@media (max-width: 768px) {
    .sh-stats { gap: .3rem; margin-bottom: .6rem; }
    .sh-statchip { font-size: .72rem; padding: .3rem .5rem .3rem .4rem; }
    .sh-doeschip { padding: .28rem .5rem; font-size: .72rem; }
}

/* ---- the "start from" picker --------------------------------
   In the Documents hub it is a second labelled row of the New
   sheet form; inside a New document ▾ menu it is a small select
   under the button that submits it. See sheet_templates(). */
.inline-form--start { display: flex; flex-direction: column; align-items: stretch; gap: .2rem; }
.inline-form--start .menu-sel {
    font-family: var(--font-read); font-size: .72rem; color: var(--ink-soft);
    background: var(--panel); border: 1px solid var(--line);
    padding: .22rem .3rem; max-width: 100%; }
.inline-form--start .menu-sel:hover,
.inline-form--start .menu-sel:focus { border-color: var(--accent); color: var(--ink); outline: none; }

/* ============================================================
   WHAT THEY SAID, AS A COLUMN

   It was a ◗ in the row tools and a floating panel: to find out
   whether anybody had written on row 14 you clicked row 14, and
   to see the next one you closed that and clicked again. A note
   about a row belongs beside the row.

   The cell is a PREVIEW — the newest note, clamped by the sheet's
   own row height like any other long cell. Clicking it opens the
   thread, which is where the conversation and the reply box are.
   Not a column of the document: it cannot be renamed, retyped,
   moved or saved. See sh_say_cell_html() in sheet_render.php.
   ============================================================ */
.sh-th--saycol { background: var(--panel); }
.sh-th--saycol .sh-thlabel { color: var(--ink-soft); }
/* The glyph on a column you cannot retype is a label, not a button. */
.sh-colglyph--fixed { cursor: default; color: var(--muted); }
.sh-th:hover .sh-colglyph--fixed { border-color: transparent; }

.sh-td--saycol { padding: 0; vertical-align: top;
    background: color-mix(in srgb, var(--wash) 40%, transparent); }
.sh-saycell {
    display: block; width: 100%; text-align: left; cursor: pointer;
    background: none; border: 0; padding: .42rem .6rem; font: inherit;
}
.sh-saycell:hover { background: color-mix(in srgb, var(--accent) 7%, transparent); }
.sh-saycell-h { display: flex; align-items: center; gap: .3rem; margin-bottom: .1rem; }
.sh-saycell-who {
    font-family: var(--font-read); font-size: calc(.6875rem * var(--sh-fs, 1));
    font-weight: 600; color: var(--ink-soft); }
.sh-saycell-n {
    font-family: var(--mono); font-size: calc(.6rem * var(--sh-fs, 1)); font-weight: 700;
    color: var(--muted); background: var(--wash); padding: 0 .25rem; line-height: 1.5; }
/* Not ticked off. The one thing worth seeing from across the table. */
.sh-saycell-dot { width: 6px; height: 6px; flex: none; background: var(--accent);
    border-radius: 50%; }
.sh-saycell-body {
    display: -webkit-box; -webkit-box-orient: vertical; overflow: hidden;
    font-family: var(--font-read); font-size: calc(.78rem * var(--sh-fs, 1));
    line-height: 1.35; color: var(--ink); overflow-wrap: anywhere; }
/* The preview obeys the sheet's own row height, so this column never makes a
   row taller than the row height you asked for. */
.sh-table[data-density="compact"] .sh-saycell-body { -webkit-line-clamp: 1; }
.sh-table[data-density="normal"]  .sh-saycell-body { -webkit-line-clamp: 2; }
.sh-table[data-density="full"]    .sh-saycell-body { -webkit-line-clamp: 6; }

/* Nothing said yet: a way in that stays out of the way. */
.sh-td--saycol.is-empty { background: none; }
.sh-saynew {
    display: block; width: 100%; text-align: left; cursor: pointer;
    background: none; border: 0; padding: .42rem .6rem; font: inherit;
    font-family: var(--mono); font-size: .78rem; color: var(--line-strong);
    opacity: 0; transition: opacity .12s, color .12s;
}
.sh-table tr:hover .sh-saynew, .sh-saynew:focus-visible { opacity: 1; }
.sh-saynew:hover { color: var(--accent); }

/* A row with something still open is worth finding from the edge. */
.sh-td--saycol.has-open { box-shadow: inset 2px 0 0 var(--accent); }

/* Preview and the client's page never draw it — theirs is the "Your notes"
   column, which is a different thing: a place to write, not a place to read
   what was written. */
.sh--preview .sh-th--saycol, .sh--preview .sh-td--saycol { display: none; }

@media (max-width: 768px) {
    .sh-saynew { opacity: 1; }
}

/* ============================================================
   THE PAGE IS THE SHEET

   Seven bands of chrome stood between the top of the window and
   the first row: a save bar, a client-link strip, a title, a
   standfirst, the status counts, the view controls, and Add a
   row. On a page you open twenty times a day to tick things off,
   six of those are set-up you did once.

   Two bands now. The dark bar, and ONE toolbar carrying the
   sheet's name, undo, Display, Add a row, Paste and the counts —
   the things you act on. The name and the standfirst fold into a
   panel behind the name; the client link went altogether, because
   the bar above already has "View as client" and "Copy link".
   ============================================================ */
.sh-view { align-items: center; row-gap: .35rem; padding: .1rem 0 .45rem;
    border-bottom: 1px solid var(--line); margin-bottom: .55rem; }

/* The sheet's name, leading the toolbar. A label, not a headline. */
.sh-vname { display: inline-flex; align-items: baseline; gap: .32rem; cursor: pointer;
    background: none; border: 1px solid transparent; padding: .24rem .45rem .24rem .3rem;
    font-family: var(--font); font-size: .95rem; font-weight: 600; letter-spacing: -.01em;
    color: var(--ink); max-width: 30ch; }
.sh-vname-t { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sh-vname:hover { border-color: var(--line); }
.sh-vname.is-on { border-color: var(--accent); color: var(--accent); }

.sh-vsep { width: 1px; align-self: stretch; background: var(--line); margin: .1rem .25rem; }

/* The panel behind the name: the title and the standfirst, exactly as the
   client reads them, edited in place. Open only when you want them. */
.sh-about { border: 1px solid var(--line-strong); background: var(--panel);
    padding: 1rem 1.15rem 1.1rem; margin: 0 0 .7rem;
    box-shadow: 0 4px 14px rgba(20,24,30,.06); }
.sh-about[hidden] { display: none; }
.sh-about .sh-head { margin: 0; }
.sh-about .sh-title { font-size: 1.25rem; margin-bottom: .3rem; }
.sh-about .sh-blurb { font-size: .86rem; }

/* The counts join the toolbar rather than standing as a band. */
.sh-view .sh-stats { margin: 0; gap: .3rem; }
.sh-view .sh-statchip { padding: .24rem .5rem .24rem .38rem; }

/* ---- Held vs booked days ------------------------------------------------
   A studio confirms week by week, so each day carries its own hold/booked and
   the Days list + billing rows say which weeks are sold. Sold is the default
   and wears nothing; a hold is a dashed tag in the hold tint (the same pair of
   tints the status dropdown already uses), a 2nd hold its own, and a half-sold
   week says how much of it is sold. */
.wk-hold { display: inline-block; margin-left: .45rem; padding: .08rem .45rem; border: 1px dashed #c9a27f;
    border-radius: 0; font-family: var(--mono); font-size: .62rem; font-weight: 600; letter-spacing: .06em;
    text-transform: uppercase; color: #9a6a3a; background: #f4e8dd; vertical-align: middle; white-space: nowrap; }
.wk-hold--hold2 { color: #a8542c; background: #f4ddd0; border-color: #d9a48a; }
.wk-hold--partial { color: var(--ink-soft); background: var(--panel); border-color: var(--line); border-style: solid; text-transform: none; letter-spacing: .02em; }
.week-group.wg-hold .wg-label, .week-group.wg-hold2 .wg-label { color: var(--ink-soft); }
.day-chip--held { background: none; border-style: dashed; color: var(--muted); }
.day-chip--hold2 { border-color: #d9a48a; }
.wg-actions { display: inline-flex; align-items: center; gap: .4rem; flex-wrap: wrap; justify-content: flex-end; }
/* "Book week": the one-click confirm. Ink, like every primary move; the removes
   beside it stay the quiet outlined kind. */
.wg-book { background: var(--ink); color: #fff; border: 1px solid var(--ink); border-radius: 0;
    padding: .2rem .6rem; font-size: .74rem; font-weight: 600; cursor: pointer; }
.wg-book:hover { background: var(--accent); border-color: var(--accent); color: #fff; }
.wk-action .wk-held { display: inline-flex; align-items: center; gap: .6rem; }
.wk-action .wk-held .wk-hold { margin-left: 0; }
.wk-action .wg-book { padding: .34rem .7rem; }
/* Add days as booked / hold / 2nd hold — a small segmented switch in the range row. */
.add-days-as { display: inline-flex; border: 1px solid var(--line); background: var(--panel); }
.add-days-as label { display: inline-flex; align-items: center; gap: .3rem; padding: .3rem .6rem; font-size: .78rem;
    color: var(--ink-soft); cursor: pointer; border-right: 1px solid var(--line); white-space: nowrap; }
.add-days-as label:last-child { border-right: 0; }
.add-days-as input { margin: 0; accent-color: var(--ink); }
.add-days-as label:has(input:checked) { background: var(--ink); color: #fff; }
/* The Sheet's Days cell, when some of them are still pencilled. */
.sheet-held { display: block; font-family: var(--mono); font-size: .6rem; font-weight: 600; letter-spacing: .05em;
    text-transform: uppercase; color: #9a6a3a; line-height: 1.2; }

@media (max-width: 768px) {
    .sh-vname { max-width: 14ch; font-size: .88rem; }
    .sh-vsep { display: none; }
}

/* ============================================================
   THE SHEET ON A PHONE — a row is a card

   A table eleven columns wide is unreadable on a screen four
   inches across, and not because the type is small. There is
   nowhere to put eleven things side by side, so shrinking the
   table only makes the same problem harder to look at. On a
   phone the row stops being a row: a headline, the status pill
   beside it, the note underneath, and the small facts run
   together on one line. Everything else waits behind a tap.

   The card is NOT a second rendering. It is the same <tr>, with
   its cells laid out by flex instead of by table layout, which
   is why nothing can drift between the two screens: a hidden
   column, a held row, a column you handed to the client and a
   status that reorders are all decided once, in PHP, and both
   shapes read the same answer.

   Which cell plays which part rides on the cell as data-phone,
   put there by sheet_phone_roles() in app/sheet.php and kept up
   to date in the editor by paintPhone() in sheet.js.
   ============================================================ */

/* Above 768px there is room for the table, and these would be
   controls that do nothing when pressed. Stated out here rather
   than only inside the query so that a panel left open on a
   phone cannot reappear, unstyled, when the window is widened. */
.sh-phonebar { display: none; }
.sh-cardmenu { display: none; }

@media (max-width: 768px) {

/* ---- cards or the table -------------------------------------
   A reading state, not a setting: it never reaches the save, and
   the client's page keeps its own answer. */
.sh-phonebar { display: flex; align-items: center; flex-wrap: wrap; gap: .3rem; margin: .1rem 0 .6rem; }
.sh-phonelab { font-family: var(--mono); font-size: .56rem; text-transform: uppercase;
    letter-spacing: .1em; color: var(--muted); margin-right: .25rem; }
.sh-phonebtn { font-family: var(--font-read, inherit); font-size: .78rem; cursor: pointer;
    min-height: 30px; padding: .3rem .66rem; color: var(--ink-soft);
    border: 1px solid var(--line); background: var(--panel); }
.sh-phonebtn.is-on { border-color: var(--accent); color: var(--accent); font-weight: 600; }
/* Not one of the two views, so it sits away from the pair. */
.sh-phonebtn--set { margin-left: auto; }

/* ---- what goes on a card ------------------------------------
   In the flow under the bar rather than floating: a popover has
   to be positioned, and there is nowhere on a phone to position
   one except exactly here. */
.sh-cardmenu:not([hidden]) {
    display: block;
    margin: -.2rem 0 .7rem; padding: .7rem .8rem .8rem;
    border: 1px solid var(--line-strong); background: var(--panel);
    box-shadow: 0 6px 18px rgba(20, 24, 30, .07); }
.sh-cardmenu-h { font-family: var(--mono); font-size: .58rem; text-transform: uppercase;
    letter-spacing: .1em; color: var(--muted); margin-bottom: .3rem; }
.sh-cardmenu-why { margin: 0 0 .6rem; font-family: var(--font-read, inherit);
    font-size: .76rem; line-height: 1.4; color: var(--ink-soft); }
.sh-cardrow {
    display: flex; align-items: center; justify-content: space-between;
    flex-wrap: wrap; gap: .3rem .5rem; padding: .4rem 0;
    border-top: 1px solid var(--line); }
.sh-cardrow:first-child { border-top: 0; }
.sh-cardrow-n { font-family: var(--font-read, inherit); font-size: .84rem; font-weight: 600;
    color: var(--ink); min-width: 0; overflow-wrap: anywhere; }
/* What a type-driven role is, said rather than offered. */
.sh-cardrow-is { display: block; font-family: var(--mono); font-size: .56rem;
    text-transform: uppercase; letter-spacing: .08em; font-weight: 400; color: var(--muted); }
.sh-cardrow-set { display: flex; gap: 2px; flex: 0 0 auto; }
.sh-cardopt {
    cursor: pointer; font: inherit; font-family: var(--font-read, inherit);
    font-size: .72rem; min-height: 30px; padding: .26rem .5rem;
    border: 1px solid var(--line); background: var(--bg); color: var(--ink-soft); }
.sh-cardopt.is-on { border-color: var(--accent); background: var(--panel);
    color: var(--accent); font-weight: 600; }
.sh-cardreset {
    margin-top: .6rem; width: 100%; cursor: pointer; font: inherit;
    font-family: var(--font-read, inherit); font-size: .78rem; min-height: 32px;
    border: 1px solid var(--line); background: none; color: var(--ink-soft); }
.sh-cardreset:hover, .sh-cardreset:focus-visible { border-color: var(--accent); color: var(--accent); }

/* In table mode the card is not what you are reading. */
.sh--phonetable .sh-cardmenu { display: none; }

/* ---- the table stops being a table --------------------------
   All three display overrides are load-bearing and have to go
   together: take the rows out of table layout without taking out
   the table and its body, and the browser puts back the
   anonymous table boxes it is missing and the row collapses. */
.sh:not(.sh--phonetable) .sh-tablewrap { overflow: visible; }
.sh:not(.sh--phonetable) .sh-table,
.sh:not(.sh--phonetable) .sh-table > tbody { display: block; width: 100%; min-width: 0; }
.sh:not(.sh--phonetable) .sh-table > thead { display: none; }

/* A frozen first column has nothing to freeze against here, and
   paintFreeze() leaves an inline `left` behind on the cells it
   last touched. Static makes that offset inert without having to
   out-shout an inline style. */
.sh:not(.sh--phonetable) .sh-table .sh-freeze { position: static; }

.sh:not(.sh--phonetable) .sh-table > tbody > tr {
    display: flex; flex-wrap: wrap; align-items: flex-start;
    gap: .16rem .5rem; padding: .62rem .72rem .6rem; margin: 0 0 .5rem;
    position: relative;
    border: 1px solid var(--line); background: var(--panel); box-shadow: var(--shadow); }

/* ---- the card has three bands, and they have to STAY three ---
   Left to itself, flex-wrap puts as much on a line as will fit,
   so a number would land beside the status pill on the headline
   row and its neighbours would strand on the next one — which is
   exactly what "things aren't stacking properly" looks like.
   A flex line can only be forced by an item 100% wide, and the
   row's own ::before and ::after are flex items. Two of them, at
   orders that sit between the bands, give: headline · note ·
   facts. Zero height, so they cost nothing, and when there is no
   note the two breaks fall together and read as one.
   The `order` values are spread by tens to leave room between. */
.sh:not(.sh--phonetable) .sh-table > tbody > tr::before,
.sh:not(.sh--phonetable) .sh-table > tbody > tr::after {
    content: ''; flex: 1 0 100%; height: 0; margin: 0; }
.sh:not(.sh--phonetable) .sh-table > tbody > tr::before { order: 35; }
.sh:not(.sh--phonetable) .sh-table > tbody > tr::after  { order: 45; }
/* An open card is one field per line and needs neither. */
.sh:not(.sh--phonetable) .sh-table > tbody > tr.is-phoneopen::before,
.sh:not(.sh--phonetable) .sh-table > tbody > tr.is-phoneopen::after { display: none; }
/* The row highlight is a colour edge down the side of the card
   rather than a wash across it — a card is already a box, and a
   filled box on a white page reads as selected, not as marked. */
.sh:not(.sh--phonetable) .sh-table > tbody > tr.sh-row--marked { box-shadow: inset 3px 0 0 var(--sh-mark), var(--shadow); }
.sh:not(.sh--phonetable) .sh-table > tbody > tr.sh-row--held { opacity: .55; }

/* Cells own nothing on a card: the card carries the padding and
   the rules, so a cell that keeps its own draws a grid inside a
   box that is no longer a grid. */
.sh:not(.sh--phonetable) .sh-table > tbody > tr > .sh-td {
    display: block; border: 0; padding: 0; white-space: normal; vertical-align: baseline; }
.sh:not(.sh--phonetable) .sh-table > tbody > tr > .sh-td .sh-cellin { padding: 0; }

/* ---- what each part of the card is ---------------------------
   The picture is lifted out of the flow into a rail down the
   left. In the flow it pushed the headline sideways while the
   note and the facts wrapped back under it, so one card was
   indented on one line and not on the next three. The rail is
   kept even where a row has no picture yet: an even left edge
   down a list of cards is worth more than the space saved. */
.sh:not(.sh--phonetable) .sh-table > tbody > tr:has(> .sh-td[data-phone="thumb"]) {
    padding-left: 4.05rem; min-height: 3.9rem; }
.sh:not(.sh--phonetable) .sh-td[data-phone="thumb"] {
    position: absolute; left: .72rem; top: .66rem; width: 50px; height: 50px;
    display: flex; align-items: center; justify-content: center;
    border: 1px solid var(--line); background: var(--bg); overflow: hidden; }
.sh:not(.sh--phonetable) .sh-td[data-phone="thumb"] .sh-imgbtn,
.sh:not(.sh--phonetable) .sh-td[data-phone="thumb"] .sh-imglink { display: flex; width: 100%; height: 100%;
    align-items: center; justify-content: center; }
/* contain, not cover: a logo cropped to a square is a logo with
   its edges cut off, and half these pictures are logos. */
.sh:not(.sh--phonetable) .sh-td[data-phone="thumb"] .sh-img {
    max-width: 100%; max-height: 100%; width: auto; height: auto; object-fit: contain; }
.sh:not(.sh--phonetable) .sh-td[data-phone="thumb"] .sh-imgadd { width: 100%; height: 100%; border: 0; }

.sh:not(.sh--phonetable) .sh-td[data-phone="head"] {
    order: 10; flex: 1 1 8rem; min-width: 0;
    font-family: var(--font-read, inherit); font-size: calc(.98rem * var(--sh-fs, 1));
    font-weight: 600; line-height: 1.3; color: var(--ink); }
.sh:not(.sh--phonetable) .sh-td[data-phone="badge"] { order: 20; flex: 0 0 auto; margin-left: auto; }
.sh:not(.sh--phonetable) .sh-td[data-phone="tick"]  { order: 30; flex: 0 0 auto; }

.sh:not(.sh--phonetable) .sh-td[data-phone="body"] {
    order: 40; flex: 1 1 100%; min-width: 0; margin-top: .1rem;
    font-family: var(--font-read, inherit); font-size: calc(.83rem * var(--sh-fs, 1));
    line-height: 1.42; color: var(--ink-soft); }

/* The small facts, run together on the last band. */
.sh:not(.sh--phonetable) .sh-td[data-phone="meta"] {
    order: 50; flex: 0 1 auto; min-width: 0; margin-top: .14rem;
    font-size: calc(.73rem * var(--sh-fs, 1)); line-height: 1.35; color: var(--muted);
    /* The row aligns to the top, which is right for the headline
       band. Down here one fact may be a pill twice the height of
       the words beside it, and top-aligning those leaves the
       separators hanging under it. */
    align-self: center; }
/* INLINE, and it matters. A cell's words live in a block box so
   that a long note can be clamped to three lines — but a block
   after the separator drops to a line of its own INSIDE the
   cell, which is a middot on one line and the value under it.
   Nothing on the facts line is ever long enough to clamp. */
.sh:not(.sh--phonetable) .sh-td[data-phone="meta"] .sh-cellin { display: inline; }
/* A pill is a box, and a middot beside a box sits on the text
   baseline the box does not share — so it hangs below it. These
   two are laid out against each other instead. */
.sh:not(.sh--phonetable) .sh-td[data-phone="meta"]:is(.sh-td--tag, .sh-td--yesno, .sh-td--check) {
    display: inline-flex; align-items: center; }

/* ---- a fact that isn't there takes no room ------------------
   In the table an empty cell is a gap in a column and reads as
   "nothing here". On a card it is a stray ＿ or a middot with
   nothing after it, and four of them in a row is the noise this
   layout exists to remove. So a blank fact is struck out — and
   because the test is a live selector rather than something
   stamped on at render, it comes back the moment you type.

   The list is one per cell type: a tag or a checkbox keeps its
   value in data-value, prose in .sh-cellin, a person in the
   name, a link in the address, a yes/no in the class it wears,
   and a client's link cell is simply empty. */
.sh:not(.sh--phonetable) .sh-table > tbody > tr:not(.is-phoneopen) > .sh-td:is([data-phone="meta"], [data-phone="badge"]):is(
    [data-value=""], :empty,
    :has(> .sh-cellin:empty), :has(> .sh-facename:empty),
    :has(> .sh-linkurl:empty), :has(.sh-yn--nil), :has(.sh-pill--empty)
) { display: none; }

/* The separator, which has to survive all of that.
   A middot goes on any fact that HAS A NON-BLANK FACT BEFORE IT
   — the general sibling combinator, not the adjacent one,
   because the columns between two facts (a hidden one, the
   headline, the status) are siblings too and `+` would never
   match. The same blank test is spelled out again on the left of
   the combinator: a middot after something invisible is the
   stranded "· ·" this replaced.

   Scoped to a COLLAPSED row, and that is not tidiness: an open
   card puts the column's name in the same ::before, and this
   selector carries the whole blank test on its left — far too
   specific to override from anywhere else. Left unscoped, every
   open card labelled its yes/no column "·". */
.sh:not(.sh--phonetable) tr:not(.is-phoneopen) > .sh-td[data-phone="meta"]:not(:is(
    [data-value=""], :empty,
    :has(> .sh-cellin:empty), :has(> .sh-facename:empty),
    :has(> .sh-linkurl:empty), :has(.sh-yn--nil), :has(.sh-pill--empty)
)) ~ .sh-td[data-phone="meta"]:not(.sh-td--link)::before {
    content: "·"; margin-right: .45rem; color: var(--line-strong); }

/* A link is as long as somebody's URL, so it gets the whole of
   the last line and an ellipsis rather than pushing every other
   fact off the card. Quiet, too: full-strength accent on a
   forty-character address was the loudest thing on the card and
   the least worth reading. */
.sh:not(.sh--phonetable) .sh-td[data-phone="meta"].sh-td--link { flex: 1 1 100%; }
/* A link starts a line of its own, so it is excluded from the
   separator rule above rather than overriding it here — the
   separator's selector carries the whole blank test on its left,
   which makes it far too specific to out-shout. */
.sh:not(.sh--phonetable) .sh-td[data-phone="meta"].sh-td--link .sh-link,
.sh:not(.sh--phonetable) .sh-td[data-phone="meta"].sh-td--link .sh-linkurl {
    display: inline-block; max-width: 100%; overflow: hidden;
    text-overflow: ellipsis; white-space: nowrap; vertical-align: bottom;
    color: var(--ink-soft); text-decoration: underline;
    text-decoration-color: var(--line-strong); text-underline-offset: 2px; }
.sh:not(.sh--phonetable) .sh-td[data-phone="meta"].sh-td--link .sh-linklabel { display: none; }
.sh:not(.sh--phonetable) .sh-td[data-phone="meta"].sh-td--link.has-label .sh-linklabel {
    display: inline; color: var(--ink-soft); }
.sh:not(.sh--phonetable) .sh-td[data-phone="meta"].sh-td--link.has-label .sh-linkurl { display: none; }
/* A date's "in 3 days" is drawn under it in the table. On one
   line of small facts it belongs beside it. */
.sh:not(.sh--phonetable) .sh-td[data-phone="meta"].sh-td--date .sh-cellin { display: inline; }
.sh:not(.sh--phonetable) .sh-td[data-phone="meta"] .sh-face { width: 15px; height: 15px; font-size: .56rem; }
/* A name field holds itself open to 48px so an empty one is still
   something you can hit. On a line of small facts that is a gap
   after every name; the floor comes back when the card opens,
   which is where you type into it. */
.sh:not(.sh--phonetable) tr:not(.is-phoneopen) .sh-td[data-phone="meta"] .sh-facename { min-width: 0; }

/* Not on the face of the card. Still in the row, and still there
   the moment the card is opened.
   Written out the long way ON PURPOSE. An attribute counts the
   same as a class, so the short form lost to the `display: block`
   that puts every cell into card layout — and a column meant to
   be off the card was drawn at the front of it, before the
   headline, because `off` never sets an `order`. */
.sh:not(.sh--phonetable) .sh-table > tbody > tr > .sh-td[data-phone="off"] { display: none; }

/* The machinery columns. Dragging a row and resizing a column
   are mouse gestures; the row tools come back when the card is
   opened, where there is room to label them. */
.sh:not(.sh--phonetable) .sh-table > tbody > tr > .sh-td--grip,
.sh:not(.sh--phonetable) .sh-table > tbody > tr > .sh-td--n,
.sh:not(.sh--phonetable) .sh-table > tbody > tr > .sh-td--tools { display: none; }

/* Except the client's own — a card they cannot write on is the
   card that misses the point of sending it. */
.sh:not(.sh--phonetable) .sh-table > tbody > tr > .sh-td--say {
    display: block; order: 70; flex: 1 1 100%; margin-top: .34rem; text-align: left; }
/* And your side of the same conversation.
   ORDER 46, which puts it above the facts rather than below
   them: if somebody has written on a row, that is the news, and
   a shot code is not. It sat at 6 for a while — under the old
   numbering that was after the note, and when the bands were
   renumbered by tens it silently became FIRST, drawing the
   client's note above the headline. Any new band goes between
   the tens, never below them. */
.sh:not(.sh--phonetable) .sh-table > tbody > tr > .sh-td--saycol {
    display: block; order: 46; flex: 1 1 100%; margin-top: .4rem;
    border-top: 1px solid var(--line); padding-top: .1rem; }
.sh:not(.sh--phonetable) .sh-saycell { padding: .34rem 0; }
.sh:not(.sh--phonetable) .sh-saynew { padding: .34rem 0; }
/* A row nobody has said anything about gets nothing. In the table
   the empty cell is a quiet way in that costs no room, because
   the column is there either way; on a card it is a rule and a
   ＋ on the end of every card in the sheet, which is the column
   asking for as much space when there is nothing in it as when
   there is. It comes back when the card is opened. */
.sh:not(.sh--phonetable) .sh-table > tbody > tr > .sh-td--saycol.is-empty { display: none; }
.sh:not(.sh--phonetable) .sh-table > tbody > tr.is-phoneopen > .sh-td--saycol.is-empty { display: block; }

/* ---- read first ---------------------------------------------
   A collapsed card is for reading. Nothing in it takes a caret,
   so a tap anywhere on it opens the card rather than dropping
   the keyboard into whichever word you happened to hit. The
   controls that stay live are the ones that are one tap in the
   first place — the status, the tick, the notes button — because
   opening a card underneath them would be two things happening
   on one press. */
.sh:not(.sh--phonetable) tr:not(.is-phoneopen) .sh-cellin,
.sh:not(.sh--phonetable) tr:not(.is-phoneopen) .sh-facename,
.sh:not(.sh--phonetable) tr:not(.is-phoneopen) .sh-linkurl,
.sh:not(.sh--phonetable) tr:not(.is-phoneopen) .sh-linklabel,
.sh:not(.sh--phonetable) tr:not(.is-phoneopen) .sh-link { pointer-events: none; }
/* The "there is more of this" chevron goes altogether. Tapping
   the card is already how you see the rest, and an affordance
   sitting next to it that does nothing when pressed is worse
   than no affordance. */
.sh:not(.sh--phonetable) tr:not(.is-phoneopen) [data-more] { display: none; }
.sh:not(.sh--phonetable) tr:not(.is-phoneopen) .sh-pillbtn,
.sh:not(.sh--phonetable) tr:not(.is-phoneopen) .sh-checkbtn,
.sh:not(.sh--phonetable) tr:not(.is-phoneopen) .sh-ynbtn,
.sh:not(.sh--phonetable) tr:not(.is-phoneopen) .sh-saybtn,
.sh:not(.sh--phonetable) tr:not(.is-phoneopen) .sh-saycell,
.sh:not(.sh--phonetable) tr:not(.is-phoneopen) .sh-saynew,
/* The picture is a control now: tapping it opens the picture,
   not the card. That is what anybody tapping a fifty-pixel
   thumbnail was reaching for. */
.sh:not(.sh--phonetable) tr:not(.is-phoneopen) .sh-imgbtn,
.sh:not(.sh--phonetable) tr:not(.is-phoneopen) .sh-imglink { pointer-events: auto; }

/* ---- the card, opened ---------------------------------------
   Every column, labelled, one to a line — the ones the card
   leaves off included. It opens IN PLACE rather than over the
   page: you keep your position in a four-hundred-row sheet, and
   the fields in it are the REAL cells, which is the whole reason
   typing in them saves. A drawer would have had to be a copy. */
.sh:not(.sh--phonetable) .sh-table > tbody > tr.is-phoneopen {
    border-color: var(--accent); box-shadow: 0 6px 20px rgba(20, 24, 30, .12);
    padding: .5rem .72rem .55rem; gap: 0; }
/* FLEX, not grid, and it has to stay that way. A grid auto-places
   every child of the cell into its own track, and two of the cell
   types have two children: a person is a face and a name, a link
   is an address and a label. On a grid they came out one under
   the other with the label column empty beside the second. Flex
   lays the label and whatever the cell holds along one line and
   leaves the cell's own children alone. */
.sh:not(.sh--phonetable) .sh-table > tbody > tr.is-phoneopen > .sh-td {
    display: flex; align-items: baseline; gap: .55rem; order: 0; flex: 1 1 100%;
    padding: .42rem 0; margin: 0; border-top: 1px solid var(--line);
    font-size: calc(.86rem * var(--sh-fs, 1)); font-weight: 400; color: var(--ink);
    line-height: 1.42; text-align: left; }
.sh:not(.sh--phonetable) tr.is-phoneopen > .sh-td > * { min-width: 0; }
.sh:not(.sh--phonetable) tr.is-phoneopen > .sh-td > .sh-cellin { flex: 1 1 auto; }
/* No rule above the first line. In the editor the first cell is
   the grip, which is hidden here — so the cell after it is the
   one that must not draw one. */
.sh:not(.sh--phonetable) .sh-table > tbody > tr.is-phoneopen > .sh-td:first-child,
.sh:not(.sh--phonetable) .sh-table > tbody > tr.is-phoneopen > .sh-td--grip + .sh-td { border-top: 0; }
.sh:not(.sh--phonetable) .sh-table > tbody > tr.is-phoneopen > .sh-td::before {
    content: attr(data-phone-label); flex: 0 0 6.2rem;
    font-family: var(--mono); font-size: .56rem;
    text-transform: uppercase; letter-spacing: .1em; color: var(--muted);
    overflow-wrap: anywhere; }

/* The row's own number, which is the name you say out loud to
   point at a row — and the one cell in the open card that has no
   column to take a label from. */
.sh:not(.sh--phonetable) .sh-table > tbody > tr.is-phoneopen > .sh-td--n {
    display: flex; font-size: .8rem; }
.sh:not(.sh--phonetable) .sh-table > tbody > tr.is-phoneopen > .sh-td--n::before { content: "Row no."; }
/* The grip stays gone even here: dragging a row into place is not
   a gesture this screen has, so a handle for it is a control that
   does nothing. */
.sh:not(.sh--phonetable) .sh-table > tbody > tr.is-phoneopen > .sh-td--grip { display: none; }

/* The picture comes back into the flow. On the face of the card
   it is lifted out into a rail down the left; open, it is a
   field like the others and it gets its column's name — left
   absolute it would have hung over the top of the open card
   with an empty line where it used to be. Bigger, too: there is
   room here, and it is still one tap from the full size. */
.sh:not(.sh--phonetable) .sh-table > tbody > tr.is-phoneopen > .sh-td[data-phone="thumb"] {
    position: static; width: auto; height: auto; border: 0; background: none;
    /* Every other field lines its label up with the value's own
       baseline. Against a picture a hundred pixels tall that puts
       the word at the bottom of it, so this one row aligns to the
       top instead. */
    align-items: flex-start; justify-content: flex-start; }
.sh:not(.sh--phonetable) tr.is-phoneopen > .sh-td[data-phone="thumb"] .sh-imgbtn,
.sh:not(.sh--phonetable) tr.is-phoneopen > .sh-td[data-phone="thumb"] .sh-imglink {
    display: inline-flex; width: auto; height: auto; }
.sh:not(.sh--phonetable) tr.is-phoneopen > .sh-td[data-phone="thumb"] .sh-img {
    max-height: 120px; max-width: 100%; }
.sh:not(.sh--phonetable) tr.is-phoneopen > .sh-td[data-phone="thumb"] .sh-imgadd {
    width: 44px; height: 34px; border: 1px dashed var(--line-strong); }
/* And the rail's gutter goes with it, or the open card is inset
   by four rems for a picture that is no longer sitting there. */
.sh:not(.sh--phonetable) .sh-table > tbody > tr.is-phoneopen:has(> .sh-td[data-phone="thumb"]) {
    padding-left: .72rem; min-height: 0; }
/* A cell being typed into is not clamped, and the open card is
   where you type. */
.sh:not(.sh--phonetable) tr.is-phoneopen .sh-cellin {
    -webkit-line-clamp: unset; display: block; min-height: 1.4em; }
.sh:not(.sh--phonetable) tr.is-phoneopen .sh-td[data-phone="meta"]::before { margin-right: 0; }

/* The row's own controls, back where there is room to say what
   they are. The grip stays gone: reordering by drag is a gesture
   this screen does not have. */
.sh:not(.sh--phonetable) .sh-table > tbody > tr.is-phoneopen > .sh-td--tools {
    display: flex; align-items: center; text-align: left; }
.sh:not(.sh--phonetable) .sh-table > tbody > tr.is-phoneopen > .sh-td--tools::before { content: "Row"; }
.sh:not(.sh--phonetable) tr.is-phoneopen .sh-rowtools { display: flex; gap: .3rem; flex: 1 1 auto; }
.sh:not(.sh--phonetable) tr.is-phoneopen .sh-rowbtn {
    opacity: 1; min-width: 32px; min-height: 32px; font-size: .95rem; }
/* These two carry a whole panel rather than a value, so they get
   the full width instead of a label and a column. */
.sh:not(.sh--phonetable) .sh-table > tbody > tr.is-phoneopen > .sh-td--saycol,
.sh:not(.sh--phonetable) .sh-table > tbody > tr.is-phoneopen > .sh-td--say { display: block; }
.sh:not(.sh--phonetable) tr.is-phoneopen > .sh-td--saycol::before,
.sh:not(.sh--phonetable) tr.is-phoneopen > .sh-td--say::before { content: none; }

/* The way out, added to the open row by sheet.js and taken away
   with it — one element at a time rather than a button per row
   on a sheet that may have four hundred of them. */
.sh-td--phoneclose { display: block; padding: .45rem 0 0; border-top: 1px solid var(--line); }
.sh-phoneclose {
    width: 100%; min-height: 34px; cursor: pointer; font: inherit;
    font-family: var(--font-read, inherit); font-size: .8rem; color: var(--ink-soft);
    background: none; border: 1px solid var(--line); }
.sh-phoneclose:hover, .sh-phoneclose:focus-visible { border-color: var(--accent); color: var(--accent); }

/* Neither gesture exists on this screen. */
.sh:not(.sh--phonetable) .sh-table .sh-resize,
.sh:not(.sh--phonetable) .sh-table .sh-colgrip { display: none; }

/* ---- what a type looks like when it is not a column ---------
   Three of the type treatments are drawn against the COLUMN and
   have nothing to sit in here. A timecode is right-aligned on a
   faint wash the width of its column — which is what says
   "machine-readable" from across a table, and on a card is a
   grey box behind the headline, pushed to the right margin for
   no reason. Alignment and wash go; the mono figures, which are
   the actual point, stay. */
.sh:not(.sh--phonetable) .sh-td[data-phone] {
    background: none; background-image: none; text-align: left; }
.sh:not(.sh--phonetable) .sh-td[data-phone="head"].sh-td--timecode,
.sh:not(.sh--phonetable) .sh-td[data-phone="head"].sh-td--duration,
.sh:not(.sh--phonetable) .sh-td[data-phone="head"].sh-td--number {
    font-family: var(--mono); letter-spacing: -.01em; }

/* And the row highlight, which washes every cell in the row. A
   card is already a box: filling it reads as selected rather
   than as marked, so the colour goes down the edge instead. */
.sh:not(.sh--phonetable) .sh-table > tbody > tr.sh-row--marked > .sh-td { background: none; }
.sh:not(.sh--phonetable) .sh-table > tbody > tr.sh-row--marked > .sh-td:first-child { box-shadow: none; }

/* A date's "in 3 days" is a second line under the date in the
   table, where the column is only as wide as the date. On one
   line of small facts there is room to say it in place. */
.sh:not(.sh--phonetable) .sh-td[data-phone="meta"].sh-td--date .sh-cellin[data-rel]::after {
    content: " (" attr(data-rel) ")"; white-space: normal; }

}

/* ============================================================
   THE PICTURE, LOOKED AT

   One overlay per sheet, filled in from whichever cell was
   clicked. Every size, both surfaces: a frame you are being
   asked to approve is worth more than fifty pixels of it, and
   that was true on a laptop before it was true on a phone.

   ‹ › walk the sheet's other pictures without closing, which is
   what turns a column of frames into something you can review.
   ============================================================ */
.sh-lb {
    position: fixed; inset: 0; z-index: 90;
    display: flex; align-items: center; justify-content: center;
    padding: clamp(.75rem, 4vw, 3rem);
}
.sh-lb[hidden] { display: none; }

/* The way out that is the whole background. A button and not a
   div, so clicking away from a thing to close it works from a
   keyboard too. */
.sh-lb-scrim {
    position: absolute; inset: 0; border: 0; padding: 0; cursor: zoom-out;
    background: rgba(16, 19, 24, .82); backdrop-filter: blur(2px);
}

.sh-lb-fig {
    position: relative; margin: 0; z-index: 1;
    display: flex; flex-direction: column; align-items: center; gap: .7rem;
    max-width: 100%; max-height: 100%;
}
/* The picture is never scaled UP. A 300px thumbnail blown across
   a 27-inch screen is a worse look at it than the thumbnail. */
.sh-lb-img {
    display: block; max-width: 100%; max-height: calc(100vh - 9rem);
    width: auto; height: auto; object-fit: contain;
    background: #fff; box-shadow: 0 24px 60px rgba(0, 0, 0, .5);
}
.sh-lb-cap {
    display: flex; align-items: baseline; gap: .6rem; max-width: 100%;
    font-family: var(--font-read, inherit); font-size: .84rem; color: rgba(255, 255, 255, .9);
}
.sh-lb-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sh-lb-n {
    font-family: var(--mono); font-size: .66rem; letter-spacing: .08em;
    text-transform: uppercase; color: rgba(255, 255, 255, .5); white-space: nowrap;
}
.sh-lb-cap:empty { display: none; }

.sh-lb-step {
    position: relative; z-index: 1; flex: 0 0 auto; cursor: pointer;
    width: 44px; height: 64px; border: 0; padding: 0;
    font: inherit; font-size: 1.9rem; line-height: 1;
    background: rgba(255, 255, 255, .07); color: rgba(255, 255, 255, .75);
    transition: background .12s, color .12s;
}
.sh-lb-step:hover, .sh-lb-step:focus-visible {
    background: rgba(255, 255, 255, .16); color: #fff; }
.sh-lb-step[hidden] { display: none; }
.sh-lb-step--prev { margin-right: .5rem; }
.sh-lb-step--next { margin-left: .5rem; }

.sh-lb-acts {
    position: absolute; z-index: 2; right: 0; bottom: 0; left: 0;
    display: flex; align-items: center; justify-content: center; gap: .4rem;
    padding: .7rem; flex-wrap: wrap;
}
.sh-lb-act {
    cursor: pointer; font: inherit; font-family: var(--font-read, inherit);
    font-size: .8rem; text-decoration: none; min-height: 34px;
    display: inline-flex; align-items: center; padding: .35rem .8rem;
    border: 1px solid rgba(255, 255, 255, .22); background: rgba(255, 255, 255, .06);
    color: rgba(255, 255, 255, .88);
}
.sh-lb-act:hover, .sh-lb-act:focus-visible {
    border-color: rgba(255, 255, 255, .5); background: rgba(255, 255, 255, .14); color: #fff; }
.sh-lb-act--del:hover { border-color: var(--danger); color: #fff; background: var(--danger); }
.sh-lb-act--go { border-color: rgba(255, 255, 255, .5); }

/* The page behind must not scroll under the overlay — on a phone
   that is the difference between a picture you are looking at
   and a picture that slides away while you pinch it. */
body.sh-lb-on { overflow: hidden; }

/* On a phone the actions take the bottom edge as a bar, and the
   step buttons shrink out of the picture's way. */
@media (max-width: 620px) {
    .sh-lb { padding: .5rem .5rem 3.6rem; }
    .sh-lb-img { max-height: calc(100vh - 11rem); }
    .sh-lb-step { width: 34px; height: 52px; font-size: 1.5rem; }
    .sh-lb-step--prev { margin-right: .25rem; }
    .sh-lb-step--next { margin-left: .25rem; }
    .sh-lb-acts { gap: .3rem; padding: .5rem; }
    .sh-lb-act { font-size: .76rem; padding: .3rem .6rem; }
}

/* A picture you can open says so. */
.sh-imglink, .sh-imgbtn { cursor: zoom-in; }
.sh-imgbtn:has(.sh-imgadd) { cursor: pointer; }

/* ============================================================
   PROMISES  —  what somebody said they would do
   ------------------------------------------------------------
   Two shapes: the rows on /promises, and the row the quick-search palette shows
   while you are still typing the sentence.

   The colour rule is the one thing worth stating, and the two surfaces answer it
   differently on purpose. On the DASHBOARD a promise is only ever shown once it
   has come due, so it is genuinely your move and takes the ember every other
   your-move row takes. On the PAGE most rows are not due for days, so ember there
   would put a Friday promise at the same volume as an overdue invoice. Only the
   late bucket gets colour; the rest is deliberately quiet, because the page is a
   list you consult rather than a thing shouting at you.
   ============================================================ */

/* Both sides are your move by the time a promise reaches the dashboard — that is
   the whole reason it only appears on the day. So both take ember, and the tag
   text carries the difference between chasing and doing. */
.feed-row--promise { --bar: var(--accent); }
.feed-tag--owe-them, .feed-tag--owe-you {
    color: var(--accent); background: color-mix(in srgb, var(--accent) 12%, transparent);
    border-color: color-mix(in srgb, var(--accent) 34%, transparent);
}
/* Except the one you owe, which is plum — the colour this app already uses for
   what goes OUT. A glance down the band should separate "chase somebody" from
   "do something" without reading a word. */
.feed-tag--owe-you {
    color: var(--c-out); background: color-mix(in srgb, var(--c-out) 12%, transparent);
    border-color: color-mix(in srgb, var(--c-out) 34%, transparent);
}

/* ---- The box ---------------------------------------------------------- */
/* One field, deliberately. The whole feature dies at the second one: the moment
   you would use it is twenty seconds after a text message, standing up. */
.prom-add { padding: 1rem 1.1rem; }
.prom-add-form { display: flex; gap: .5rem; align-items: stretch; }
.prom-add-in {
    flex: 1 1 auto; min-width: 0;
    font-size: 1rem; padding: .6rem .75rem;
    border: 1px solid var(--line-strong); background: var(--panel); color: var(--ink);
}
.prom-add-in:focus { outline: none; border-color: var(--accent);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 14%, transparent); }
.prom-add-hint { margin: .55rem 0 0; line-height: 1.5; max-width: 62ch; }
.prom-add-hint kbd {
    font-family: var(--mono); font-size: .78em; padding: .05em .35em;
    border: 1px solid var(--line-strong); background: var(--wash);
}

/* ---- Which side ------------------------------------------------------- */
.prom-chips { display: flex; gap: .4rem; margin: 1rem 0 .75rem; flex-wrap: wrap; }
.prom-chip {
    display: inline-flex; align-items: center; gap: .4rem;
    padding: .35rem .7rem; font-size: .82rem; text-decoration: none;
    color: var(--ink-soft); background: var(--panel);
    border: 1px solid var(--line);
}
.prom-chip:hover { border-color: var(--line-strong); color: var(--ink); }
.prom-chip.on { color: var(--ink); background: var(--wash); border-color: var(--line-strong); }
.prom-chip-n { font-family: var(--mono); font-size: .74rem; color: var(--muted); }

/* ---- The rows --------------------------------------------------------- */
.prom-list { list-style: none; margin: 0; padding: 0; }
.prom-row {
    display: grid;
    grid-template-columns: 1fr auto;
    grid-template-areas: "main acts" "edit edit";
    gap: .4rem .9rem; align-items: start;
    padding: .7rem 0; border-top: 1px solid var(--line);
}
.prom-list > .prom-row:first-child { border-top: 0; }
.prom-main { grid-area: main; min-width: 0; display: flex; flex-direction: column; gap: .25rem; }
.prom-acts { grid-area: acts; display: flex; gap: .35rem; align-items: center; flex-wrap: wrap; }
.prom-edit { grid-area: edit; }

/* Mono, because it's a date, and dates in this app are read down a column. */
.prom-due { font-family: var(--mono); font-size: .74rem; letter-spacing: .02em;
    text-transform: uppercase; color: var(--muted); }
.prom-due.is-guessed { font-style: italic; text-transform: none; letter-spacing: 0; }
.prom-body { font-size: .95rem; color: var(--ink); line-height: 1.45; }
.prom-tags { display: flex; gap: .45rem; align-items: center; flex-wrap: wrap; font-size: .78rem; }

.prom-side { padding: .1rem .45rem; border: 1px solid var(--line);
    background: var(--wash); color: var(--ink-soft); white-space: nowrap; }
/* Yours reads as work, theirs as a chase — the only visual difference the two
   sides get, because everything else about them is identical. */
.prom-side--you { color: var(--c-out); border-color: color-mix(in srgb, var(--c-out) 32%, transparent);
    background: color-mix(in srgb, var(--c-out) 10%, transparent); }
.prom-side--kept { color: var(--ok); border-color: color-mix(in srgb, var(--ok) 32%, transparent);
    background: color-mix(in srgb, var(--ok) 10%, transparent); }
.prom-side--dropped { color: var(--muted); }
.prom-who { color: var(--ink-soft); text-decoration: none; border-bottom: 1px solid var(--line-strong); }
.prom-who:hover { color: var(--accent); border-color: var(--accent); }
.prom-who--none { color: var(--muted); border: 0; font-style: italic; }

/* Late is the only bucket that is news, so it is the only one that gets colour. */
.prom-band--late .panel-head h2 { color: var(--danger); }
.prom-band--late .prom-due { color: var(--danger); }

.prom-act { white-space: nowrap; }
.prom-act--kept:hover { border-color: var(--ok); color: var(--ok); }
.prom-act--drop:hover { border-color: var(--line-strong); color: var(--muted); }
.prom-row--settled .prom-body { color: var(--ink-soft); }

.prom-edit > summary { font-size: .76rem; color: var(--muted); cursor: pointer; list-style: none;
    padding: .15rem 0; }
.prom-edit > summary::-webkit-details-marker { display: none; }
.prom-edit > summary:hover { color: var(--ink-soft); }
.prom-edit[open] > summary { color: var(--ink-soft); }
.prom-edit-form { padding: .5rem 0 .25rem; }
.prom-del { font-size: .76rem; color: var(--muted); }
.prom-del:hover { color: var(--danger); }
.prom-settled > summary { cursor: pointer; font-weight: 500; }

/* ---- The palette row -------------------------------------------------- */
/* Same skeleton as the calculator row above it: a head line and a strip of chips
   underneath saying what it worked out. The chips ARE the safety rail — this is
   the one moment you can see it matched the wrong Marcus and stop. */
.cmdk-promise { align-items: flex-start; }
.cmdk-prom-body { display: flex; flex-direction: column; gap: .3rem; min-width: 0; flex: 1 1 auto; }
.cmdk-promise .cmdk-item-label { white-space: normal; line-height: 1.4; }
.cmdk-prom-read { display: flex; gap: .35rem; flex-wrap: wrap; }
.cmdk-prom-chip {
    font-size: .72rem; padding: .08rem .4rem;
    border: 1px solid var(--line); background: var(--wash); color: var(--ink-soft);
    white-space: nowrap;
}
.cmdk-prom-chip--none { color: var(--muted); font-style: italic; }
.cmdk-filed {
    padding: .6rem .85rem; font-size: .84rem; color: var(--ok);
    border-left: 2px solid var(--ok); background: color-mix(in srgb, var(--ok) 7%, transparent);
}

/* ---- Phone ------------------------------------------------------------ */
@media (max-width: 620px) {
    /* The buttons go under the sentence rather than beside it: three of them in a
       right-hand column squeeze the text into a two-word ribbon. */
    .prom-row { grid-template-columns: 1fr; grid-template-areas: "main" "acts" "edit"; }
    .prom-acts { gap: .3rem; }
    .prom-acts .btn { flex: 1 1 auto; }
    .prom-add-form { flex-direction: column; }
    .prom-add-hint { font-size: .78rem; }
}

/* ============================================================
   PROMISES, WHERE THEY SHOW UP
   ------------------------------------------------------------
   Promises stopped being a section of their own. They now appear inside the
   things they are about — the client's "Coming up" strip, the Pipeline's two
   groups, and both calendars — so everything below is a small addition to
   somebody else's component rather than a component of its own. That is
   deliberate: the day a promise needs its own visual language on four screens is
   the day it should have gone back on the nav.
   ============================================================ */

/* ---- In the client's "Coming up" strip --------------------------------- */
/* The box. Sits above the list because you write more often than you read here,
   and a field at the bottom of a growing list moves further away every week. */
.tl-add { display: flex; gap: .5rem; padding: .1rem 0 .7rem; border-bottom: 1px solid var(--line);
    margin-bottom: .2rem; }
.tl-add-in { flex: 1 1 auto; min-width: 0; font-size: .88rem; padding: .45rem .6rem;
    border: 1px solid var(--line-strong); background: var(--panel); color: var(--ink); }
.tl-add-in:focus { outline: none; border-color: var(--accent);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 14%, transparent); }
.tl-empty { margin: .7rem 0 .2rem; line-height: 1.5; }
.tl-empty kbd, .cal-meet-empty kbd { font-family: var(--mono); font-size: .9em;
    padding: .02em .3em; border: 1px solid var(--line-strong); background: var(--wash); }
.tl-foot { margin: .7rem 0 0; }

/* Where the row came from. The quietest thing on the line by design: it matters
   only when you are wondering why one row has a ✉ and the next one doesn't, and
   it must never compete with the sentence it is labelling. */
.tl-src { font-size: .6rem; text-transform: uppercase; letter-spacing: .08em;
    margin-left: .4rem; padding: .05em .3em; border: 1px solid var(--line);
    color: var(--muted); vertical-align: .1em; }
.tl-src--typed { border-color: color-mix(in srgb, var(--accent) 30%, transparent);
    color: color-mix(in srgb, var(--accent) 80%, var(--muted)); }
/* A date the app chose rather than one anybody named. Italic and never coloured:
   it must not borrow the urgency the is-soon rows get. */
.tl-in.is-guessed { font-style: italic; color: var(--muted); }
/* Late is the one state the strip did not have before, and promises can be in it
   — a noticed date drops off when it passes, a promise does not. */
.tl-row.is-late .tl-when b, .tl-row.is-late .tl-in { color: var(--danger); }
.tl-quote--who { font-style: normal; }

/* ---- In the Pipeline --------------------------------------------------- */
/* Quieter than a job row: a job has money and a tracker behind it, a promise is
   a sentence somebody said. Same left-to-right reading, no dot strip and no
   stage pill -- a promise has no stages, and five empty dots would invent a
   process that does not exist. */
.ip-row--prom { padding-top: .5rem; padding-bottom: .5rem; }
.ip-row--prom .ip-who b { font-weight: 500; font-size: .86rem; color: var(--ink-soft); }
.ip-row--prom .ip-line { font-size: .82rem; color: var(--ink); margin-top: .15rem; }
.ip-prom-tag { font-size: .62rem; text-transform: uppercase; letter-spacing: .08em;
    color: var(--muted); margin-left: .45rem; font-weight: 400; }
.ip-prom-when { font-family: var(--mono); font-size: .7rem; letter-spacing: .04em;
    text-transform: uppercase; color: var(--muted); white-space: nowrap; flex: none; }
.ip-prom-when.is-late { color: var(--danger); }
.ip-prom-when.is-guessed { text-transform: none; letter-spacing: 0; font-style: italic; }

/* ---- On the calendars -------------------------------------------------- */
/* A mark, never a shaded cell. Every colour on a calendar grid in this app means
   "this day is sold", and a promise is not work booked -- so it gets its own
   small shape in the corner, the way a meeting gets a dot.
   Hollow = the app chose the date. A guessed deadline drawn solid would be the
   app quietly inventing one, which is the thing this feature must not do. */
.cal-proms { position: absolute; right: .3rem; bottom: .28rem; display: flex; gap: 2px; }
.cal-prom { width: 5px; height: 5px; display: block; transform: rotate(45deg); }
.cal-prom--them { background: var(--accent); }
.cal-prom--you  { background: var(--c-out); }
.cal-prom.is-guessed { background: transparent; }
.cal-prom--them.is-guessed { box-shadow: inset 0 0 0 1px var(--accent); }
.cal-prom--you.is-guessed  { box-shadow: inset 0 0 0 1px var(--c-out); }
.cal-cell.has-prom { cursor: help; }

/* The dashboard's mini month. Same idea at half the size, and paired with the
   pay-day dot it already carries -- so the two live in opposite corners. */
.cal-prom-dot { position: absolute; bottom: 2px; right: 3px; width: 4px; height: 4px;
    display: block; transform: rotate(45deg); background: var(--accent); }
.cal-prom-dot.cal-prom--you { background: var(--c-out); }
.cal-prom-dot.is-guessed { background: transparent; box-shadow: inset 0 0 0 1px var(--accent); }
.sheet-cal-legend { flex-wrap: wrap; gap: .55rem; }
.sheet-cal-legend span { display: inline-flex; align-items: center; gap: .3rem; }
.sheet-cal-legend .cal-prom-dot { position: static; }
/* The mini legend is a flex row with its own gap, so the swatch drops the
   month legend's trailing margin and comes down a size with the type. */
.sheet-cal-legend .sw { width: 9px; height: 9px; margin-right: 0; }

/* ---- The Calendar page's Promised rail --------------------------------- */
/* Borrows the meetings rail's chrome on purpose: they are two answers to the
   same question -- what is coming -- and giving the second one its own look
   would make the rail read as two unrelated boxes stacked up. */
.cal-prom-rail { margin: 0 0 1.4rem; border: 1px solid var(--line); background: var(--panel); }
.cal-prom-row { display: grid; grid-template-columns: 5.4rem minmax(0, 1fr) auto;
    gap: .2rem .6rem; align-items: baseline; padding: .5rem .85rem;
    border-top: 1px solid var(--line); }
.cal-prom-when { font-family: var(--mono); font-size: .68rem; letter-spacing: .04em;
    text-transform: uppercase; color: var(--muted); }
.cal-prom-when.is-guessed { text-transform: none; letter-spacing: 0; font-style: italic; }
.cal-prom-row.is-late .cal-prom-when { color: var(--danger); }
.cal-prom-body { min-width: 0; }
.cal-prom-side { font-weight: 600; font-size: .78rem; margin-right: .3rem; }
.cal-prom-side--them { color: var(--accent); }
.cal-prom-side--you  { color: var(--c-out); }
.cal-prom-what { font-size: .82rem; color: var(--ink); }
.cal-prom-guess { display: block; font-size: .68rem; color: var(--muted); font-style: italic; }
.cal-prom-tick { background: none; border: 0; padding: .1rem .25rem; cursor: pointer;
    font: inherit; font-size: .82rem; line-height: 1; color: var(--muted); }
.cal-prom-tick:hover { color: var(--ok); background: var(--wash); }
.cal-prom-foot { margin: 0; padding: .6rem .85rem .8rem; font-size: .74rem; }

@media (max-width: 620px) {
    .tl-add { flex-direction: column; }
    /* .ba-kind is hidden here, so auto-placement would shuffle the remaining
       cells up a column and drop the padlock under the amount. Place every
       cell explicitly: one header line, the slider on its own beneath. */
    .ba-row { grid-template-columns: minmax(0, 1fr) 3.4rem 4.6rem 2rem; gap: .35rem .5rem; }
    .ba-name { grid-area: 1 / 1; }
    .ba-pct  { grid-area: 1 / 2; }
    .ba-amt  { grid-area: 1 / 3; }
    .ba-lock { grid-area: 1 / 4; min-width: 2rem; min-height: 2rem; display: flex;
               align-items: center; justify-content: center; }
    .ba-row input[type=range] { grid-area: 2 / 1 / 2 / -1; }
    .ba-kind { display: none; }
    .ba-master label { font-size: .74rem; }
    .bud-weekly { gap: .5rem; padding: .7rem .8rem; }
    .bw-figure { font-size: 1.3rem; }
    .bud-wk { width: auto; margin-left: auto; }
    .sp-env-nums { flex: 1 0 100%; }
    .sp-env-left { width: 4.6rem; }
    .sp-left-val { font-size: 1.9rem; }
    /* The carry moves under the sentence it qualifies rather than trailing off
       the end of it. */
    .sp-carry { display: block; margin: .2rem 0 0; }
    .cal-prom-row { grid-template-columns: minmax(0, 1fr) auto; }
    .cal-prom-body { grid-column: 1 / -1; }
}
