/*
 * app.css — Continuum local UI.
 *
 * The Product Scope brand, applied (design pass, 2026-07). Distilled from the
 * design tool's high-fidelity prototype and the brand style guide: Poppins
 * headings each with an oxblood divider, DM Sans body, near-black slate on
 * white, burgundy (#800020) as a SPARING accent, slate structural chrome, and
 * contained pastel panels for grouping. Fonts are self-hosted (no third-party
 * calls, per the brand's font-src 'self'); the woff2 files are vendored in
 * ./fonts. This is a deliberately light, single-theme surface — the brand is a
 * light world and so is the tool.
 *
 * No bucket logic lives in the UI: the board groups by the `bucket` field the
 * command layer already derived (spec 3.4). Colour is presentation only.
 */

/* ------------------------------------------------------------------ fonts */
@font-face { font-family:'DM Sans'; font-style:normal; font-weight:400; font-display:swap; src:url('/static/fonts/dm-sans-400-latin-ext.woff2') format('woff2'); unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF; }
@font-face { font-family:'DM Sans'; font-style:normal; font-weight:400; font-display:swap; src:url('/static/fonts/dm-sans-400-latin.woff2') format('woff2'); unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD; }
@font-face { font-family:'DM Sans'; font-style:normal; font-weight:500; font-display:swap; src:url('/static/fonts/dm-sans-500-latin-ext.woff2') format('woff2'); unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF; }
@font-face { font-family:'DM Sans'; font-style:normal; font-weight:500; font-display:swap; src:url('/static/fonts/dm-sans-500-latin.woff2') format('woff2'); unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD; }
@font-face { font-family:'DM Sans'; font-style:normal; font-weight:600; font-display:swap; src:url('/static/fonts/dm-sans-600-latin-ext.woff2') format('woff2'); unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF; }
@font-face { font-family:'DM Sans'; font-style:normal; font-weight:600; font-display:swap; src:url('/static/fonts/dm-sans-600-latin.woff2') format('woff2'); unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD; }
@font-face { font-family:'DM Sans'; font-style:normal; font-weight:700; font-display:swap; src:url('/static/fonts/dm-sans-700-latin-ext.woff2') format('woff2'); unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF; }
@font-face { font-family:'DM Sans'; font-style:normal; font-weight:700; font-display:swap; src:url('/static/fonts/dm-sans-700-latin.woff2') format('woff2'); unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD; }
@font-face { font-family:'Poppins'; font-style:normal; font-weight:400; font-display:swap; src:url('/static/fonts/poppins-400-latin-ext.woff2') format('woff2'); unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF; }
@font-face { font-family:'Poppins'; font-style:normal; font-weight:400; font-display:swap; src:url('/static/fonts/poppins-400-latin.woff2') format('woff2'); unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD; }
@font-face { font-family:'Poppins'; font-style:normal; font-weight:500; font-display:swap; src:url('/static/fonts/poppins-500-latin-ext.woff2') format('woff2'); unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF; }
@font-face { font-family:'Poppins'; font-style:normal; font-weight:500; font-display:swap; src:url('/static/fonts/poppins-500-latin.woff2') format('woff2'); unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD; }
@font-face { font-family:'Poppins'; font-style:normal; font-weight:600; font-display:swap; src:url('/static/fonts/poppins-600-latin-ext.woff2') format('woff2'); unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF; }
@font-face { font-family:'Poppins'; font-style:normal; font-weight:600; font-display:swap; src:url('/static/fonts/poppins-600-latin.woff2') format('woff2'); unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD; }
@font-face { font-family:'Poppins'; font-style:normal; font-weight:700; font-display:swap; src:url('/static/fonts/poppins-700-latin-ext.woff2') format('woff2'); unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF; }
@font-face { font-family:'Poppins'; font-style:normal; font-weight:700; font-display:swap; src:url('/static/fonts/poppins-700-latin.woff2') format('woff2'); unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD; }

/* ------------------------------------------------------------------ tokens */
/*
 * EVERY colour in this file is a custom property defined here, so dark mode
 * (below) is a token swap and nothing else. If you find yourself typing a hex
 * value into a component rule, it belongs up here instead — that is the whole
 * bargain that keeps two themes tractable in one stylesheet.
 *
 * Two pairings are worth stating because they are easy to get backwards:
 *  - A SATURATED FILL keeps its white label. --brand as a background ("New
 *    card", the overdue chip, the sidebar count badge) stays white-on-burgundy
 *    in both themes.
 *  - BURGUNDY TEXT uses --brand-ink, never --brand. #800020 text on a dark
 *    panel fails contrast badly, so the ink lifts to pink in dark while the
 *    fill deepens. Same for --navy/--navy-ink and --gold-fill/--gold-ink.
 */
:root {
  color-scheme: light;
  /* brand — fill, fill-hover, tint, and the separate INK used for burgundy text */
  --brand:#800020; --brand-dark:#5e0018; --brand-tint:#f7ebee;
  --brand-ink:#800020; --brand-ink-hover:#5e0018;
  --brand-line:#eccdd4;   /* burgundy-family borders (dropped chip, flag note) */
  --focus:#800020;        /* focus rings — lifts in dark so it reads on dark inputs */
  /* --faint is the lightest TEXT tier that still passes AA. It was #94a0a8
     (~2.6:1 — below WCAG AA); darkened to clear 4.5:1 on both white cards and
     the grey canvas. --faint-soft is the sub-AA decorative tier below it
     (timestamps, "optional" labels, the umbrella chip) and collapses the old
     #8b959c/#95a0a7/#9aa4ab/#a9b3b9 near-duplicates onto one deliberate step.
     For a purely decorative hairline/dot use a --line value, not faint. */
  --ink:#263037; --heading:#20282d; --muted:#5c6770; --lead:#43505a; --faint:#656e76;
  --faint-soft:#9aa4ab;
  /* Surfaces, page-outward. --surface is what David's dark-mode spec calls
     "--panel": the standard panel. Kept under its existing name rather than
     renamed across ~40 call sites for no behavioural gain. */
  --bg:#f4f6f7; --surface:#ffffff;
  --raised:#f4f6f7;       /* inset rows, group heads, note cards — sits ON a panel */
  --overlay:#ffffff;      /* popovers, the card modal, toasts — one step above a panel */
  --sunken:#fbfcfc;       /* rows that sit BACK from the panel (terminal search rows) */
  --row-hover:#f4f6f7;    /* list-row hover fill */
  --scrim:rgba(16,24,32,.5);
  --line:#e3e8ea; --line-soft:#eef2f3;   /* --line-soft is the in-panel hairline */
  --line-strong:#dbe1e4;  /* control/input borders — one step darker than --line */
  --idle:#c3ccd1;         /* idle icon glyphs, disabled marks, neutral dots */
  --dash-line:#c3ccd1;    /* dashed pills — absence of a date is not a status */
  --chip-ring:rgba(0,0,0,.12);           /* the hairline ring on a coloured swatch */
  --neutral-fill:#e2e8ec; --neutral-line:#cbd4d9;
  /* Inverted pair: a dark fill with a light label in light mode, which FLIPS to
     a light fill with a dark label in dark mode (the "done" chip, the dark
     button, the active status tab). */
  --invert-bg:#20282d; --invert-ink:#ffffff; --invert-bg-hover:#0f1316;
  --disabled-fill:#cbb2b8;
  --slate:#1b2329;        /* the sidebar plane — the one surface that goes DARKER */
  --toast-bg:#1b2329; --toast-line:rgba(255,255,255,.10);
  --navy:#1e3a5f; --navy-ink:#1e3a5f;
  --gold:#c9a227;         /* the one fill that is unchanged in dark */
  --gold-ink:#836411;     /* gold TEXT */
  --gold-muted:#6d5f41;   /* gold meta text */
  --gold-fill:#836411; --gold-fill-hover:#6a5010;  /* the chase button: white on gold-brown */
  --gold-tint:#fdfaf2; --gold-band:#efe6d2; --gold-line:#ecdfba;
  --ok:#1d7f54; /* was #1f8a5b (2.5.1: 4.33:1 as text on --surface, below AA's 4.5:1) — darkened to 4.98:1 */
  --band-warm:#f4e6e6; --band-warm-line:#ecd7d7; --band-warm-ink:#786366; /* was #9a7f83 (2.5.1: 3.02:1 on --band-warm, below AA's 4.5:1 for its 13px text) — darkened to 4.58:1, same hue */
  --band-cool:#dde6ef; --band-cool-line:#cdd9e6; --band-cool-ink:#4d6076;
  /* Row treatments: the warm (overdue / dropped) and quiet (done) blocks. */
  --warm-row-bg:#fbf5f6; --warm-row-line:#f1dde1; --warm-muted:#7f6269; /* was #8a6b72 (2.5.1: 4.40:1 on --warm-row-bg, below AA) — darkened to 5.06:1, same hue */
  --blush-bg:#f4dde2; --blush-line:#e0b2bd;
  --done-block-bg:#f8fafa; --done-block-line:#eef2f3;
  --selection-bg:#f4e6e6; --selection-ink:#263037;
  --font-head:'Poppins', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
  --font-body:'DM Sans', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
  /* Four elevation steps, one per kind of surface. In dark these do very little
     work — the visible edge comes from --line — so every panel, card and
     popover carries a border as well as a shadow. */
  --shadow-sm:0 1px 3px rgba(0,0,0,.08);      /* a small pill lifted off its own track */
  --shadow-panel:0 10px 30px rgba(0,0,0,.05);
  --shadow-card:0 4px 14px rgba(0,0,0,.06);
  --shadow-popover:0 14px 34px rgba(0,0,0,.14);
  --shadow-modal:0 30px 80px rgba(0,0,0,.30);
  /* Spacing scale — eight steps, the last of the three dimensional systems to
     be tokenised (type and radius were done in the 2026-08-21 pass; spacing
     never was, which is why every page agreed on fonts and colours and
     disagreed on everything measured). Padding, margin and gap snap to these.
     Values above --s-8 stay literal on purpose: a page/panel/modal's own
     chrome (28/32/40/44/72) is a deliberately larger, one-off measure, not
     "space between two related things", and forcing it onto the scale would
     be a change of design rather than a removal of drift. */
  --s-1:2px; --s-2:4px; --s-3:6px; --s-4:8px;
  --s-5:12px; --s-6:16px; --s-7:20px; --s-8:24px;
  /* Radius scale — four steps + pill. Everything snaps to these (see
     design/CONTINUUM-UI.md); no more 7/9/10/16 near-duplicates. */
  --r-hair:2px; --r-sm:6px; --r:8px; --r-lg:12px; --r-pill:20px;
  --radius:12px; /* legacy alias of --r-lg; prefer --r-lg in new CSS */
  /* Text scale — 7 sizes only (design pass, 2026-08-21; CONTINUUM-UI.md's own
     audit flagged the earlier 9-size scale, with --t-list/--t-h1, as drift —
     micro-variation that reads as random rather than deliberate). Every size
     in the app is one of these seven; a page title is 22px (h2-sized), not
     its own larger step. */
  --t-micro:11px; --t-caption:12px; --t-secondary:13px; --t-ui:14px; --t-body:15px;
  --t-title:18px; --t-h2:22px;
  /* Icon glyph sizes — kept separate from the TEXT scale so tuning headings
     never resizes an icon (and vice-versa). */
  --ico:18px; --ico-lg:22px;
  /* One shell width for the app bar and every page, so the header stays aligned
     with the content and the width does not jump as you move between views. A
     large screen gets more room; smaller screens fall to 100% width and the
     media queries below reflow the grids. */
  --shell:1600px;
}

/* ------------------------------------------------------------------- dark */
/*
 * Dark mode (David's spec, 2026-09-07) is a COLOUR-TOKEN SWAP and nothing else.
 * Nothing below changes a layout, spacing, type or radius rule; if it ever has
 * to, that is a bug in the light theme's structure, not a dark requirement.
 *
 * The attribute is set on <html> by a blocking script in <head> (render.ts)
 * before the first paint, reading localStorage then prefers-color-scheme — a
 * later toggle would flash white on every load of an app you open every morning.
 *
 * Four things that are NOT inversions:
 *  1. Dark surfaces carry less contrast range than light ones, or the page
 *     glares. The page bottoms out at #14191d and body text tops out at
 *     #dde4e8 — no pure black, no pure white.
 *  2. Elevation is surface LIGHTNESS, not shadow: bg < panel < raised <
 *     overlay. The shadows stay for edge definition at roughly half strength;
 *     the visible edge comes from --line.
 *  3. Saturated brand colours are LIFTED, not dimmed. #800020 on a dark ground
 *     is mud, so the fill deepens to #9c1030 and the ink lifts to #e58a9d.
 *  4. The sidebar is the one surface that goes DARKER (#1b2329 -> #101418), so
 *     it still reads as a distinct chrome plane rather than merging with the
 *     page. Its own rgba(255,255,255,…) fills already work on both and stay.
 */
[data-theme="dark"] {
  /* Load-bearing: this is what darkens native select dropdowns, scrollbars, the
     text caret and autofill grounds. Without it those stay light and the page
     looks broken in exactly the places no CSS was written for. */
  color-scheme: dark;
  --brand:#9c1030; --brand-dark:#b3163a; --brand-tint:#2b1a20;
  --brand-ink:#e58a9d; --brand-ink-hover:#f0aebd;
  --brand-line:#4a2b34;
  --focus:#c25a72;
  --ink:#dde4e8; --heading:#e8eef1; --muted:#9aa7b0; --lead:#c3ced5; --faint:#8b98a1;
  /* --faint-soft is deliberately sub-AA in light (it is decoration: timestamps,
     "optional" labels). In dark it converges on --faint rather than sitting a
     step below it: measured against --overlay, the LIGHTEST surface it can land
     on, a step below fell to 4.2:1. Dark needs less contrast range than light
     (principle 1), so the two softest tiers collapsing here is correct, not a
     loss — and it keeps every hint line above the 4.5:1 floor. */
  --faint-soft:#8b98a1;
  --bg:#14191d; --surface:#1b2228;
  --raised:#212a31;
  --overlay:#242e35;
  --sunken:#191f24;       /* the one place the direction flips: BACK means darker */
  --row-hover:#232c33;
  --scrim:rgba(6,9,11,.66);
  --line:#333e46; --line-soft:#262f36;
  --line-strong:#3c4750;
  --idle:#5c6a74;
  --dash-line:#4a5660;
  --chip-ring:rgba(255,255,255,.16);
  --neutral-fill:#2b353d; --neutral-line:#3d4a53;
  --invert-bg:#e8eef1; --invert-ink:#161c21; --invert-bg-hover:#ffffff;
  --disabled-fill:#4a3a3e;
  --slate:#101418;
  --toast-bg:#242e35; --toast-line:var(--line);
  --navy:#2e5484; --navy-ink:#8fb2d8;
  --gold:#c9a227;         /* unchanged: chosen against a light ground, sits well on dark too */
  --gold-ink:#e2c469;
  --gold-muted:#b5a578;
  --gold-fill:#8a6a18; --gold-fill-hover:#a07d1c;
  --gold-tint:#2a2418; --gold-band:#332b18; --gold-line:#4a4028;
  --ok:#57c48d;
  --band-warm:#28191d; --band-warm-line:#3b262c; --band-warm-ink:#c09aa2;
  --band-cool:#1a222c; --band-cool-line:#2a3541; --band-cool-ink:#9db4cd;
  --warm-row-bg:#241a1e; --warm-row-line:#3f2a31; --warm-muted:#c9a1aa;
  --blush-bg:#33202a; --blush-line:#55323e;
  --done-block-bg:#1f272d; --done-block-line:#2b343b;
  --selection-bg:#3a2a30; --selection-ink:#e8eef1;
  --shadow-sm:0 1px 3px rgba(0,0,0,.36);
  --shadow-panel:0 1px 2px rgba(0,0,0,.30);
  --shadow-card:0 4px 12px rgba(0,0,0,.34);
  --shadow-popover:0 10px 28px rgba(0,0,0,.44);
  --shadow-modal:0 24px 70px rgba(0,0,0,.60);
}

/* The only non-token dark rules in this file, and both are unavoidable:
   a project's colour is chosen per project (not per theme) and arrives inline
   as two pairs, so the theme has to pick one. */
[data-theme="dark"] .chip--proj { background:var(--pc-bg-d); color:var(--pc-ink-d); }
/* The dark grounds all sit at L≈15% by design, so they are deliberately close
   to one another — a chip reads by its INK, not its ground, and that is fine
   for a chip, which has the ink written across it.
 *
 * It is not fine for a 22px swatch carrying no text: painted with the ground,
 * all eleven collapsed into a 26-43 RGB band and the picker stopped showing
 * which colour you were choosing (alignment review, 2026-09-07 — an inked ring
 * alone, added earlier the same day, was not enough). In dark the swatch takes
 * the INK as its fill: in this theme the ink IS the colour you perceive a chip
 * to be, so this is the same decision applied to the one control whose whole
 * job is to show it. The palette itself is untouched — every chip's
 * contrast-checked ink/ground pairing is unchanged.
 *
 * "Auto" is the exception: its ink and ground are --faint-soft on --surface, so
 * inking its fill would hide its own "A". It keeps the ground treatment. */
[data-theme="dark"] .swpick { background:var(--pc-ink-d); border-color:var(--pc-ink-d); }
[data-theme="dark"] .swpick--auto { background:var(--pc-bg-d); color:var(--pc-ink-d); border-color:var(--pc-ink-d); }

* { box-sizing:border-box; }

/* Enforce the collapsed state of <details> rather than trusting the browser to.
   Most engines hide non-summary children when closed, but not all do — and the
   card actions menu is an absolutely-positioned panel, so a renderer that skips
   that behaviour leaves it floating over the card permanently. Stating it
   explicitly costs nothing where the browser already does the right thing. */
details:not([open]) > *:not(summary) { display:none; }
/* Reserve the scrollbar's space permanently, so hiding it while a modal is open
   (overflow:hidden below) does not widen the page and shift everything sideways. */
/* background set here too, not just on body: View Transitions snapshot the
   page as ::view-transition-old/new(root), and the gap between them during
   the cross-fade shows through to <html>'s own background — if that's left
   transparent (the browser's default canvas, white), every htmx swap flashed
   white regardless of theme, worst in dark mode where it's most visible. */
html { scroll-behavior:smooth; scrollbar-gutter:stable; background:var(--bg); }
body {
  margin:0; font-family:var(--font-body); color:var(--ink); background:var(--bg);
  line-height:1.6; -webkit-font-smoothing:antialiased; overflow-wrap:break-word;
}
body.modal-open { overflow:hidden; }

/* The modal backdrop is a persistent layer whose opacity is driven purely by the
   `modal-open` body class, so it darkens on open and lightens on close over a
   calm half-second — independent of how the modal panel itself swaps in and out. */
.scrim {
  position:fixed; inset:0; background:var(--scrim); z-index:39;
  opacity:0; pointer-events:none; transition:opacity .45s ease;
}
body.modal-open .scrim { opacity:1; }
a { color:var(--brand-ink); text-decoration:none; }
a:hover { color:var(--brand-ink-hover); }
::selection { background:var(--selection-bg); color:var(--selection-ink); }
input, textarea, select, button { font-family:inherit; }
textarea { resize:vertical; }
:focus-visible { outline:3px solid var(--focus); outline-offset:2px; border-radius:var(--r-hair); }

/* Inline Material icons: size to the current font-size, colour to currentColor,
   so the tokenised sizes and existing colour rules drive them unchanged. */
svg.ico { width:1em; height:1em; fill:currentColor; vertical-align:-0.14em; flex:none; }

/* Auto-linked URLs in prose (description, notes): clearly clickable, brand-
   coloured, with a small "opens externally" mark. */
.exturl { color:var(--brand-ink); font-weight:500; text-decoration:none; border-bottom:1px solid color-mix(in srgb, var(--brand-ink) 34%, transparent); overflow-wrap:anywhere; }
.exturl:hover { border-bottom-color:var(--brand-ink); background:var(--brand-tint); }
.exturl__ico { font-size:.82em; margin-left:var(--s-1); opacity:.7; }
/* Fullscreen toggle shows enter/exit glyph by state (class set in toggleFull). */
.iconbtn--full .ico--exit { display:none; }
.iconbtn--full.is-full .ico--enter { display:none; }
.iconbtn--full.is-full .ico--exit { display:inline-block; }

/* Skip link — the first focusable thing on the page, so a keyboard user can jump
   past the app bar straight to the content. Hidden until focused. */
.skip {
  position:fixed; top:10px; left:10px; z-index:100; transform:translateY(-160%);
  background:var(--surface); color:var(--brand-ink); border:1px solid var(--brand-ink);
  border-radius:var(--r); padding:var(--s-4) var(--s-6); font-weight:600; font-size:var(--t-ui);
  box-shadow:var(--shadow-card); transition:transform .15s ease;
}
.skip:focus { transform:translateY(0); }
/* The content wrapper is a skip target (tabindex=-1); don't ring the whole page. */
#main:focus { outline:none; }
#main { scroll-margin-top:16px; }
::-webkit-scrollbar { width:10px; height:10px; }
::-webkit-scrollbar-thumb { background:var(--line-strong); border-radius:var(--r-sm); border:2px solid var(--bg); }

/* ------------------------------------------------------------------ chrome */
/* The app shell: a persistent left sidebar (nav + sync status) plus a slim
   sticky per-page header (design pass, 2026-08-15 — replaces the old top app
   bar). Kept below the modal-scrim (40) and toasts (60). */
.shell { display:flex; align-items:stretch; min-height:100vh; }
.sidebar {
  flex:none; width:236px; background:var(--slate); color:#fff;
  display:flex; flex-direction:column; position:sticky; top:0; height:100vh; z-index:30;
}
.sidebar__topbar { height:3px; background:var(--brand); flex:none; }
.sidebar__brand { display:flex; align-items:center; gap:var(--s-5); padding:var(--s-7); }
.psmark { position:relative; width:26px; height:26px; flex:none; }
.psmark span { position:absolute; width:9px; height:9px; animation:psFocus .9s ease both; }
.psmark span:nth-child(1) { top:0; left:0;  border-top:2px solid var(--brand); border-left:2px solid var(--brand); }
.psmark span:nth-child(2) { top:0; right:0; border-top:2px solid var(--brand); border-right:2px solid var(--brand); animation-delay:.05s; }
.psmark span:nth-child(3) { bottom:0; right:0; border-bottom:2px solid var(--brand); border-right:2px solid var(--brand); animation-delay:.1s; }
.psmark span:nth-child(4) { bottom:0; left:0;  border-bottom:2px solid var(--brand); border-left:2px solid var(--brand); animation-delay:.15s; }
.sidebar__word { display:flex; flex-direction:column; gap:var(--s-1); }
.sidebar__word b { font-family:var(--font-head); font-weight:600; font-size:var(--t-title); letter-spacing:.2px; color:#fff; }
.sidebar__word span { font-size:var(--t-micro); letter-spacing:.14em; text-transform:uppercase; color:#7d8993; }

.sidebar__nav { flex:1 1 auto; overflow-y:auto; display:flex; flex-direction:column; gap:var(--s-1); padding:var(--s-4) var(--s-5); }
.sidebar__link {
  display:flex; align-items:center; gap:var(--s-5); padding:var(--s-4) var(--s-5); border-radius:var(--r);
  color:#aab3ba; font-weight:600; font-size:var(--t-ui); transition:background .12s, color .12s;
}
.sidebar__link:hover { background:rgba(255,255,255,.06); color:#fff; }
.sidebar__link--active { background:rgba(255,255,255,.1); color:#fff; box-shadow:inset 3px 0 0 var(--brand); }
.sidebar__ico { display:flex; font-size:var(--ico); flex:none; }
.sidebar__label { flex:1 1 auto; }

.sidebar__foot { flex:none; padding:var(--s-5) var(--s-6) var(--s-7); border-top:1px solid rgba(255,255,255,.08); }
.sidebar__syncline { display:flex; align-items:center; gap:var(--s-4); font-size:var(--t-caption); color:#9aa4ab; margin:0 0 var(--s-4); }
.sidebar__syncdot { width:7px; height:7px; border-radius:50%; background:var(--ok); flex:none; }
.sidebar__syncbtn {
  width:100%; display:flex; align-items:center; justify-content:center; gap:var(--s-4); cursor:pointer;
  background:rgba(255,255,255,.07); border:1px solid rgba(255,255,255,.16); color:#fff;
  border-radius:var(--r); padding:var(--s-4) var(--s-5); font-weight:600; font-size:var(--t-secondary);
  transition:background .12s, border-color .12s;
}
.sidebar__syncbtn:hover { background:rgba(255,255,255,.13); border-color:rgba(255,255,255,.26); }
.sidebar__tz { font-size:var(--t-micro); color:#6b747b; text-align:center; margin:var(--s-4) 0 0; }
/* Theme picker — Settings only now (David, 2026-09-08: dropped the sidebar's
   duplicate copy, then "reduce this to two options, just light and dark and
   make the select better its weird probably a dropdown" — the old three-way
   segmented button row is gone; a plain <select> needs none of its own
   on/off state styling, just the ordinary field look). */
.themeselect {
  height:44px; padding:0 var(--s-6); font-size:var(--t-ui); color:var(--ink);
  border:1px solid var(--line-strong); border-radius:var(--r-lg); background:var(--surface); cursor:pointer;
}

.shell__main { flex:1 1 auto; min-width:0; }

/* Global search box + its drop-down of results — lives in the light .pagehead
   now (moved from the dark app bar), so its colours flip to a light control. */
.appsearch { position:relative; }
.appsearch__input {
  width:200px; max-width:32vw; height:36px; padding:0 var(--s-5);
  background:var(--surface); border:1px solid var(--line-strong); border-radius:var(--r);
  color:var(--ink); font-family:var(--font-body); font-size:var(--t-secondary);
  transition:border-color .12s, box-shadow .12s, width .18s ease;
}
.appsearch__input::placeholder { color:var(--faint); }
.appsearch__input:focus { outline:none; border-color:var(--focus); width:280px; box-shadow:0 0 0 3px var(--brand-tint); }
.appsearch__results { position:absolute; top:calc(100% + 8px); right:0; width:min(480px,86vw); z-index:45; }
.appsearch__results:empty { display:none; }
/* Hidden (not cleared) while a card is open — it sits above the modal's z-index
   and would otherwise float over the opened card. body.modal-open is already
   toggled on every open/close, so the dropdown reappears, unchanged, the moment
   the card closes: the search "persists until cleared" rather than vanishing
   the instant you pick a result (David, 2026-08-05). */
body.modal-open .appsearch__results { display:none; }
.srch__panel {
  background:var(--overlay); border:1px solid var(--line); border-radius:var(--r-lg); overflow:hidden;
  box-shadow:var(--shadow-popover); max-height:min(70vh,560px); overflow-y:auto;
  animation:rise .16s ease both;
}
.srch__head { padding:var(--s-4) var(--s-5); font-size:var(--t-micro); font-weight:700; letter-spacing:.12em; text-transform:uppercase; color:var(--faint); border-bottom:1px solid var(--line-soft); position:sticky; top:0; background:var(--overlay); }
.srch__row { display:flex; align-items:center; justify-content:space-between; gap:var(--s-5); padding:var(--s-5); border-bottom:1px solid var(--line-soft); }
.srch__row:last-child { border-bottom:none; }
.srch__row:hover { background:var(--bg); }
/* baseline, not center (David: "the card id is not aligned") — #id (t-micro,
   bold) and the title (t-body, medium) are different font sizes on one line;
   centering by line-height instead of by text baseline is what read as off. */
.srch__main { display:flex; align-items:baseline; gap:var(--s-4); min-width:0; flex:1 1 auto; }
/* Only the title shrinks/ellipsizes — id and the (full-page-only) project
   chip stay full size, never squeezed down to a couple of clipped letters. */
/* A fixed, right-aligned id column: without it the id's own width (#7 to #382)
   set where the title started, so the left edge of the titles drifted 18px down
   the list. Same class, same fix, in the app-bar dropdown. */
.srch__id { font-size:var(--t-micro); font-weight:700; color:var(--faint); flex:none; min-width:32px; text-align:right; }
/* A card's own id, shown on every list/tile representation of a card so it's
   always quotable — same visual weight as .srch__id/.link__id, generalised.
   Sits inline wherever the caller places it, not absolutely positioned:
   every card shape already has content claiming its literal top-right
   corner (a hover schedule button, an urgency pill, a due chip), so the id
   joins that existing cluster rather than fighting it for the same pixels. */
.cardid { font-size:var(--t-micro); font-weight:700; color:var(--faint); font-family:var(--font-head); flex:none; }
/* min-width is a floor, not a size: at ~960px the meta column kept its full
   width while the title was crushed to a couple of clipped letters. The title
   holds this much before anything else yields; the meta (and the waiting line
   inside it, the one genuinely long thing there) shrinks first.
   flex-grow is 0, not 1 (David: "position the project tag to follow the
   title") — grow:1 let a short title's box stretch to fill all the leftover
   room in .srch__main, shoving the project chip after it out to the row's far
   right edge, snug against the status pill instead of the title it names.
   Shrink stays on (still ellipsizes at narrow widths, still floored at the
   same 96px) — only the "claim empty space when there's plenty of it" half
   of flex:1 1 auto is gone. */
.srch__title { font-size:var(--t-body); color:var(--ink); font-weight:500; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; flex:0 1 auto; min-width:96px; }
/* Deliberately unchanged, and measured before leaving it alone: the chip's own
   TEXT sits on exactly the same line box as the card id's (260.78-274.78 for
   both), and its baseline matches the title's to within a rounding error. The
   ~3.6px the alignment review measured is the chip's padding box hanging below
   the title's, which is the .chip family's own deliberate height (2026-08-15:
   a bare chip beside a bordered one reads visibly uneven). Nothing to fix. */
.srch__main .chip--proj { flex:none; }
.srch__meta { display:flex; align-items:center; gap:var(--s-4); flex:0 1 auto; min-width:0; }
/* A fixed date column is what makes the status pills before it form a column.
   NOT font-variant-numeric: the vendored DM Sans subset carries no `tnum`
   feature, so tabular-nums is a no-op here — measured, not assumed. 74px is
   the font's own worst case at 12px ("1" is 4.06px wide and "0" is 8.89px, so
   a DD/MM/YYYY reaches 73.64px), not the widest date currently in the data —
   sized to the data (68px) the column still drifted 1.09px. The span is
   rendered even when a card has no due date, which is what keeps the pills of
   the 201 undated rows in the same column as everyone else's. */
.srch__due { font-size:var(--t-caption); color:var(--muted); white-space:nowrap; min-width:74px; text-align:right; }
.srch__waiting { font-size:var(--t-caption); color:var(--muted); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; min-width:0; }
.srch__waiting b { color:var(--ink); font-weight:600; }
.srch__empty { padding:var(--s-6); margin:0; font-size:var(--t-ui); color:var(--muted); }
/* Done and dropped results sit back a shade so live work reads first. */
.srch__row--dim { background:var(--sunken); }
.srch__row--dim .srch__title { color:var(--muted); }
.srch__row--dim:hover { background:var(--line-soft); }
/* Browsing's "Load more" (David, 2026-09-08 — the browse view no longer
 * fetches/renders every card up front). A full-width row so it reads as part
 * of the list, not a floating action. */
.srch__more {
  display:block; width:100%; padding:var(--s-5); border:none; border-top:1px solid var(--line-soft);
  background:var(--sunken); color:var(--muted); font-family:var(--font-body); font-size:var(--t-secondary); font-weight:600;
  cursor:pointer; text-align:center;
}
.srch__more:hover { background:var(--line-soft); color:var(--heading); }
@media (max-width: 760px) { .appsearch { display:none; } }
/* .srch__row's id/title/chip and status/date/waiting cluster are floored
   (.srch__title at 96px, .srch__due at 74px, etc — deliberately, see those
   rules) for the ~960px case where the meta column mustn't crush the title.
   At phone widths the floors add up to more than the row has: confirmed the
   full search page overflowed the viewport itself (429px of content in a
   375px window) and, clipped by .archcard's overflow:hidden, the status pill
   and due/waiting text visually ran into each other. Wrapping .srch__main
   and .srch__meta onto their own full-width lines keeps every floor intact
   but stops them competing for the same row. */
@media (max-width: 640px) {
  .srch__row { flex-wrap:wrap; row-gap:var(--s-3); }
  .srch__main { flex-basis:100%; }
  .srch__meta { flex-basis:100%; justify-content:flex-end; }
}

/* Full search page: a prominent input over a persistent results list. */
/* David, 2026-09-08: "make search bar full width, put everything else below"
 * — the search box is now the only thing in the static bar, full width on
 * its own row; every filter lives in the swapped panel underneath it (see
 * .searchpage__filters below). */
.searchpage { margin:0 0 var(--s-6); }
.searchpage__input {
  width:100%; height:52px; padding:0 var(--s-6); font-family:var(--font-body); font-size:var(--t-title);
  color:var(--ink); background:var(--surface); border:1px solid var(--line-strong); border-radius:var(--r-lg);
  transition:border-color .12s, box-shadow .12s;
}
.searchpage__input:focus { outline:none; border-color:var(--focus); box-shadow:0 0 0 3px var(--brand-tint); }
/* The filter row, below the full-width search box (David, 2026-09-08 — full
 * redesign: "make search bar full width, put everything else below... give
 * all the filters a title"). Each field is its own labelled group so the
 * control is never just guessed at from an unlabelled box; the hint (real
 * shown/total count) sits at the row's far end, same role .statustabs__hint
 * used to play. */
.searchpage__filters { display:flex; align-items:flex-end; gap:var(--s-5); flex-wrap:wrap; margin-top:var(--s-5); }
.searchpage__filterform { display:flex; align-items:flex-end; gap:var(--s-5); flex-wrap:wrap; }
.searchpage__field { display:flex; flex-direction:column; gap:var(--s-2); }
.searchpage__fieldlbl { font-size:var(--t-caption); font-weight:600; color:var(--muted); }
/* .searchpage__meta wraps the hint and the (conditional) Clear link so both
   sit together at the row's far end, not spread apart — the hint alone used
   to claim the whole flex remainder, leaving nothing for a sibling to sit
   next to (David, 2026-09-09: "add a clear search option"). */
.searchpage__meta { flex:1 0 auto; display:flex; justify-content:flex-end; align-items:baseline; gap:var(--s-5); padding-bottom:var(--s-3); }
.searchpage__hint { text-align:right; font-size:var(--t-caption); color:var(--muted); white-space:nowrap; }
.searchpage__clear { font-size:var(--t-caption); font-weight:600; color:var(--brand-ink); white-space:nowrap; }
.searchpage__clear:hover { text-decoration:underline; }
/* The select keeps the shared .filterbar field treatment (colours, border,
   radius, min-width — see the filter-bar rules below) but takes the search
   input's HEIGHT, which is the one thing it sits directly beside: matched to
   the shared 38px it read as visibly short next to a 52px input, which is the
   mismatch David actually asked to have fixed (alignment review, 2026-09-07 —
   narrowing the earlier call the same day that it should match the field
   family in every respect). The height override itself lives with the
   filter-bar rules below, which would otherwise win on source order. */
/* On the page every row is its own bordered .archcard (David: "this is the
   design" — the old Archive page's look, applied to every row, not just
   Done/Dropped's outcome block) — the panel's own border/shadow would just
   double-box that, so the full-page variant goes bare: no background, no
   border, no shadow, no scroll clipping. The dropdown keeps its own floating-
   popover look (border, shadow, max-height scroll, rise animation) — its rows
   stay flat, never individually carded (compact quick-pick, not a browsing
   list), so its panel chrome is still doing real work there. */
.searchpage__results .srch__panel {
  background:transparent; border:none; box-shadow:none; overflow:visible; max-height:none; animation:none;
}
/* On the full page, "no results" is its own centred moment, not a compact
   dropdown line (design pass, 2026-08-21). */
.searchpage__results .srch__empty { padding:28px var(--s-7); text-align:center; font-size:var(--t-body); }

/* In dark the sidebar and the page sit ~2% apart in lightness (#101418 against
   #14191d) with nothing between them, so the strongest structural element in
   light mode all but dissolved. A hairline is enough to give it its edge back;
   light mode has real contrast already and is untouched. */
[data-theme="dark"] .sidebar { border-right:1px solid var(--line); }

/* Below 900px the sidebar collapses to an icon-only top strip: still a single,
   simple layout to reason about, and this is a personal desktop-first tool
   (CLAUDE.md) so a deeper responsive redesign is not worth building. */
@media (max-width: 900px) {
  [data-theme="dark"] .sidebar { border-right:none; border-bottom:1px solid var(--line); }
  .shell { flex-direction:column; }
  .sidebar { position:static; width:100%; height:auto; flex-direction:row; align-items:center; }
  .sidebar__topbar { display:none; }
  .sidebar__brand { padding:var(--s-4) var(--s-5); }
  .sidebar__nav { flex-direction:row; overflow-x:auto; padding:var(--s-3) var(--s-4); }
  .sidebar__label { display:none; }
  .sidebar__foot { display:none; }
  .pagehead { position:static; }
  /* This row is the ONLY way to change page on a phone (the sidebar's own
     nav, not a hamburger menu elsewhere), so it is the single most-tapped
     control in the app on mobile — yet .sidebar__link's desktop padding
     (var(--s-4) var(--s-5)) plus an 18px icon measured 42×34px live at
     375px, under the ~44px accessibility guideline on the dimension that
     matters most for a horizontal strip of adjacent targets: height. Wider
     padding here, not just taller, so five icons stay a comfortable
     distance apart rather than merely tall. */
  .sidebar__link { padding:var(--s-6) var(--s-6); }
}
@media (max-width: 620px) {
  .pagehead { flex-wrap:wrap; }
}
/* At true phone widths the icon strip plus the full wordmark no longer both
   fit: the row's own overflow-x:auto quietly scrolled Settings — the last
   icon — off the right edge with no visible scrollbar to hint it, so it read
   as missing rather than reachable (confirmed: at 375px the strip needed
   234px but only had ~208px). Dropping the wordmark (the mark itself stays)
   below 480px reclaims enough width that every icon fits without scrolling. */
@media (max-width: 480px) {
  .sidebar__word { display:none; }
}

/* ------------------------------------------------------------------ page shell */
main.page { max-width:var(--shell); margin:0 auto; padding:clamp(24px,4vw,40px) 28px 72px; }
main.page.wide { max-width:var(--shell); }

/* The slim sticky per-page header: title + optional subtitle, global search,
   New card — replaces the old full-width hero title block on every page. */
.pagehead {
  position:sticky; top:0; z-index:20; background:var(--bg); border-bottom:1px solid var(--line);
  display:flex; align-items:center; gap:var(--s-6); flex-wrap:wrap; padding:var(--s-6) 28px;
}
.pagehead__title-row { display:flex; align-items:baseline; gap:var(--s-5); min-width:0; flex:1 1 auto; }
.pagehead__title {
  font-family:var(--font-head); font-weight:600; font-size:var(--t-h2);
  line-height:1.2; color:var(--heading); margin:0; letter-spacing:.2px; white-space:nowrap;
}
.pagehead__sub { font-size:var(--t-secondary); color:var(--muted); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
/* The header wraps at any width, not only below 620px where the auto-margin
   used to live — so between roughly 900px and 1110px "New card" dropped onto a
   second line and sat bottom-LEFT under the page title. The margin costs
   nothing on a row with no free space, so it applies everywhere. */
.pagehead__new { flex:none; margin-left:auto; }

/* A short explanatory line under a page's controls (Projects, project
   drilldown, Settings) — not to be confused with .note, the card
   detail modal's boxed append-only log entry. */
.pagenote { font-size:var(--t-body); color:var(--muted); margin:0 0 var(--s-7); max-width:640px; line-height:1.6; }
.pagenote--tight { margin-bottom:14px; } /* Settings' panel notes, tighter above a control right below them — was a copy-pasted inline style on three separate <p>s */
.projectpage__umbrella { margin:0 0 var(--s-6); }

/* ------------------------------------------------------------------ buttons */
.btn {
  display:inline-flex; align-items:center; gap:var(--s-4); white-space:nowrap; cursor:pointer;
  font-weight:600; font-size:var(--t-ui); padding:var(--s-4) var(--s-6); border-radius:var(--r); border:1px solid transparent;
  transition:background .15s, border-color .15s;
}
.btn--primary { background:var(--brand); color:#fff; }
.btn--primary:hover { background:var(--brand-dark); color:#fff; }
.btn--outline { background:transparent; border-color:var(--brand-ink); color:var(--brand-ink); }
.btn--outline:hover { background:var(--brand-tint); color:var(--brand-ink); }
.btn--dark { background:var(--invert-bg); color:var(--invert-ink); }
.btn--dark:hover { background:var(--invert-bg-hover); color:var(--invert-ink); }
.btn--text { background:transparent; border:none; color:var(--muted); font-weight:500; padding:var(--s-4) var(--s-3); }
.btn--text:hover { color:var(--brand-ink); }
.btn[disabled] { background:var(--disabled-fill); color:#fff; cursor:not-allowed; border-color:transparent; }
.btn .dot { width:7px; height:7px; border-radius:50%; background:currentColor; display:inline-block; }

/* card-opening anchors carry list/card classes but must not read as links */
a.oc { color:inherit; text-decoration:none; cursor:pointer; }
a.oc:hover { color:inherit; text-decoration:none; }

/* ------------------------------------------------------------------ panels */
.panel {
  background:var(--surface); border:1px solid var(--line); border-radius:var(--radius);
  box-shadow:var(--shadow-panel); padding:var(--s-8) 28px;
}
.panel + .panel { margin-top:var(--s-7); } /* stacked settings sections — was a copy-pasted inline style on each <section> after the first */
.panel__title { font-family:var(--font-head); font-weight:600; font-size:var(--t-h2); color:var(--heading); margin:0; letter-spacing:.2px; }

/* A "split" panel tints only its header band, not the whole card — the
 * Cockpit's Needs-attention and Inbox panels (design v2). */
.panel--split { padding:0; overflow:hidden; }
.panel__band { padding:var(--s-6) var(--s-7); border-bottom:1px solid; }
.panel__band--warm { background:var(--band-warm); border-color:var(--band-warm-line); }
.panel__band--cool { background:var(--band-cool); border-color:var(--band-cool-line); }
.panel__band-top { display:flex; align-items:center; gap:var(--s-4); }
.panel__band .panel__title { font-size:var(--t-title); }
.panel__body { padding:var(--s-6); }
.prule { height:3px; width:56px; background:var(--brand); border-radius:var(--r-hair); margin:var(--s-5) 0 var(--s-7); }
.phead { display:flex; align-items:baseline; justify-content:space-between; gap:var(--s-6); flex-wrap:wrap; }
.phead-l { display:flex; align-items:baseline; gap:var(--s-5); }
.phead .hint { font-size:var(--t-secondary); color:var(--faint); }
/* The right side of a .phead can carry more than the bare hint now (This
   week's date range plus its project filter trigger) — one flex wrapper so
   .phead's own space-between still only ever sees two children. */
.phead__r { display:flex; align-items:center; gap:var(--s-4); }

/* Cockpit panel collapse/expand (David, 2026-09-08: Today/This week/Next
 * week should be collapsable). `.disclosure` sits before the title in
 * `.phead-l`/`.dayhead`'s own left group; `[data-collapsible]` is the
 * element `togglePanel` (render.ts) adds/removes `.is-collapsed` on and
 * whose `.panel__collapsible` child it hides — always the panel's own root,
 * so this one class pair works for `.yourday` and `.weekpanel` alike. */
.disclosure {
  background:none; border:none; cursor:pointer; padding:var(--s-2); margin:0 calc(var(--s-2) * -1) 0 0;
  border-radius:var(--r-sm); color:var(--muted); display:inline-flex; align-items:center; align-self:center;
}
.disclosure:hover { background:var(--line-soft); color:var(--heading); }
.disclosure .ico { transition:transform .15s; }
[data-collapsible].is-collapsed .disclosure .ico { transform:rotate(-90deg); }
[data-collapsible].is-collapsed .panel__collapsible { display:none; }
[data-collapsible].is-collapsed.weekpanel { padding-bottom:20px; }
/* var(--s-2) padding plus the 18px chevron icon measured 21×21px live at
   375px — this button is the ONLY way to collapse/expand a Cockpit panel
   (togglePanel binds the click to the button itself, not the heading text
   beside it), so a too-small target here blocks the action outright rather
   than just being fiddly. Widened below 640px only — desktop already has
   an easy, precise mouse pointer. */
@media (max-width: 640px) {
  .disclosure { padding:var(--s-4); }
}

/* ------------------------------------------------------------------ pills & chips */
.countpill {
  min-width:22px; text-align:center; font-family:var(--font-head); font-weight:700; font-size:var(--t-caption);
  color:#fff; background:var(--brand); border-radius:var(--r-pill); padding:var(--s-1) var(--s-4);
}
.countpill--slate { background:var(--invert-bg); color:var(--invert-ink); font-size:var(--t-caption); padding:var(--s-1) var(--s-5); }
.countpill--navy  { background:var(--navy); }

.chip {
  font-size:var(--t-micro); font-weight:700; letter-spacing:.05em; text-transform:uppercase;
  border-radius:var(--r-pill); padding:var(--s-2) var(--s-5); white-space:nowrap; display:inline-block;
}
/* due chips — same uppercase/weight/size as every other chip (David,
   2026-09-07: "some [chips are] all caps and others not" was a real, app-wide
   inconsistency, not just the card header — these five used to opt out of
   .chip's own text-transform, so a due-date chip was the one odd one out
   sitting next to a status or project chip anywhere they appear together). */
.chip--overdue { background:var(--brand); color:#fff; border:1px solid var(--brand); }
.chip--today   { background:var(--blush-bg); color:var(--brand-ink); border:1px solid var(--blush-line); }
.chip--future  { background:var(--neutral-fill); color:var(--lead); border:1px solid var(--neutral-line); }
.chip--parked  { background:var(--line-soft); color:var(--lead); border:1px dashed var(--dash-line); }
.chip--nodate  { background:var(--line-soft); color:var(--muted); border:1px solid var(--line); }
/* status chips */
.chip--open    { background:var(--overlay); color:var(--lead); border:1px solid var(--line-strong); }
.chip--waiting { background:var(--gold-tint); color:var(--gold-ink); border:1px solid var(--gold-line); }
.chip--done    { background:var(--invert-bg); color:var(--invert-ink); border:1px solid var(--invert-bg); }
.chip--dropped { background:var(--brand-tint); color:var(--brand-ink); border:1px solid var(--brand-line); }
/* A role icon inside a chip — due/project/umbrella in the card detail header
   (David: colour alone wasn't enough to say what each chip was — unlike a
   status chip, whose own text already says everything, so it carries no
   icon at all). fill:currentColor (from svg.ico's own rule) already matches
   the chip's own ink, light or dark, with no extra colour work needed. */
.chip .ico, .chip--proj .ico, .chip--umbrella .ico { width:11px; height:11px; margin:0 var(--s-3) -1px 0; }
/* Umbrella (derived, secondary): the SAME colour as the project it comes from,
   but outlined rather than filled — filled means "a tag you set", outlined
   means "derived from the hierarchy". A chip carrying no project colour (there
   is nowhere left that renders one, but the rule must not collapse to
   invisible if one appears) falls back to the neutral dashed treatment. */
.chip--umbrella {
  font-weight:600; text-transform:uppercase; background:transparent;
  color:var(--pc-ink, var(--faint-soft));
  border:1px dashed color-mix(in srgb, var(--pc-ink, var(--dash-line)) 55%, transparent);
}
[data-theme="dark"] .chip--umbrella {
  color:var(--pc-ink-d, var(--faint-soft));
  border-color:color-mix(in srgb, var(--pc-ink-d, var(--dash-line)) 55%, transparent);
}
/* session state */
.chip--planned { background:var(--band-cool); color:var(--navy-ink); font-size:var(--t-micro); letter-spacing:.06em; }
/* A past session is history, not a failure — neutral grey, not the old burgundy
   "lapsed" flag. The stored state is unchanged; only the framing is. */
.chip--past  { background:var(--line-soft); color:var(--muted); font-size:var(--t-micro); letter-spacing:.06em; }
.chip--completed { background:var(--invert-bg); color:var(--invert-ink); font-size:var(--t-micro); letter-spacing:.06em; }

/* ------------------------------------------------------------------ cockpit */
/* minmax(0,…) pins each column to its fraction. Without it an fr track's floor
   is its content's min-width, so expanding an inbox row widened the column and
   shoved the whole layout sideways. */
.cockpit { display:grid; grid-template-columns:minmax(0,1.65fr) minmax(0,1fr); gap:var(--s-8); margin-top:36px; align-items:start; }
.col { display:flex; flex-direction:column; gap:var(--s-7); }

.seclabel {
  display:flex; align-items:center; gap:var(--s-4); font-weight:700; font-size:var(--t-micro);
  letter-spacing:.14em; text-transform:uppercase; color:var(--muted);
}
.secrow { display:flex; align-items:center; gap:var(--s-4); margin-bottom:var(--s-5); flex-wrap:wrap; }
.secrow .hint { font-size:var(--t-caption); color:var(--faint); margin-left:auto; }
/* A section heading can also carry a link out to the fuller view of the same
   thing (Waiting → Search filtered to waiting). It right-aligns like .hint. */
.secrow .seclink { font-size:var(--t-caption); font-weight:600; color:var(--brand-ink); margin-left:auto; white-space:nowrap; }
.secrow .seclink:hover { text-decoration:underline; }
/* A list broken into labelled groups (Unscheduled's Backlog/Waiting split, the
   project drilldown's deadline/no-deadline/waiting split) — same section
   heading as the cockpit's own panels, so grouping is not a new design idea. */
.listsec + .listsec { margin-top:var(--s-8); }
/* A grouped list's rows carry their own 16px horizontal padding (.archrow), and
   that padding is invisible because the row's background is transparent — so an
   unpadded heading above them read as outdented on both sides. The heading takes
   the same inset as the rows it labels. */
.listsec > .secrow { padding-left:var(--s-6); padding-right:var(--s-6); }
/* The first heading sat flush against the filter bar above it (0px) while
   holding 12px off its own cards — reading as attached to the wrong element. */
#unscheduled { margin-top:var(--s-7); }
.thinrule { height:1px; background:var(--line-soft); margin-bottom:var(--s-6); }
.ico-clock { width:11px; height:11px; border:1.5px solid var(--navy-ink); border-radius:50%; position:relative; flex:none; }
.ico-clock::after { content:''; position:absolute; top:1px; left:3.5px; width:1.5px; height:4px; background:var(--navy-ink); }
.ico-diamond { width:9px; height:9px; background:var(--brand-ink); transform:rotate(45deg); border-radius:var(--r-hair); flex:none; }

.stack { display:flex; flex-direction:column; gap:var(--s-4); }
.stack--tight { gap:var(--s-3); }
/* THE empty state. One family, everywhere (design review, 2026-09-07): a
   dashed box, optionally with an icon. The bare-grey-text `.emptyline`
   alternative is gone — it had no rule about when to use which, so the same
   page could show one of each side by side (the cockpit did). */
.emptybox { display:flex; align-items:center; gap:var(--s-4); background:var(--bg); border:1px dashed var(--line-strong); border-radius:var(--r); padding:var(--s-5); color:var(--faint); font-size:var(--t-ui); }
.emptybox .ico { flex:none; }
.emptybox--ok .ico { color:var(--ok); }
/* Inbox's empty state is its own family — solid navy-tinted border on white,
   not the grey/dashed pattern every other empty state uses (it sits inside the
   cool-banded Inbox panel and reads as part of it). */
.emptybox--cool { background:var(--surface); border:1px solid var(--band-cool-line); color:var(--band-cool-ink); }
.emptybox--cool .ico { color:var(--navy-ink); }
.gap-b { margin-bottom:var(--s-8); }

/* session card */
.sess {
  display:flex; align-items:center; gap:var(--s-6); width:100%; text-align:left;
  border-radius:var(--r); padding:var(--s-5);
  transition:box-shadow .12s, filter .12s;
  /* Filled with the event's actual Google Calendar colour, with a text colour
     flipped for contrast (set per-row), exactly as Google draws its events. */
  --row-colour:#c3ccd4; --row-text:#2a2a2a;
  background:var(--row-colour);
  color:var(--row-text);
  border:1px solid color-mix(in srgb, var(--row-colour) 82%, #000);
}
.sess:hover { box-shadow:var(--shadow-card); filter:brightness(.96); }

/* A session reschedules in place: the row is a summary that expands to an editor. */
.sess--session { display:block; padding:0; align-items:stretch; overflow:hidden; }
.sess--session > .sess__summary { display:flex; align-items:center; gap:var(--s-6); padding:var(--s-5); cursor:pointer; list-style:none; color:var(--row-text); }
.sess--session > .sess__summary::-webkit-details-marker { display:none; }
/* The editor is a clean white panel under the coloured summary. */
.sess__resched { display:flex; flex-wrap:wrap; gap:var(--s-5); align-items:flex-end; padding:var(--s-5); background:var(--surface); color:var(--ink); }
.sess__resched label { display:flex; flex-direction:column; gap:var(--s-2); font-size:var(--t-caption); color:var(--muted); }
.sess__resched input, .sess__resched select { font-family:inherit; font-size:var(--t-ui); color:var(--ink); background:var(--surface); border:1px solid var(--line-strong); border-radius:var(--r); padding:var(--s-4); }
.sess__actions { display:flex; align-items:center; gap:var(--s-5); }
.sess__cardlink { font-size:var(--t-secondary); font-weight:600; color:var(--brand-ink); }
.sess__resched .dsess__hint { flex-basis:100%; }
.sess__edit { font-size:var(--t-caption); font-weight:600; color:var(--row-text); opacity:.75; white-space:nowrap; }
.sess--session:hover .sess__edit { opacity:1; }
/* All the row's own text takes the per-row contrast colour. */
.sess__time { font-family:var(--font-head); font-weight:600; font-size:var(--t-body); color:var(--row-text); min-width:52px; }
.sess__sep { width:1px; height:16px; background:color-mix(in srgb, var(--row-text) 40%, transparent); }
.sess__title { font-weight:500; font-size:var(--t-body); color:var(--row-text); flex:1 1 auto; }
/* Length sits at the right edge so the gaps between blocks — the free time — read at a glance. */
.sess__len { font-family:var(--font-head); font-weight:600; font-size:var(--t-secondary); color:var(--row-text); opacity:.8; white-space:nowrap; }
/* Finished blocks: dimmed so the day reads as done-behind, ahead-in-front. They
   stay legible (and still open their card) — just quieter. */
.sess--past { opacity:.5; }
.sess--past:hover { opacity:.85; }
/* A past session opened to reschedule is fully legible while you edit it. */
.sess--session[open] { opacity:1; }
/* At phone widths, .sess__title's default flex:1 1 auto has no floor and no
   nowrap, so with .sess__time (min-width:52px), .sess__edit ("Reschedule")
   and .sess__len both fixed and unwilling to shrink, the title was the only
   thing left to give — it wrapped word-by-word into a sliver a few characters
   wide, and the row (clipped by .sess--session's own overflow:hidden) still
   ran wider than its box, cutting "Reschedule" and the length short. Confirmed
   at 375px: the summary needed up to 305px in a 259px box. Wrapping the row
   and giving the title a single ellipsised line fixes both — Reschedule and
   the length drop to their own line under the title instead of fighting it
   for the same one. */
@media (max-width: 640px) {
  .sess__title { white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
  .sess--session > .sess__summary { flex-wrap:wrap; row-gap:var(--s-2); }
  .sess--session > .sess__summary .sess__title { flex:1 1 100px; min-width:60px; order:0; }
  .sess--session > .sess__summary .sess__time { order:1; }
  .sess--session > .sess__summary .sess__sep { order:2; }
  .sess--session > .sess__summary .sess__edit { flex-basis:100%; order:4; }
  .sess--session > .sess__summary .sess__len { order:5; margin-left:auto; }
}
/* The "now" line: where the finished day gives way to what is still to come. */
.nowline { display:flex; align-items:center; gap:var(--s-4); margin:var(--s-1); }
.nowline__dot { width:8px; height:8px; border-radius:50%; background:var(--brand-ink); flex:none; box-shadow:0 0 0 3px var(--brand-tint); }
.nowline__label { font-family:var(--font-head); font-weight:600; font-size:var(--t-caption); letter-spacing:.06em; text-transform:uppercase; color:var(--brand-ink); white-space:nowrap; }
.nowline::after { content:""; flex:1; height:1px; background:linear-gradient(to right,var(--brand-ink),transparent); opacity:.5; }

/* "Your day" header: the day label (in the panel title) + a date picker. */
.dayhead { display:flex; align-items:center; justify-content:space-between; gap:var(--s-6); flex-wrap:wrap; }
.dayhead .panel__title { margin:0; }
.daynav { display:flex; align-items:center; gap:var(--s-4); }
.daynav__btn {
  border:1px solid var(--line); background:var(--surface); color:var(--navy-ink); cursor:pointer;
  height:32px; padding:0 var(--s-5); border-radius:var(--r);
  font-family:var(--font-body); font-size:var(--t-caption); font-weight:600; line-height:1;
  transition:border-color .12s, color .12s;
}
.daynav__btn:hover { border-color:var(--brand-ink); color:var(--brand-ink); }
/* Prev/next are square icon buttons, matching the design's chevron pair either
   side of "Today"; "Today" itself dims slightly once it's already the day shown. */
.daynav__step { width:32px; padding:0; display:inline-flex; align-items:center; justify-content:center; }
.daynav__now--current { color:var(--muted); cursor:default; }
.daynav__now--current:hover { border-color:var(--line); color:var(--muted); }
.daypick {
  border:1px solid var(--line); background:var(--surface); color:var(--navy-ink); cursor:pointer;
  height:32px; padding:0 var(--s-4); border-radius:var(--r);
  font-family:var(--font-body); font-size:var(--t-secondary); font-weight:600;
  transition:border-color .12s, box-shadow .12s;
}
.daypick:hover { border-color:var(--brand-ink); }
.daypick:focus { outline:none; border-color:var(--focus); box-shadow:0 0 0 3px var(--brand-tint); }

/* task row (due today) */
.taskrow {
  display:flex; align-items:center; justify-content:space-between; gap:var(--s-5); width:100%; text-align:left;
  background:var(--surface); border:1px solid var(--line); border-radius:var(--r); padding:var(--s-5);
  transition:box-shadow .12s, border-color .12s;
}
.taskrow:hover { box-shadow:var(--shadow-card); border-color:var(--band-cool-line); }
/* align-items:flex-start is load-bearing — a column flex container stretches its
   children by default, which made project chips span the whole row. */
.taskrow__main { display:flex; flex-direction:column; align-items:flex-start; gap:var(--s-3); min-width:0; }
.taskrow__title { font-weight:500; font-size:var(--t-body); color:var(--ink); }
.taskrow__right { display:flex; align-items:center; gap:var(--s-4); flex:none; }

/* this-week list */
/* Round four (David, 2026-09-08): the panel gets real breathing room and its
 * own border — distinct from the plain .panel it was sharing with "Your day",
 * kept different on purpose rather than folded into the same look. */
.weekpanel { border:1px solid var(--line-strong); padding:32px 28px; }
.weekgrid { display:grid; grid-template-columns:repeat(2,1fr); gap:var(--s-6) var(--s-6); }
@media (max-width:640px) { .weekgrid { grid-template-columns:1fr; } }
/* Date above the title, plain text rather than a pill (David: the chip-beside-
 * title layout was "too hard to read"). A small role icon (calendar for a due
 * date, clock for a session-only date — see weekRowDate in views.ts) does the
 * job a coloured chip was doing, without the visual weight.
 * The project chip moved up to join the date on this same top line (David,
 * 2026-09-08: "the tags... should be at the top of each card") — its own,
 * smaller padding here rather than a fourth chip size across the app, since
 * the type scale's own floor (--t-micro) is already this row's font-size. */
.weekrow {
  display:flex; flex-direction:column; align-items:flex-start; gap:var(--s-3); width:100%; text-align:left;
  background:transparent; border:none; border-radius:var(--r); padding:var(--s-4);
  transition:background .12s, box-shadow .12s, transform .12s;
}
/* Hover lifts the card toward you, same treatment as .bcard on the project
   drilldown grid — one hover language for "a card in a grid" everywhere. */
.weekrow:hover { background:var(--surface); box-shadow:var(--shadow-card); transform:translateY(-2px); z-index:1; position:relative; }
/* Checkerboard, not row bands (David, 2026-09-08: "row 1 white grey row 2
   grey then white and alternate in that way") — in the 2-column grid that's
   the 2nd and 3rd card of every 4, since that's exactly the diagonal pair;
   :hover always wins back to the plain surface regardless. */
.weekrow:nth-child(4n+2), .weekrow:nth-child(4n+3) { background:var(--line-soft); }
.weekrow__top { display:flex; align-items:center; justify-content:space-between; gap:var(--s-3); width:100%; }
.weekrow__leftmeta { display:flex; align-items:center; gap:var(--s-3); min-width:0; }
.weekrow__leftmeta .chip--proj { padding:2px var(--s-3); }
.weekrow__title { font-size:var(--t-ui); line-height:1.35; color:var(--lead); }
.weekrow__date { display:inline-flex; align-items:center; font-size:var(--t-caption); color:var(--muted); flex:none; }
.weekrow__date .ico { width:11px; height:11px; margin:0 var(--s-2) -1px 0; }
.weekrow__date--none { color:var(--faint); }

/* This week's project filter — plain checkboxes in a popover, not a native
   <select multiple> (no ctrl/cmd-click to discover), same popover family as
   .qsched/.projform elsewhere on a card. */
.weekfilter { position:relative; }
.weekfilter__btn {
  list-style:none; cursor:pointer; user-select:none; display:inline-flex; align-items:center; gap:var(--s-3);
  font-size:var(--t-caption); font-weight:600; color:var(--muted); padding:var(--s-2) var(--s-4); border-radius:var(--r);
  transition:background .12s, color .12s;
}
.weekfilter__btn .ico { width:11px; height:11px; }
.weekfilter__btn::-webkit-details-marker { display:none; }
.weekfilter__btn:hover, .weekfilter[open] .weekfilter__btn { background:var(--line-soft); color:var(--heading); }
.weekfilter__pop {
  position:absolute; top:calc(100% + var(--s-2)); right:0; z-index:15; width:max-content; max-width:min(260px, 90vw);
  display:flex; flex-direction:column; gap:var(--s-2); max-height:280px; overflow-y:auto;
  background:var(--overlay); border:1px solid var(--line); border-radius:var(--r-lg); box-shadow:var(--shadow-popover); padding:var(--s-4);
}
.weekfilter__opt { display:flex; align-items:center; gap:var(--s-3); font-size:var(--t-secondary); cursor:pointer; padding:var(--s-2); border-radius:var(--r-sm); }
.weekfilter__opt:hover { background:var(--line-soft); }
.weekfilter__clear { align-self:flex-start; font-size:var(--t-caption); font-weight:600; color:var(--brand-ink); padding:var(--s-2); }
.weekfilter__clear:hover { text-decoration:underline; }

/* Overdue attention item — a white card, same family as the Inbox rows,
   so the panel reads as one set. A left accent bar carries the urgency colour
   (design pass, 2026-08-15): burgundy for overdue, gold for waiting, deepening
   to burgundy once a waiting card has gone twice as long past the stale fuse. */
.attn__item { position:relative; background:var(--surface); border:1px solid var(--band-warm-line); border-left-width:3px; border-radius:var(--r); padding:var(--s-5); margin-bottom:var(--s-4); transition:box-shadow .12s; }
.attn__item:hover { box-shadow:var(--shadow-card); }
.attn__item--overdue { border-left-color:var(--brand); }
.attn__item--waiting { border-left-color:var(--gold); }
.attn__item--waiting-urgent { border-left-color:var(--brand); }
.attn__row { display:flex; align-items:center; justify-content:space-between; gap:var(--s-5); width:100%; text-align:left; }
.attn__row .t { font-weight:500; font-size:var(--t-body); color:var(--ink); overflow:hidden; text-overflow:ellipsis; }
.attn__row .meta { font-size:var(--t-caption); font-weight:600; color:var(--muted); white-space:nowrap; flex:none; }
.attn__row .meta--overdue { color:var(--brand-ink); }
/* Waiting row: title + urgency pill, a "who" line, a coloured "when" line — a
 * fuller card than the plain overdue row, since two different reasons a
 * waiting card surfaces (review due vs gone quiet) need to read at a glance. */
.attn__row--waiting { flex-direction:column; align-items:stretch; gap:var(--s-4); }
.attn__row-top { display:flex; align-items:flex-start; justify-content:space-between; gap:var(--s-5); width:100%; }
.attn__row-badges { display:flex; align-items:center; gap:var(--s-4); flex:none; }
.attn__urg { flex:none; font-size:var(--t-micro); font-weight:700; letter-spacing:.05em; text-transform:uppercase; border-radius:var(--r-pill); padding:var(--s-1) var(--s-4); white-space:nowrap; }
.attn__urg--review { background:var(--blush-bg); color:var(--brand-ink); }
.attn__urg--quiet { background:var(--gold-band); color:var(--gold-ink); }
.attn__who { display:flex; align-items:center; gap:var(--s-3); font-size:var(--t-secondary); color:var(--muted); min-width:0; }
.attn__who .ico { font-size:12px; flex:none; }
.attn__who span:last-child { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
/* .attn__row-top .t has overflow:hidden but, unlike its siblings, no
   white-space:nowrap — so text-overflow:ellipsis (which only ever applies to
   single-line, nowrap text) was doing nothing, and the title just wrapped.
   Because overflow:hidden also drops a flex item's automatic min-width to 0,
   the title had no floor to stop it shrinking against .attn__row-badges
   (id + urgency pill, flex:none): at 375px it was confirmed squeezed down to
   a sliver narrow enough that the page's global overflow-wrap:break-word
   (app.css, body) was splitting whole words mid-letter ("Contribut" /
   "or"). Wrapping the row and giving the title the full width fixes it —
   the badges drop to their own line underneath instead of fighting the
   title for the same one. */
@media (max-width: 640px) {
  .attn__row-top { flex-wrap:wrap; row-gap:var(--s-3); }
  .attn__row-top .t { flex-basis:100%; white-space:normal; }
}
/* action toolbar — small secondary buttons, right-aligned (David, 2026-09-08). */
.attn__acts { display:flex; gap:var(--s-3); align-items:center; justify-content:flex-end; margin-top:var(--s-4); flex-wrap:wrap; }
.attn__acts form { margin:0; }
/* line-height is STATED, not inherited. Every shared pill class in this file is
   worn by both a <button> and a <summary>/<a>: a button's UA rule is
   line-height:normal (~1.16), everything else inherits body's 1.6, so the two
   render 2.5-3.7px apart wherever they sit side by side (Reschedule/Done here,
   Edit/Archive on Projects, the ··· menu, the waiting strip). One explicit
   value on the class is the whole fix — 1.6 rather than something tighter, so
   the taller element keeps the height it already had and only the button moves. */
.attn__act {
  display:inline-flex; align-items:center; gap:var(--s-2); cursor:pointer; list-style:none; line-height:1.6;
  font-size:var(--t-caption); font-weight:600; color:var(--muted);
  background:transparent; border:1px solid var(--line-strong); border-radius:var(--r-sm); padding:var(--s-2) var(--s-4);
  transition:color .12s, border-color .12s, background .12s;
}
.attn__act::-webkit-details-marker { display:none; }
.attn__act .ico { font-size:13px; }
.attn__act:hover { color:var(--brand-ink); border-color:var(--brand-ink); background:var(--brand-tint); }
.attn__act--done:hover { color:var(--ok); border-color:var(--ok); background:rgba(31,138,91,.08); }
/* Reschedule/Complete/Chased/Resume are real state-changing actions, not
   decoration — desktop's compact var(--s-2) var(--s-4) padding measured
   ~78×28px live at 375px. .attn__acts already wraps (flex-wrap:wrap), so
   there is room to grow these without a layout rewrite. Placed after the
   base rule above (same specificity, so source order decides the winner —
   an earlier attempt above the base rule was silently overridden by it). */
@media (max-width: 640px) {
  .attn__act { padding:var(--s-4) var(--s-5); }
}
/* The inline-toolbar variant of the quick-schedule popover lives with .qsched
   itself now, as .qsched--inline — see the board section below. */
/* Overdue/Waiting became two separate panels (David, 2026-09-08), each
   stating its own true total in the band; this is the "there's more" line
   that only appears in the body once the list is actually truncated below
   that total — same wording family as .seclink, its own line rather than
   squeezed into a heading that no longer exists here. */
.attn__seeall { display:block; margin-top:var(--s-4); font-size:var(--t-caption); font-weight:600; color:var(--brand-ink); text-align:right; }
.attn__seeall:hover { text-decoration:underline; }

/* Unmatched events — Create card / Drop, two actions only (David, 2026-09-07).
   Same card shape as .attn__item (left accent, same padding/radius/margin) so
   the three cockpit "attention" panels read as one family (David, 2026-09-08). */
.inboxrow { display:flex; flex-direction:column; gap:var(--s-3); background:var(--surface); border:1px solid var(--band-cool-line); border-left-width:3px; border-left-color:var(--navy-ink); border-radius:var(--r); padding:var(--s-5); margin-bottom:var(--s-4); }
.inboxrow__top { display:flex; align-items:center; justify-content:space-between; gap:var(--s-4); }
.inboxrow__title { font-size:var(--t-body); color:var(--ink); }
.inboxrow__bottom { display:flex; align-items:center; justify-content:space-between; gap:var(--s-4); flex-wrap:wrap; }
.inboxrow__when { font-size:var(--t-caption); color:var(--muted); }
.inboxrow__actions { display:flex; align-items:center; gap:var(--s-4); }
.inboxrow__actions form { margin:0; }
.inboxrow__actions .btn { padding:var(--s-3) var(--s-5); font-size:var(--t-secondary); }
.inboxfoot { font-size:var(--t-secondary); color:var(--band-cool-ink); margin:var(--s-6) 0 0; line-height:1.55; }

/* ------------------------------------------------------------------ board */
.filterbar {
  display:flex; align-items:center; gap:var(--s-5); flex-wrap:wrap; margin-top:var(--s-5);
  background:var(--surface); border:1px solid var(--line); border-radius:var(--r-lg); padding:var(--s-5); box-shadow:var(--shadow-card);
}
/* An explicit height, because a <select> and an <input> given identical padding
   and font still land ~1-2px apart on their own UA metrics — which is exactly
   the pair that sits side by side in every filter bar. */
.filterbar select, .filterbar input, .searchpage__filterform select {
  font-size:var(--t-ui); color:var(--ink); border:1px solid var(--line-strong); border-radius:var(--r); padding:var(--s-4) var(--s-5); background:var(--surface); height:38px;
}
.filterbar select, .searchpage__filterform select { cursor:pointer; min-width:190px; }
/* Everything above except the height, which matches the 52px search input it
   stands beside on the full Search page. */
.searchpage__filterform select { height:52px; border-radius:var(--r-lg); }
/* "Unscheduled only" (David, 2026-09-08 — folded the dedicated Unscheduled
 * page into Search's own filter bar; then, "the unscheduled shouldn't be a
 * separate checkbox, it should be the same ui as the others and should be a
 * multi select" — a genuine dropdown now, not a checkbox styled to match a
 * dropdown's box dimensions from the outside). Same <details>+popover shape
 * as the Cockpit's own project filter (.weekfilter), sized to this page's own
 * 52px field family instead of .weekfilter's compact toolbar one. */
.searchfilter { position:relative; }
.searchfilter__btn {
  list-style:none; display:inline-flex; align-items:center; gap:var(--s-3); height:52px; padding:0 var(--s-5);
  border:1px solid var(--line-strong); border-radius:var(--r-lg); background:var(--surface);
  font-size:var(--t-ui); color:var(--ink); cursor:pointer; white-space:nowrap; user-select:none;
}
.searchfilter__btn::-webkit-details-marker { display:none; }
.searchfilter__btn .ico { width:14px; height:14px; color:var(--faint); }
.searchfilter:has(input:checked) .searchfilter__btn { border-color:var(--brand); background:var(--brand-tint); color:var(--brand-ink); }
.searchfilter:has(input:checked) .searchfilter__btn .ico { color:var(--brand-ink); }
.searchfilter__pop {
  position:absolute; top:calc(100% + var(--s-2)); left:0; z-index:15; width:max-content; min-width:200px;
  display:flex; flex-direction:column; gap:var(--s-2);
  background:var(--overlay); border:1px solid var(--line); border-radius:var(--r-lg); box-shadow:var(--shadow-popover); padding:var(--s-4);
}
.searchfilter__opt { display:flex; align-items:center; gap:var(--s-3); font-size:var(--t-secondary); cursor:pointer; padding:var(--s-2); border-radius:var(--r-sm); white-space:nowrap; }
.searchfilter__opt:hover { background:var(--line-soft); }
.filterbar input { flex:1; min-width:220px; }
.filterbar .clear { color:var(--brand-ink); font-weight:600; font-size:var(--t-ui); padding:var(--s-4) var(--s-3); }
/* The project drilldown page's open/waiting list — its only user now that the
 * Board is gone. A single narrow column (David, 2026-09-07 screenshot: "this
 * page is inbalanced... cards should be full width or in columns") left most
 * of a wide page empty; a two-column grid uses the space the way the cockpit's
 * own "This week" card grid already does, rather than introducing a new
 * layout idea. */
/* align-items:stretch (the default, stated), not start: top-aligned cards of
   different heights stepped their project chips down the column. The cockpit's
   own "This week" grid had the opposite half of the same bug — it stretched but
   let the chip float — so both grids are fixed the same way now: stretch the
   row, then pin the chip to the bottom of its own card (.bcard__meta /
   .weekrow > .chip--proj). Two grids, one behaviour. */
.backlog { display:grid; grid-template-columns:repeat(2, 1fr); gap:var(--s-4) var(--s-5); margin-top:var(--s-7); align-items:stretch; }
.backlog > .bcard-wrap { display:flex; flex-direction:column; }
@media (max-width: 700px) { .backlog { grid-template-columns:1fr; } }

/* Card + its hover quick-schedule control. The wrapper lifts above sibling
   cards when the popover is open so it is never clipped. */
.bcard-wrap { position:relative; }
.bcard-wrap:has(.qsched[open]) { z-index:20; }
.qsched { position:absolute; top:7px; right:7px; }
/* The same popover, but sitting in an ordinary action toolbar rather than a
   card's hover corner (the Needs-attention row's Reschedule, the waiting
   strip's Mark chased). Written as a MODIFIER, not as a per-context descendant
   selector: the descendant version only ever covered .attn__acts, so the
   waiting strip — added later, reusing the same .qsched markup — inherited the
   absolute positioning and threw its Mark chased button to the top-right
   corner of the page, outside the card modal entirely. A modifier travels with
   the markup, so the next place that reuses this cannot silently miss it.
   The popover is right-anchored to the TOOLBAR it sits in, not to its own
   trigger: measured live, anchoring to the trigger pushed the 236px panel
   ~100px past the left edge of the (overflow:hidden) Needs-attention panel and
   clipped it. The toolbar is the widest thing that is still guaranteed to be
   inside the panel, so that is the containing block. */
.qsched--inline { position:static; top:auto; right:auto; }
.qsched--inline > .qsched__pop { top:calc(100% + var(--s-2)); left:auto; right:0; }
.attn__acts, .waitstrip__acts { position:relative; }
.qsched__btn {
  list-style:none; cursor:pointer; width:26px; height:26px; border-radius:var(--r-sm);
  display:flex; align-items:center; justify-content:center; font-size:var(--ico);
  color:var(--faint); background:color-mix(in srgb, var(--surface) 72%, transparent); opacity:0; transition:opacity .12s, color .12s, background .12s;
}
.qsched__btn::-webkit-details-marker { display:none; }
.bcard-wrap:hover .qsched__btn, .qsched[open] .qsched__btn { opacity:1; }
.qsched__btn:hover, .qsched[open] .qsched__btn { color:var(--brand-ink); background:var(--surface); }
/* Offset and shadow match every other popover in the app (.cardmenu__panel,
   .projform, the due-date editor): --shadow-popover, not the flatter
   --shadow-panel meant for large static panels, and an offset measured from
   the trigger rather than a hardcoded 32px that drifts if the trigger's own
   size ever changes. */
.qsched__pop {
  position:absolute; top:calc(100% + var(--s-3)); right:0; z-index:20; width:236px;
  background:var(--overlay); border:1px solid var(--line); border-radius:var(--r-lg); box-shadow:var(--shadow-popover); padding:var(--s-5);
}
.qsched__pop form { display:flex; flex-direction:column; gap:var(--s-4); }
.qsched__lbl { font-size:var(--t-caption); font-weight:600; color:var(--muted); }
.qsched__row { display:flex; gap:var(--s-4); align-items:center; }
.qsched__row input, .qsched__row select {
  flex:1 1 0; min-width:0; font-size:var(--t-secondary); color:var(--ink); background:var(--surface);
  border:1px solid var(--line-strong); border-radius:var(--r-sm); padding:var(--s-3) var(--s-4);
}
.qsched__row .btn { flex:0 0 auto; padding:var(--s-3) var(--s-5); font-size:var(--t-secondary); }

/* A flex column (an <a> is inline by default, so it needs an explicit display
   or it ignores width:100% and the cards collapse) — the column is what lets
   .bcard__meta take margin-top:auto and sit on the card's floor however tall
   the title runs. */
.bcard { display:flex; flex-direction:column; flex:1 1 auto; text-align:left; width:100%; background:var(--surface); border:1px solid var(--line); border-radius:var(--r); padding:var(--s-5); transition:box-shadow .13s, transform .13s; }
.bcard:hover { box-shadow:var(--shadow-card); transform:translateY(-1px); }
.bcard--overdue { background:var(--warm-row-bg); border-color:var(--warm-row-line); }
.bcard--waiting { background:var(--raised); }
/* padding-right clears the hover schedule button (.qsched, top:7px/right:7px
   on the wrapping .bcard-wrap) so the id doesn't sit under it once it fades
   in — confirmed live: the two shared a 20px horizontal band with no gap. */
.bcard__head { display:flex; align-items:flex-start; gap:var(--s-3); margin-bottom:var(--s-4); padding-right:var(--s-8); }
/* The card grows with its title, but never runs away: long titles clamp at
   three lines. Content-responsive, not massive. min-width:0 lets it actually
   shrink inside the flex row above rather than pushing the id off the card. */
.bcard__title {
  display:-webkit-box; -webkit-box-orient:vertical; -webkit-line-clamp:3; line-clamp:3;
  overflow:hidden; font-weight:500; font-size:var(--t-body); color:var(--ink); line-height:1.35;
  flex:1 1 auto; min-width:0;
}
.bcard__meta { display:flex; align-items:center; justify-content:space-between; gap:var(--s-4); min-width:0; margin-top:auto; }
/* The project chip yields before the date chip does, but gets real room first. */
.bcard__meta > .chip--proj { flex:0 1 auto; min-width:0; }
.bcard__meta .r { display:flex; align-items:center; gap:var(--s-2); flex:none; }

/* project chip is colour-per-project → colours come inline; this is the shape.
   Padding/border match .chip exactly (David, 2026-08-15: chips of different
   families sit side by side — status, due, project — and a bare 2px/no-border
   chip next to a bordered 3px one reads 4px shorter, visibly uneven). */
.chip--proj { font-size:var(--t-micro); font-weight:700; letter-spacing:.05em; text-transform:uppercase; border-radius:var(--r-pill); padding:var(--s-2) var(--s-5); border:1px solid transparent; max-width:100%; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; display:inline-block; }
/* Per-project colour. The server cannot know the theme (it is chosen in the
   browser before first paint), so a chip carries BOTH pairs as custom
   properties — --pc-bg/--pc-ink for light, --pc-bg-d/--pc-ink-d for dark — and
   these rules pick. See render.ts's chipVars(). The dark override lives with
   the rest of the dark block at the foot of this file. */
.chip--proj { background:var(--pc-bg); color:var(--pc-ink); }
.swpick { background:var(--pc-bg); color:var(--pc-ink); }
/* A tag is navigation (David, 2026-08-06): cursor + a subtle lift on hover/focus
   signal it is clickable, without touching the per-project inline bg/color. */
.chip--proj-link { cursor:pointer; transition:filter .12s, box-shadow .12s; }
/* No hover treatment (David: every tag type's hover — including the due
   date's — looked different and all of them read as bad; removed outright
   rather than picking one to keep). Keyboard focus keeps its own outline —
   that's accessibility, not a hover style, and stays on every editable/
   clickable element regardless of this decision. */
.chip--proj-link:focus-visible { outline:2px solid var(--focus); outline-offset:2px; }

/* ------------------------------------------------------------------ archive */
/* Padded to the same 16px inset as the .archrow rows it counts — the rows' own
   padding is invisible (transparent background), so an unpadded count line
   above them read as outdented. Same fix as .listsec > .secrow. */
.archcount { margin:var(--s-7) 0 var(--s-4); padding:0 var(--s-6); font-weight:700; font-size:var(--t-micro); letter-spacing:.13em; text-transform:uppercase; color:var(--muted); }
.archlist { display:flex; flex-direction:column; gap:var(--s-4); }
/* One card per entry — the clickable row on top, an optional non-clickable
   outcome/reason block underneath (design pass, 2026-08-15): a logged drop
   reason is prose, so it earns its own block instead of crowding the title. */
.archcard { background:var(--surface); border:1px solid var(--line); border-radius:var(--r-lg); overflow:hidden; transition:box-shadow .12s, border-color .12s; }
.archcard:hover { box-shadow:var(--shadow-card); border-color:var(--band-cool-line); }
/* A backlog row's quick-schedule popover (.qsched__pop) drops below its own
   .bcard-wrap, well past the archcard's own box — the same popover the Board's
   .bcard tiles already use uncontained. archcard's overflow:hidden exists to
   clip a bordered card's own square corners to its border-radius; it was never
   meant to clip a popover escaping the card entirely, so this card in
   particular needs it off, and needs to rise above the cards below it while
   the popover is open (same pairing .bcard-wrap already does for itself). */
.archcard:has(.qsched) { overflow:visible; }
.archcard:has(.qsched[open]) { position:relative; z-index:20; }
/* Search's own plain rows have no outer margin (a shared border-bottom does
 * the separating) — but an .archcard sitting among them needs its own, or
 * its border touches its neighbours' with no breathing room at all. Vertical
 * only: a horizontal margin here (the original value, before David: "The
 * width alignment on this page is off. It should all be aligned") indented
 * every card 12px past the search bar and status tabs above them, which sit
 * flush against main.page's own padding with none of their own — the cards
 * now match that same left/right edge instead of a second, narrower one. */
.srch__panel .archcard { margin:var(--s-4) 0; }
.srch__panel .archcard .srch__row { border-bottom:none; }
.archrow { display:flex; align-items:center; justify-content:space-between; gap:var(--s-6); padding:var(--s-5) var(--s-6); }
.archrow__main { display:flex; flex-direction:column; align-items:flex-start; gap:var(--s-3); min-width:0; }
.archrow__title { font-weight:500; font-size:var(--t-body); color:var(--ink); }
.archrow__right { display:flex; align-items:center; gap:var(--s-5); flex:none; }
/* A fixed date column, so the status pill in front of it stops wobbling ~9px
   from row to row. font-variant-numeric alone could not do this: the vendored
   DM Sans subset carries no `tnum` feature, so tabular-nums renders identically
   to lining figures (measured). It stays declared — it is the right intent if
   the font ever gains the feature — but the min-width is what actually holds
   the column.
   80px is the font's own worst case, not the widest date in today's data: at
   13px this face runs "1" at 4.06px and "0" at 8.89px, so a DD/MM/YYYY can
   reach 79.78px. Sized to the data instead (74px) the column still drifted
   0.84px, and would drift further the day a wider date arrived. */
.archrow__when { font-size:var(--t-secondary); color:var(--faint); white-space:nowrap; font-variant-numeric:tabular-nums; min-width:80px; text-align:right; }
.archrow__outcome { display:flex; align-items:flex-start; gap:var(--s-5); margin:0 var(--s-6) var(--s-5); padding:var(--s-5); border-radius:var(--r); background:var(--done-block-bg); border:1px solid var(--done-block-line); }
.archrow__outcome--dropped { background:var(--warm-row-bg); border-color:var(--warm-row-line); }
.archrow__outcome-lbl { flex:none; font-size:var(--t-micro); font-weight:700; letter-spacing:.09em; text-transform:uppercase; color:var(--muted); padding-top:var(--s-1); }
.archrow__outcome--dropped .archrow__outcome-lbl { color:var(--brand-ink); }
.archrow__outcome-text { flex:1 1 auto; min-width:0; font-size:var(--t-ui); line-height:1.6; color:var(--ink); }
@media (max-width: 640px) {
  .archrow { flex-direction:column; align-items:flex-start; gap:var(--s-4); }
  .archrow__right { width:100%; justify-content:space-between; }
}

/* ------------------------------------------------------------------ modal */
.modal-scrim { position:fixed; inset:0; background:transparent; z-index:40; display:flex; align-items:center; justify-content:center; padding:44px var(--s-8); overflow-y:auto; }
.modal { width:min(820px,96vw); max-width:100%; max-height:calc(100vh - 88px); background:var(--overlay); border:1px solid var(--line); border-radius:var(--r-lg); box-shadow:var(--shadow-modal); display:flex; flex-direction:column; overflow:hidden; }
/* The panel carries a view-transition-name so its open, in-place re-render (e.g.
   adding a session) and close each morph smoothly rather than re-animating from
   scratch or jumping. The dark backdrop is the separate .scrim layer above. */
.modal { view-transition-name:cardmodal; }
.modal-scrim.full { align-items:stretch; padding:0; }
.modal-scrim.full .modal { width:100%; max-height:100vh; border-radius:0; }
/* Toolbar background is a shade off the body (design pass, 2026-08-21) — the
   persistent action row now reads as its own strip, not just a title-bar. */
/* The left inset matches .modal__body's, not its own smaller one: "CARD #108"
   sits directly above the card title, and at 20px against the body's 32px the
   two read as two different left edges stacked on top of each other. The right
   stays tighter — the icon buttons there carry their own padding. */
.modal__head { position:relative; display:flex; align-items:center; justify-content:space-between; padding:var(--s-5) var(--s-6) var(--s-5) 32px; border-bottom:1px solid var(--line-soft); flex:none; background:var(--bg); }
.modal__head .lbl { font-size:var(--t-micro); font-weight:700; letter-spacing:.14em; text-transform:uppercase; color:var(--faint); }
/* The card id, so a misbehaving card can be quoted by number. Selectable and
   slightly darker than the label so it reads as data rather than decoration. */
.modal__id { color:var(--muted); user-select:all; font-variant-numeric:tabular-nums; }
.modal__head .tools { display:flex; align-items:center; gap:var(--s-2); }
/* The one save indicator for the whole open card lives in a bar along the
 * bottom of the modal (David, 2026-10-02). History: 2026-09-09 "a save jumps
 * up to the right of the date and goes away again... should run in one place
 * in the ticket and not push the ui around" -> centred in the header; then
 * "appears terrible" -> anchored beside the title, where the Saved state still
 * dropped out of line (its toastIn animation ends on transform:none and, with
 * fill-mode both, overrode the translateY that kept it level). A header slot
 * was always going to fight the title and toolbar, so it now has a bar of its
 * own: fixed height, status right-aligned, so showing or clearing it can never
 * move anything. Only rendered for an editable card (see modalShell's `foot`).
 * The is-saving/is-saved states are in the motion section below. */
.modal__foot { flex:none; display:flex; align-items:center; justify-content:flex-end; height:38px; padding:0 var(--s-6) 0 32px; border-top:1px solid var(--line-soft); background:var(--bg); }
.modal__savestatus { white-space:nowrap; pointer-events:none; }
.iconbtn { background:transparent; border:none; color:var(--muted); font-size:var(--ico); line-height:1; cursor:pointer; padding:var(--s-3) var(--s-4); border-radius:var(--r); }
.iconbtn.x { font-size:var(--ico-lg); padding:var(--s-2) var(--s-4); }
.iconbtn:hover { color:var(--heading); background:var(--line-soft); }
.modal__body { overflow-y:auto; padding:28px 32px 44px; }

/* ---- new card form ---- */
.newcard { display:flex; flex-direction:column; gap:var(--s-6); }
.newcard__field { display:flex; flex-direction:column; gap:var(--s-3); }
.newcard__label { font-size:var(--t-caption); font-weight:600; letter-spacing:.02em; color:var(--muted); }
.newcard__opt { font-weight:500; color:var(--faint); text-transform:none; letter-spacing:0; }
.newcard input[type="text"], .newcard input[type="date"], .newcard input[type="time"], .newcard textarea, .newcard select {
  width:100%; font-family:var(--font-body); font-size:var(--t-body); color:var(--ink); background:var(--surface);
  border:1px solid var(--line-strong); border-radius:var(--r); padding:var(--s-4) var(--s-5);
  transition:border-color .12s ease, box-shadow .12s ease;
}
/* "When" — the promoted, first-class scheduling row: date · time · length. */
.newcard__when { background:var(--bg); border:1px solid var(--line-soft); border-radius:var(--r-lg); padding:var(--s-5); gap:var(--s-4); }
.newcard__when-row { display:flex; gap:var(--s-4); align-items:center; flex-wrap:wrap; }
.newcard__when-row input[type="date"] { flex:2 1 150px; }
.newcard__when-row input[type="time"] { flex:1 1 110px; }
.newcard__when-row select { flex:1 1 120px; width:auto; }
.newcard input:focus, .newcard textarea:focus, .newcard select:focus {
  outline:none; border-color:var(--focus); box-shadow:0 0 0 3px var(--brand-tint);
}
.newcard__title { font-family:var(--font-head); font-weight:600; font-size:var(--t-title); }
.newcard textarea { line-height:1.5; }
.newcard__row { display:flex; gap:var(--s-6); }
.newcard__row .newcard__field { flex:1 1 0; min-width:0; }
.newcard__sched { border:1px solid var(--line-soft); border-radius:var(--r-lg); padding:var(--s-5); background:var(--bg); }
.newcard__sched > summary { cursor:pointer; font-weight:600; font-size:var(--t-ui); color:var(--navy-ink); list-style:none; }
.newcard__sched > summary::-webkit-details-marker { display:none; }
.newcard__sched > summary::before { content:"＋"; margin-right:var(--s-4); color:var(--brand-ink); font-weight:600; }
.newcard__sched[open] > summary::before { content:"－"; }
.newcard__actions { display:flex; align-items:center; gap:var(--s-5); margin-top:var(--s-2); }
@media (max-width: 520px) { .newcard__row { flex-direction:column; } }

/* The "book a session" sub-form uses the compact scheduler field size — one
   uniform height and width:auto — so Date, Start, Length and Show as line up
   instead of Date and the selects towering over Start. Higher specificity than
   the generic .newcard field rules above, so it wins. */
.newcard .newcard__sched input,
.newcard .newcard__sched select {
  width:auto; font-size:var(--t-ui); padding:var(--s-4); border-radius:var(--r);
  height:40px; box-sizing:border-box;
}
.newcard .newcard__sched .dsess__namefield { flex-basis:100%; }
.newcard .newcard__sched .dsess__namefield input { width:100%; }
.newcard .newcard__sched .dsess__form { gap:var(--s-5); align-items:flex-end; }
.newcard .newcard__sched .dsess__form label { font-size:var(--t-caption); color:var(--muted); gap:var(--s-3); }

.detail__title { font-family:var(--font-head); font-weight:600; font-size:var(--t-h2); line-height:1.25; color:var(--heading); margin:0; letter-spacing:.2px; }
.detail__head { margin:0 0 var(--s-6); }
/* Status, due, project tags and derived umbrella tags all read as ONE actual
 * row now (David: two separate passes at "tighter spacing" were still not a
 * good use of space for the top of a card). .badges/.cardprojects/.cardumbrellas
 * stay as real elements — History-section tests and the ··· menu both still
 * address them by name/id — but `display:contents` takes them out of layout
 * entirely, so their children become direct items of .cardheader's own flex
 * row instead of three stacked boxes each claiming a full line.
 *
 * The one element that still needs to be able to claim a full line on its
 * own is the project tag-picker (`#f-proj-:id`, swapped in when you edit
 * projects — search box, recently-used, a whole small form, not a chip): the
 * 2026-09-07 fix that gave it `.projform{width:100%}` only ever worked
 * because it had a row to itself. Losing that reintroduces exactly the
 * "orphaned chip" bug from that fix — a sibling chip doesn't reflow with a
 * growing box unless the row is actually allowed to WRAP. Fixed properly this
 * time: `flex-wrap` on the shared row, and `flex-basis:100%` on the picker's
 * own wrapper (only while it IS the picker, via :has) — which is what
 * actually forces a flex-wrap line break, not `width:100%` alone. */
.cardheader { display:flex; flex-wrap:wrap; align-items:center; gap:var(--s-4); margin:0 0 var(--s-6); }
.badges, .cardprojects, .cardumbrellas { display:contents; }
/* Editing the due date or the project set used to force its own full-width
 * row, which shoved every chip after it down onto a new line the instant you
 * opened either editor (David, 2026-09-07 screenshot: "sends the tags all
 * over the place"). Both now float as a popover instead — the trigger's own
 * box (min-height keeps it from collapsing to 0 once its child goes absolute)
 * stays exactly where it was in the row, and everything after it never moves.
 * Matches the `.qsched`/`.cardmenu` popover already used elsewhere. */
.cardheader [id^="f-proj-"], .cardheader [id^="f-due-"] { position:relative; min-width:0; min-height:24px; }
.cardheader [id^="f-proj-"]:has(.projform), .cardheader [id^="f-due-"]:has(form) { z-index:15; }
.cardheader [id^="f-due-"] form.fieldform--inline {
  position:absolute; top:calc(100% + 6px); left:0; z-index:15; width:max-content; max-width:min(300px, 90vw);
  background:var(--overlay); border:1px solid var(--line); border-radius:var(--r-lg);
  box-shadow:var(--shadow-popover); padding:var(--s-5); margin:0;
}
.flagnote { background:var(--warm-row-bg); border:1px solid var(--brand-line); border-left:3px solid var(--brand); border-radius:var(--r); padding:var(--s-5) var(--s-6); margin-bottom:var(--s-7); }
.flagnote b { display:block; font-weight:600; font-size:var(--t-ui); color:var(--brand-ink); margin-bottom:var(--s-2); }
.flagnote span { display:block; font-size:var(--t-secondary); color:var(--warm-muted); line-height:1.5; }
/* Terminal-card strip (design pass, 2026-08-15): left-accent border, the
   Reopen button pinned top-right beside the headline, and — when there is one
   — the outcome/reason below a divider, matching the archive card's own
   outcome-block treatment (visual consistency between the two retrieval views). */
.terminal { border-radius:var(--r-lg); padding:var(--s-6); margin-bottom:var(--s-8); border-left-width:3px; }
.terminal--done { background:var(--done-block-bg); border:1px solid var(--done-block-line); border-left-color:var(--invert-bg); }
.terminal--dropped { background:var(--warm-row-bg); border:1px solid var(--warm-row-line); border-left-color:var(--brand); }
.terminal__top { display:flex; align-items:flex-start; justify-content:space-between; gap:var(--s-6); flex-wrap:wrap; }
.terminal__head { font-family:var(--font-head); font-weight:600; font-size:var(--t-body); color:var(--heading); margin:0 0 var(--s-2); }
.terminal__when { font-size:var(--t-secondary); color:var(--muted); margin:0; }
.terminal__reopen { flex:none; }
.terminal__outcome { display:flex; align-items:flex-start; gap:var(--s-5); margin-top:var(--s-5); padding-top:var(--s-5); border-top:1px solid var(--line); }
.terminal--dropped .terminal__outcome { border-top-color:var(--warm-row-line); }
.terminal__outcome-lbl { flex:none; font-size:var(--t-micro); font-weight:700; letter-spacing:.09em; text-transform:uppercase; padding-top:var(--s-1); color:var(--heading); }
.terminal--dropped .terminal__outcome-lbl { color:var(--brand-ink); }
.terminal__outcome-text { flex:1 1 auto; min-width:0; font-size:var(--t-ui); line-height:1.6; color:var(--ink); }

/* Waiting-state strip (design pass, 2026-08-15) — a non-terminal card parked on
   someone else gets its own gold-family banner above the description, same
   family as the terminal strip but distinct so the two states never look alike. */
.waitstrip { background:var(--gold-tint); border:1px solid var(--gold-line); border-left:3px solid var(--gold); border-radius:var(--r-lg); padding:var(--s-6); margin-bottom:var(--s-8); }
.waitstrip__top { display:flex; align-items:flex-start; justify-content:space-between; gap:var(--s-6); flex-wrap:wrap; }
.waitstrip__label { display:flex; align-items:center; gap:var(--s-4); font-size:var(--t-micro); font-weight:700; letter-spacing:.12em; text-transform:uppercase; color:var(--gold-ink); margin:0 0 var(--s-3); }
.waitstrip__on { font-size:var(--t-body); color:var(--ink); margin:0 0 var(--s-2); line-height:1.5; }
.waitstrip__meta { font-size:var(--t-secondary); color:var(--gold-muted); margin:0; }
.waitstrip__acts { display:flex; align-items:center; gap:var(--s-3); flex:none; }
/* Mark chased is a <summary> (it opens the review-date popover), Resume a
   <button> — so as well as the stated line-height (see .attn__act) the chase
   carries a transparent border, or the resume's own 1px one would leave the
   pair 2px apart even once their text boxes matched. */
.waitstrip__chase { display:inline-flex; align-items:center; gap:var(--s-3); background:var(--gold-fill); border:1px solid transparent; color:#fff; font-size:var(--t-secondary); line-height:1.6; font-weight:600; padding:var(--s-4) var(--s-6); border-radius:var(--r); cursor:pointer; white-space:nowrap; list-style:none; transition:background .14s; }
.waitstrip__chase::-webkit-details-marker { display:none; }
.waitstrip__chase:hover { background:var(--gold-fill-hover); }
.waitstrip__resume { display:inline-flex; align-items:center; gap:var(--s-3); background:var(--surface); border:1px solid var(--gold-line); color:var(--gold-ink); font-size:var(--t-secondary); line-height:1.6; font-weight:600; padding:var(--s-4) var(--s-6); border-radius:var(--r); cursor:pointer; white-space:nowrap; transition:background .14s; }
.waitstrip__resume:hover { background:var(--gold-tint); }
.waitstrip__foot { font-size:var(--t-caption); color:var(--gold-muted); margin:var(--s-5) 0 0; padding-top:var(--s-5); border-top:1px solid var(--gold-line); line-height:1.55; }
/* ---- card actions menu (design pass, 2026-08-21: lives in the toolbar) ---- */
/* Complete / Set waiting / Drop are terminal or near-terminal, so they sit
   behind a menu rather than under the title where Complete was one stray click
   away. Built from native <details>, so it works with JavaScript off. */
.cardmenu { position:relative; flex:none; }
.cardmenu > summary {
  list-style:none; cursor:pointer; user-select:none; display:flex; align-items:center; gap:var(--s-3);
  color:var(--muted); font-size:var(--t-secondary); font-weight:600; line-height:1;
  padding:var(--s-3) var(--s-4); border-radius:var(--r);
  transition:background .12s, color .12s;
}
.cardmenu > summary::before { content:"More"; }
.cardmenu > summary .ico { font-size:15px; }
.cardmenu > summary::-webkit-details-marker { display:none; }
.cardmenu > summary:hover, .cardmenu[open] > summary { background:var(--line-soft); color:var(--heading); }
.cardmenu__panel {
  position:absolute; right:0; top:calc(100% + 6px); z-index:5;
  display:grid; grid-template-columns:repeat(auto-fit, minmax(min(100%,220px), 1fr)); gap:var(--s-5);
  width:max-content; max-width:min(490px, 90vw);
  background:var(--overlay); border:1px solid var(--line); border-radius:var(--r-lg);
  box-shadow:var(--shadow-popover); padding:var(--s-5); overflow:hidden;
}
/* A trigger near the bottom of a long page (the Projects manager's own list,
 * David — "the new menu UI is dropping off the bottom of the page") opens the
 * panel below the fold, unreachable without first scrolling past it. JS
 * (render.ts) measures on open and adds this class when the panel would
 * overflow the viewport, flipping it to grow upward from the trigger instead
 * — the one thing pure CSS can't do without a fixed height to react to. */
.cardmenu__panel--up { top:auto; bottom:calc(100% + 6px); }
.cardmenu__group { min-width:0; }
.cardmenu__grouplbl { display:block; font-size:var(--t-micro); font-weight:700; letter-spacing:.13em; text-transform:uppercase; color:var(--muted); margin-bottom:var(--s-3); }
.cardmenu__grouplist { display:flex; flex-direction:column; gap:var(--s-2); }
.cardmenu__panel form { margin:0; }
/* Complete/Resume/Change projects are <button>s, Drop and Mark chased are
   <summary>s — line-height stated so every row in the menu is one height
   (see .attn__act). */
.cardmenu__item {
  display:flex; align-items:center; gap:var(--s-4); width:100%; text-align:left; cursor:pointer; line-height:1.6;
  font-family:inherit; font-size:var(--t-secondary); font-weight:500; color:var(--ink);
  background:var(--surface); border:1px solid var(--line); border-radius:var(--r); padding:var(--s-4) var(--s-5); list-style:none;
}
.cardmenu__item .ico { font-size:13px; flex:none; }
.cardmenu__item::-webkit-details-marker { display:none; }
.cardmenu__item:hover { background:var(--bg); border-color:var(--line-strong); }
.cardmenu__item--danger { color:var(--brand-ink); border-color:var(--warm-row-line); }
.cardmenu__item--danger:hover { background:var(--brand-tint); border-color:var(--brand-line); }
.cardmenu__drop[open] > .cardmenu__item--danger { background:var(--brand-tint); }
.cardmenu__drop .dropbox { margin:var(--s-3) 0 var(--s-1); padding:var(--s-5); }
.cardmenu__drop .dropbox textarea { font-size:var(--t-secondary); }
.cardmenu__drop .dropbox__hint { font-size:var(--t-caption); }

.dropbox { background:var(--warm-row-bg); border:1px solid var(--warm-row-line); border-radius:var(--r); padding:var(--s-6); margin-top:var(--s-4); }
.dropbox label { display:block; font-size:var(--t-secondary); font-weight:600; color:var(--brand-ink); margin-bottom:var(--s-4); letter-spacing:.02em; }
.dropbox label:not(:first-child) { margin-top:var(--s-5); }
.dropbox textarea, .dropbox input[type="text"], .dropbox input[type="date"] { width:100%; font-size:var(--t-ui); color:var(--ink); border:1px solid var(--brand-line); border-radius:var(--r); padding:var(--s-4) var(--s-5); line-height:1.5; background:var(--surface); }
.dropbox__opt { font-weight:500; color:var(--warm-muted); text-transform:none; letter-spacing:0; }
.dropbox__hint { font-size:var(--t-caption); color:var(--warm-muted); margin:var(--s-4) 0 0; line-height:1.5; }
.dropbox__actions { margin-top:var(--s-5); }

.dsec { border-top:1px solid var(--line-soft); margin-top:var(--s-8); padding-top:var(--s-7); }
/* Checklist + Linked cards side by side on wide screens (design pass, 2026-08-15)
   — one shared divider on the pair; each child's own is switched off so there
   is no doubled rule between them. */
.dsec-grid { display:grid; grid-template-columns:repeat(auto-fit,minmax(240px,1fr)); gap:var(--s-8); }
.dsec-grid > .dsec { border-top:none; margin-top:0; padding-top:0; }
.dsec__label { font-weight:700; font-size:var(--t-micro); letter-spacing:.13em; text-transform:uppercase; color:var(--muted); margin:0 0 var(--s-5); }
.dsec__label .sub { font-weight:500; text-transform:none; letter-spacing:0; color:var(--faint-soft); margin-left:var(--s-4); }
/* pre-wrap so line breaks typed into a description (or a note) survive display —
   HTML would otherwise collapse them into one run-on paragraph. */
.dsec p.body { font-size:var(--t-body); line-height:1.65; color:var(--lead); margin:0; white-space:pre-wrap; overflow-wrap:break-word; }
.note p { white-space:pre-wrap; overflow-wrap:break-word; }

/* Copy-link control — a toolbar button now (design pass, 2026-08-21), matching
   Copy link / More / fullscreen / close as one consistent borderless family. */
.cardurl {
  display:inline-flex; align-items:center; gap:var(--s-3); cursor:pointer;
  font-family:var(--font-body); font-weight:600; font-size:var(--t-secondary); color:var(--muted);
  background:transparent; border:none; border-radius:var(--r); padding:var(--s-3) var(--s-4);
  transition:color .12s, background .12s;
}
.cardurl:hover { color:var(--heading); background:var(--line-soft); }
.cardurl--done { color:var(--ok); }
.cardurl__ico { font-size:13px; line-height:1; }

/* linked cards */
.link__row { display:flex; align-items:center; gap:var(--s-2); }
.link__row form { margin:0; }
.link__card {
  flex:1; min-width:0; display:flex; align-items:center; gap:var(--s-4);
  padding:var(--s-4); border-radius:var(--r); transition:background .12s;
}
.link__card:hover { background:var(--bg); }
.link__card--dim { opacity:.62; }
.link__id { font-size:var(--t-micro); font-weight:700; color:var(--faint); flex:none; font-family:var(--font-head); }
.link__title { font-size:var(--t-ui); color:var(--ink); font-weight:500; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.link__del {
  background:transparent; border:none; color:var(--idle); font-size:var(--ico); line-height:1; cursor:pointer;
  padding:var(--s-2) var(--s-4); border-radius:var(--r-sm); opacity:0; transition:opacity .12s, color .12s, background .12s;
}
.link__row:hover .link__del, .link__del:focus-visible { opacity:1; }
.link__del:hover { color:var(--brand-ink); background:var(--brand-tint); }
.linkadd { position:relative; margin-top:var(--s-4); }
.linkadd__input {
  width:100%; font-size:var(--t-ui); color:var(--ink);
  border:1px solid var(--line-strong); border-radius:var(--r); padding:var(--s-4) var(--s-5); background:var(--surface);
}
.linkadd__results:not(:empty) {
  margin-top:var(--s-3); border:1px solid var(--line); border-radius:var(--r-lg); overflow:hidden;
  box-shadow:var(--shadow-popover);
}
.linkadd__results form { margin:0; }
.linkadd__pick {
  width:100%; text-align:left; display:flex; align-items:center; gap:var(--s-4); cursor:pointer;
  background:var(--overlay); border:none; border-bottom:1px solid var(--line-soft); padding:var(--s-4) var(--s-5);
  transition:background .12s;
}
.linkadd__pick:hover { background:var(--bg); }
.linkadd__results form:last-child .linkadd__pick { border-bottom:none; }
/* Matches .srch__empty (the same "no results" moment in the header search), so
   it reads as a proper row inside the .linkadd__results card, not stray text. */
.linkadd__empty { padding:var(--s-6); margin:0; font-size:var(--t-ui); color:var(--muted); }

/* Tag picker (the ··· menu's "Change projects…"): current tags as removable
   chips, then search-plus-5-recent — replaces the old checkbox wall, which does
   not scale as tags grow (David, 2026-08-06). */
/* Contained in its own bordered card (fix, 2026-08-21) — a stale duplicate
   .projform rule further down this file (display:block, no gap, from the old
   checkbox-wall picker this replaced) was silently winning over this one on
   cascade order, collapsing the whole control into loosely stacked elements
   with no spacing. Removed; this is now the only .projform rule. */
/* A popover now, not a full-width block dropped into the chip row (David,
 * 2026-09-07: "too big bulky and not well thought out") — same treatment as
 * the due-date editor just above and `.qsched`'s schedule popover: floats
 * below its trigger, capped to a compact width, closes on an outside click
 * (see the click handler in render.ts) since it has no blur-autosave of its
 * own to fall back on. */
.projform {
  position:absolute; top:calc(100% + 6px); left:0; z-index:15;
  display:flex; flex-direction:column; gap:var(--s-4); background:var(--overlay); border:1px solid var(--line);
  border-radius:var(--r-lg); padding:var(--s-5); box-shadow:var(--shadow-popover);
  width:max-content; min-width:220px; max-width:min(280px, 90vw);
}
.projpicks__selected { display:flex; flex-wrap:wrap; gap:var(--s-3); }
.projpicks__selected form { margin:0; }
.projpick__chip {
  display:inline-flex; align-items:center; gap:var(--s-2); cursor:pointer; border:none; padding:0 var(--s-2) 0 0; border-radius:var(--r-pill);
  background:transparent;
}
.projpick__chip .ico { font-size:12px; color:var(--muted); transition:color .12s; }
.projpick__chip:hover .ico { color:var(--brand-ink); }
.projpick__search {
  width:100%; font-size:var(--t-secondary); color:var(--ink);
  border:1px solid var(--line-strong); border-radius:var(--r); padding:var(--s-3) var(--s-4); background:var(--surface);
}
.projpick__hint { font-size:var(--t-caption); font-weight:600; color:var(--faint); letter-spacing:.04em; text-transform:uppercase; }
.projpick__row { display:flex; flex-wrap:wrap; gap:var(--s-3); max-height:180px; overflow-y:auto; }
.projpick__row form { margin:0; }
.projpick__add {
  cursor:pointer; font-size:var(--t-caption); font-weight:600; color:var(--muted);
  background:var(--surface); border:1px solid var(--line-strong); border-radius:var(--r-pill); padding:var(--s-2) var(--s-5);
  transition:color .12s, border-color .12s, background .12s;
}
.projpick__add:hover { color:var(--brand-ink); border-color:var(--brand-ink); background:var(--brand-tint); }

/* projects manager */
/* "Add project" lives in the modal now, not permanently open on the page
   (David, 2026-09-07) — .projnew is the form itself, styled like .newcard
   since it now shares that container rather than needing its own panel. */
.projnew { display:flex; flex-direction:column; gap:var(--s-6); }
.projnew__name { font-size:var(--t-ui); color:var(--ink); border:1px solid var(--line-strong); border-radius:var(--r); padding:var(--s-4) var(--s-5); background:var(--surface); }
.projnew select { font-size:var(--t-ui); border:1px solid var(--line-strong); border-radius:var(--r); padding:var(--s-4) var(--s-5); background:var(--surface); color:var(--ink); }
.projnew__colours { border:none; margin:0; padding:0; display:flex; flex-wrap:wrap; gap:var(--s-2); align-items:center; }
.projnew__colours legend { font-size:var(--t-caption); color:var(--muted); margin-bottom:var(--s-4); }
.projnew__colours .swatch { position:relative; cursor:pointer; }
.projnew__colours .swatch input { position:absolute; width:1px; height:1px; opacity:0; }
.projnew__colours .swatch input:checked + .swpick { outline:2px solid var(--ink); outline-offset:0; }
/* Keyboard focus had no visual state at all here — only :checked did, so
   tabbing through the picker gave no clue which swatch was current. Matches
   the --focus ring the other swatch picker already uses (.swatch__dot). */
.projnew__colours .swatch input:focus-visible + .swpick { outline:2px solid var(--focus); outline-offset:2px; }
.projpanel__toolbar { display:flex; justify-content:flex-end; margin:0 0 var(--s-5); }
/* The projects list sits in one panel with a Project/Cards header row (design
   pass, 2026-08-15), matching the Today/Needs-attention panel family. */
.projpanel { background:var(--surface); border:1px solid var(--line); border-radius:var(--r-lg); box-shadow:var(--shadow-card); overflow:hidden; }
.projpanel__head {
  display:flex; align-items:center; justify-content:space-between; padding:var(--s-5) var(--s-6);
  background:var(--bg); border-bottom:1px solid var(--line-soft);
  font-size:var(--t-micro); font-weight:700; letter-spacing:.12em; text-transform:uppercase; color:var(--muted);
}
.projlist { display:flex; flex-direction:column; }
.projrow { display:flex; align-items:center; justify-content:space-between; gap:var(--s-5); padding:var(--s-5) var(--s-6); border-bottom:1px solid var(--line-soft); flex-wrap:wrap; transition:background .12s; }
.projrow:last-child { border-bottom:none; }
.projrow:hover { background:var(--bg); }
.projrow form { margin:0; }
.projrow--child { background:var(--sunken); }
.projrow--archived { opacity:.58; }
.projrow__id { flex:1; min-width:200px; display:flex; align-items:center; gap:var(--s-4); padding-left:var(--s-6); }
/* A drawn connector (not indentation alone) marking a sub-project — matches the
   design's "carry a label and a connector" note. The elbow has to actually
   point at the child's dot, which it did not: centred in the row, a 12px box
   puts its bottom border 6px BELOW the dot's centre, and a -2px right margin
   left its horizontal line stopping 6px SHORT of the dot. margin-bottom equal
   to its own height lifts the elbow so the bottom border lands exactly on the
   row's centre line, and a right margin of a full gap (-8px) runs the line up
   to the dot's edge. */
.projrow__connector { flex:none; width:12px; height:12px; margin:0 calc(-1 * var(--s-4)) 12px 0; border-left:1px solid var(--idle); border-bottom:1px solid var(--idle); border-bottom-left-radius:2px; }
.projrow--child .projrow__id { padding-left:36px; }
.projrow--child .projrow__connector { margin-left:-18px; }
/* A child row's chip reads slightly smaller than a top-level one's — the one
 * remaining visual distinction beyond the connector/indent/sunken background,
 * now that there's no separate "Umbrella"/"Sub-project" text pill (David,
 * 2026-09-08: "adding a tag for each one is stupid" — the connector, indent
 * and background already said it). */
.projrow--child .chip--proj { font-size:calc(var(--t-micro) - 1px); padding:var(--s-1) var(--s-4); }
.projrow__tools { display:flex; align-items:center; justify-content:flex-end; gap:var(--s-5); flex:none; }
.projrow__count { font-family:var(--font-head); font-weight:700; font-size:var(--t-secondary); color:var(--heading); min-width:20px; text-align:right; }
/*
 * The panel is one table, not a header laid out independently of the rows
 * beneath it. It used to be: the head was its own flex row with
 * justify-content:space-between, so "CARDS" pinned itself to the panel's right
 * edge while the count it labels sits before the colour picker and the row
 * actions — 463px away from its own column, the single most visibly wrong thing
 * in the app.
 *
 * Fixed with subgrid rather than by reserving a spacer the width of the
 * controls: every such number (picker + Edit + Archive) is content-derived and
 * would drift the moment a label changed. Here the head and every row are
 * subgrid items of the panel's own three columns, so the count column is
 * genuinely one column and cannot drift.
 *
 * Guarded twice: @supports because without subgrid `grid-template-columns` is
 * invalid and each row would collapse to a single stacked column, and a width
 * floor because a 3-track row cannot wrap the way the flex version does on a
 * narrow screen. Below the floor, and on any engine without subgrid, the
 * original flex layout is what renders.
 */
@supports (grid-template-columns: subgrid) {
  @media (min-width: 861px) {
    .projpanel { display:grid; grid-template-columns:minmax(0,1fr) auto auto; }
    .projlist { display:contents; }
    .projpanel > .emptybox { grid-column:1 / -1; }
    .projpanel__head, .projrow { grid-column:1 / -1; display:grid; grid-template-columns:subgrid; align-items:center; column-gap:var(--s-5); }
    .projpanel__head > :last-child { justify-self:end; }
    .projrow--edit { display:flex; }
  }
}
/* Archived projects collapse out of the main list into their own disclosure
   (David, 2026-09-07) — same shape as .hist__more's "show earlier" toggle. */
.projarchive { margin:var(--s-6) 0 0; }
.projarchive > summary {
  list-style:none; cursor:pointer; display:flex; align-items:center; gap:var(--s-4);
  font-size:var(--t-caption); font-weight:600; color:var(--muted);
  padding:var(--s-4) var(--s-2); transition:color .12s;
}
.projarchive > summary::-webkit-details-marker { display:none; }
.projarchive > summary:hover { color:var(--ink); }
.projarchive__count { font-family:var(--font-head); font-weight:700; color:var(--faint); }
.projarchive[open] > summary { margin-bottom:var(--s-2); }
.projarchive .projpanel { box-shadow:none; }
.projrow--edit { align-items:flex-start; flex-direction:column; gap:var(--s-4); }
.projedit { display:flex; flex-wrap:wrap; gap:var(--s-4); align-items:center; width:100%; margin:0; }
.projedit__name { flex:1; min-width:180px; font-size:var(--t-ui); color:var(--ink); border:1px solid var(--line-strong); border-radius:var(--r); padding:var(--s-4) var(--s-5); background:var(--surface); }
.projedit select { font-size:var(--t-ui); border:1px solid var(--line-strong); border-radius:var(--r); padding:var(--s-4) var(--s-5); background:var(--surface); color:var(--ink); }
.projedit__hint { font-size:var(--t-caption); color:var(--muted); margin:0; padding-left:var(--s-1); }
.projedit__desc {
  flex:1 0 100%; font-family:inherit; font-size:var(--t-ui); color:var(--ink); line-height:1.5;
  border:1px solid var(--line-strong); border-radius:var(--r); padding:var(--s-4) var(--s-5); background:var(--surface);
  resize:none; max-height:40vh; overflow-y:auto;
}
/* 6px, not 4px: a selected swatch's 2px ring sits in this gap, and at 4px the
   two rings of adjacent selections crowded each other. Each swatch is its own
   <form>, and a form is a block box — so the empty swatches took their baseline
   from their bottom edge while the "Auto" one, which contains the letter A,
   took its from the text, dropping it 4.65px below every other swatch on the
   row. Making each form a flex container removes the line box entirely, so
   every swatch is simply 22px tall. */
.swpick__row { display:flex; flex-wrap:wrap; gap:var(--s-3); align-items:center; }
.swpick__row form { display:flex; }
.swpick {
  width:22px; height:22px; border-radius:var(--r-sm); border:1.5px solid var(--chip-ring); cursor:pointer;
  padding:0; font-size:var(--t-micro); font-weight:700; line-height:1;
  display:inline-flex; align-items:center; justify-content:center; transition:transform .1s;
}
.swpick:hover { transform:scale(1.12); }
.swpick.on { outline:2px solid var(--ink); outline-offset:0; }
/* Auto's own dashed ring marks it as the active MODE, distinct from the solid
   ring on whichever real swatch it currently resolves to (David, 2026-09-07:
   the two need to read differently, or "Auto" looks like a colour itself). */
.swpick.auto-on { outline:2px dashed var(--faint); outline-offset:0; }

/* checklist */
.check__head { display:flex; align-items:center; justify-content:space-between; gap:var(--s-5); margin:0 0 var(--s-5); }
.check__count { font-family:var(--font-head); font-weight:600; font-size:var(--t-caption); white-space:nowrap; color:var(--muted); }
.check__count.done { color:var(--ok); }
.check__track { height:6px; border-radius:var(--r-pill); background:var(--line-soft); overflow:hidden; margin:0 0 var(--s-6); }
.check__fill { height:100%; border-radius:var(--r-pill); background:var(--brand); transition:width .35s cubic-bezier(.22,.61,.36,1); }
.check__fill.done { background:var(--ok); }
.check__item { display:flex; align-items:flex-start; gap:var(--s-5); width:100%; text-align:left; background:transparent; border:none; border-radius:var(--r); padding:var(--s-4); cursor:pointer; }
.check__item:hover { background:var(--bg); }
.check__box { flex:none; width:18px; height:18px; border-radius:var(--r-sm); border:1.5px solid var(--idle); background:var(--surface); color:#fff; font-size:var(--t-caption); line-height:16px; text-align:center; margin-top:1px; }
.check__item.on .check__box { background:var(--brand); border-color:var(--brand); }
.check__item .lbl { font-size:var(--t-body); line-height:1.5; color:var(--lead); }
.check__item.on .lbl { color:var(--faint); text-decoration:line-through; }

.check__row { display:flex; align-items:center; gap:var(--s-2); }
.check__row form { margin:0; }
.check__del {
  background:transparent; border:none; color:var(--idle); font-size:var(--ico); line-height:1; cursor:pointer;
  padding:var(--s-2) var(--s-4); border-radius:var(--r-sm); opacity:0; transition:opacity .12s, color .12s, background .12s;
}
.check__row:hover .check__del, .check__del:focus-visible { opacity:1; }
.check__del:hover { color:var(--brand-ink); background:var(--brand-tint); }
/* Pencil to edit an item's text — same quiet reveal-on-hover as the delete × */
.check__edit {
  background:transparent; border:none; color:var(--idle); font-size:var(--t-secondary); line-height:1; cursor:pointer;
  padding:var(--s-2) var(--s-3); border-radius:var(--r-sm); opacity:0; transition:opacity .12s, color .12s, background .12s;
}
.check__row:hover .check__edit, .check__edit:focus-visible { opacity:1; }
.check__edit:hover { color:var(--brand-ink); background:var(--brand-tint); }
/* Reorder: a compact up/down column, revealed on hover like edit/remove. */
.check__reorder { display:inline-flex; flex-direction:column; gap:0; opacity:0; transition:opacity .12s; }
.check__row:hover .check__reorder, .check__reorder:focus-within { opacity:1; }
.check__move { background:transparent; border:none; color:var(--idle); cursor:pointer; padding:0 var(--s-2); display:flex; line-height:.6; }
.check__move .ico { font-size:15px; }
.check__move:hover:not(:disabled) { color:var(--brand-ink); }
.check__move:disabled { opacity:.28; cursor:default; }
/* Fold for a long list: completed items + overflow tucked behind a toggle. */
.check__fold { margin:var(--s-2) 0 0; }
/* Board pages (David, 2026-08-12 as the Blog board, any board since 2026-10-02):
   full page width, one list per stage, stacked (not side by side, which cramped each into half-width) — reads as
   a table, one row per card, order/title/due/move-column all on one line. */
.planboard { display:flex; flex-direction:column; gap:28px; margin-top:var(--s-7); }
.phead__hint { font-size:var(--t-caption); color:var(--faint); }
.planpanel { width:100%; }
.planlist { display:flex; flex-direction:column; }
.planrow { display:flex; align-items:center; gap:var(--s-5); padding:var(--s-4) var(--s-1); border-bottom:1px solid var(--line-soft); border-radius:var(--r-sm); transition:opacity .15s; }
.planlist .planrow:last-child { border-bottom:none; }
/* Drag to reorder (design pass, 2026-08-15) — the up/down arrows still work
   (keyboard/no-JS fallback), but dragging the handle is the fast path: grab,
   drop anywhere in the column, done in one gesture instead of N clicks. */
.planrow[draggable="true"] { cursor:grab; }
.planrow.dragging { opacity:.35; cursor:grabbing; }
.planrow__handle { flex:none; display:flex; color:var(--idle); cursor:grab; margin-right:-6px; }
.planrow__handle .ico { font-size:16px; }
.planrow:hover .planrow__handle { color:var(--muted); }
.planrow__title { flex:1 1 auto; min-width:0; font-weight:500; font-size:var(--t-body); color:var(--ink); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.planrow__due { flex:none; }
/* At phone widths .planrow's handle + due chip + #id badge +
   the "More ..." move-menu left .planrow__title (flex:1 1 auto, the row's
   only shrinkable item) as little as 74px of a 262px row — confirmed live,
   titles were reading as 4-8 visible characters, not enough to tell cards
   apart at a glance. Two changes claw most of that back for phones only:
   the drag handle is dropped (native HTML5 drag-and-drop, the only way this
   list reorders, doesn't fire from touch, so on a phone it was already dead
   weight) and the menu's "More" text collapses to its icon alone (its title/
   aria-label still name it for anyone using a screen reader). Together that
   took the title from 74px to over 130px in testing. */
@media (max-width: 640px) {
  .planrow__handle { display:none; }
  .cardmenu > summary::before { content:""; }
  .cardmenu > summary { padding:var(--s-3); }
  /* Rows that carry an editable date field (Draft in progress, Scheduled) have
     one more wide control than the rest: the date field alone is ~140px, which
     left the title ~107px. Let those rows wrap — title on its own full line,
     date, #id and menu beneath — instead of squeezing the title further. */
  .planrow--dated { flex-wrap:wrap; row-gap:var(--s-2); }
  .planrow--dated .planrow__title { flex:1 1 100%; white-space:normal; }
  .planrow--dated .cardid { margin-left:auto; }
}

/* A board's finished sections (Published, Dropped) and its Edit board panel —
 * a look back, not a working column, so each stays collapsed (David,
 * 2026-08-27): same disclosure pattern as Archive's Ignored list. */
.blogpublished { margin-top:var(--s-8); }
.blogpublished__toggle {
  list-style:none; cursor:pointer; display:inline-flex; align-items:center; gap:var(--s-3);
  font-size:var(--t-caption); font-weight:600; color:var(--muted); padding:var(--s-3) var(--s-1);
}
.blogpublished__toggle::-webkit-details-marker { display:none; }
.blogpublished__toggle .ico { font-size:14px; color:var(--ok); }
.blogpublished__toggle:hover { color:var(--brand-ink); }
.blogpublished .archlist { margin-top:var(--s-4); }
/* Same shape, on a project's own drilldown page (David: "archived or complete
   in a separate section"). */
.projectpage__finished { margin-top:var(--s-8); }
.projectpage__finished-toggle {
  list-style:none; cursor:pointer; display:inline-flex; align-items:center; gap:var(--s-3);
  font-size:var(--t-caption); font-weight:600; color:var(--muted); padding:var(--s-3) var(--s-1);
}
.projectpage__finished-toggle::-webkit-details-marker { display:none; }
.projectpage__finished-toggle .ico { font-size:14px; color:var(--ok); }
.projectpage__finished-toggle:hover { color:var(--brand-ink); }
.projectpage__finished .archlist { margin-top:var(--s-4); }
/* .archrow__main is a COLUMN everywhere else (title, then tags, stacked) —
   a new, scoped class rather than touching that shared one, so the status
   chip sits inline with the title here without breaking Unscheduled/Archive's
   own use of the same parent class. */
.archrow__inline { display:flex; align-items:center; gap:var(--s-4); }
.check__foldtoggle { list-style:none; cursor:pointer; display:inline-flex; align-items:center; gap:var(--s-2); font-size:var(--t-caption); font-weight:600; color:var(--muted); padding:var(--s-3) var(--s-2); }
.check__foldtoggle::-webkit-details-marker { display:none; }
.check__foldtoggle .ico { font-size:14px; }
.check__foldtoggle:hover { color:var(--brand-ink); }
/* Inline label editor row */
.check__editform { display:flex; gap:var(--s-4); align-items:center; flex:1; min-width:0; }
.check__toggleform { flex:1; min-width:0; } /* the row's own checked-toggle form — needs the same flex/truncation room as .check__editform, was an inline style */
.check__editform input[type=text] {
  flex:1; min-width:0; font-size:var(--t-ui); color:var(--ink);
  border:1px solid var(--line-strong); border-radius:var(--r); padding:var(--s-4) var(--s-5); background:var(--surface);
}
.check__editform .btn { padding:var(--s-4) var(--s-5); font-size:var(--t-secondary); }
.check__add { display:flex; gap:var(--s-4); margin-top:var(--s-4); }
.check__add input {
  flex:1; min-width:0; font-size:var(--t-ui); color:var(--ink);
  border:1px solid var(--line-strong); border-radius:var(--r); padding:var(--s-4) var(--s-5); background:var(--surface);
}
.check__add .btn { padding:var(--s-4) var(--s-6); font-size:var(--t-secondary); }

/* notes */
.note { background:var(--raised); border:1px solid var(--line); border-radius:var(--r-lg); padding:var(--s-5) var(--s-6); }
.note__head { display:flex; align-items:center; gap:var(--s-4); margin-bottom:var(--s-3); }
.note__avatar { flex:none; width:24px; height:24px; border-radius:50%; font-family:var(--font-head); font-weight:600; font-size:var(--t-micro); display:flex; align-items:center; justify-content:center; background:var(--blush-bg); color:var(--brand-ink); }
.note__avatar.sync { background:var(--neutral-fill); color:var(--lead); }
.note__author { font-size:var(--t-secondary); font-weight:600; color:var(--lead); }
.note__ts { margin-left:auto; font-size:var(--t-caption); color:var(--faint-soft); white-space:nowrap; }
.note p { font-size:var(--t-ui); line-height:1.6; color:var(--lead); margin:0; }
/* A retracted note stays visible but reads as superseded, never erased. */
.note.retracted { background:var(--raised); border-style:dashed; }
.note.retracted > p { color:var(--faint-soft); text-decoration:line-through; }
.note__flag {
  font-size:var(--t-micro); font-weight:700; letter-spacing:.08em; text-transform:uppercase;
  color:var(--muted); background:var(--line-soft); border-radius:var(--r-pill); padding:var(--s-1) var(--s-4);
}
.note__flag.retracted { color:var(--brand-ink); background:var(--brand-tint); }
.note__hint { font-size:var(--t-caption); color:var(--muted); margin:var(--s-4) 0 0; line-height:1.5; }
.note__edit { margin-top:var(--s-4); }
.note__edit > summary {
  cursor:pointer; font-size:var(--t-caption); font-weight:600; letter-spacing:.06em; text-transform:uppercase;
  color:var(--muted); list-style:none;
}
.note__edit > summary:hover { color:var(--brand-ink); }
.note__edit textarea, .note__edit input[type=text] {
  width:100%; margin-top:var(--s-4); font-size:var(--t-ui); color:var(--ink);
  border:1px solid var(--line-strong); border-radius:var(--r); padding:var(--s-4) var(--s-5); line-height:1.5; background:var(--surface);
}
.note__actions { display:flex; gap:var(--s-4); margin-top:var(--s-4); }
.note__actions .btn { padding:var(--s-4) var(--s-6); font-size:var(--t-secondary); }
.note__link {
  background:none; border:none; padding:var(--s-3) 0; margin-top:var(--s-3); cursor:pointer;
  font-size:var(--t-caption); font-weight:600; color:var(--muted);
}
.note__link.danger:hover { color:var(--brand-ink); }
.composer { background:var(--surface); border:1px solid var(--line); border-radius:var(--r-lg); padding:var(--s-5); }
.composer .lbl { display:block; font-size:var(--t-micro); font-weight:700; letter-spacing:.1em; text-transform:uppercase; color:var(--faint); margin-bottom:var(--s-4); }
.composer textarea { width:100%; font-size:var(--t-ui); color:var(--ink); border:1px solid var(--line-strong); border-radius:var(--r); padding:var(--s-4) var(--s-5); line-height:1.55; }
.composer__foot { display:flex; align-items:center; justify-content:space-between; gap:var(--s-5); margin-top:var(--s-4); }
.composer__foot span { font-size:var(--t-caption); color:var(--faint-soft); }

/* sessions in detail */
.dsess { display:flex; align-items:center; justify-content:space-between; gap:var(--s-5); background:var(--bg); border:1px solid var(--line); border-radius:var(--r); padding:var(--s-4) var(--s-5); }
.dsess .when { font-size:var(--t-ui); color:var(--lead); display:flex; flex-direction:column; gap:var(--s-1); }
.dsess .when b { font-family:var(--font-head); font-weight:600; color:var(--navy-ink); }
/* The session's own name is the line that maps to the calendar event title. */
.dsess__name { font-weight:500; color:var(--ink); }
/* Upcoming vs passed at a glance: an upcoming block reads active (white, a navy
   edge); a passed one recedes to quiet grey history. */
.dsess--upcoming { background:var(--surface); border-color:var(--band-cool-line); box-shadow:inset 3px 0 0 var(--navy); }
.dsess--editable.dsess--upcoming { background:var(--surface); }
.dsess--past { background:var(--line-soft); border-color:var(--line); }
.dsess--editable.dsess--past { background:var(--line-soft); }
.dsess--past .when, .dsess--past .when b, .dsess--past .dsess__name { color:var(--muted); }
/* Earlier sessions (>48h) folded away so they do not clog the card. */
.sesshist { border:1px dashed var(--band-cool-line); border-radius:var(--r); background:transparent; }
.sesshist > summary { list-style:none; cursor:pointer; display:flex; align-items:center; gap:var(--s-3); padding:var(--s-4) var(--s-5); font-size:var(--t-secondary); font-weight:600; color:var(--muted); }
.sesshist > summary::-webkit-details-marker { display:none; }
.sesshist > summary:hover { color:var(--brand-ink); }
.sesshist > summary .ico { font-size:14px; }
.sesshist[open] > summary { border-bottom:1px dashed var(--band-cool-line); margin-bottom:var(--s-4); }
.sesshist__list { display:flex; flex-direction:column; gap:var(--s-4); padding:0 var(--s-3) var(--s-3); }

/* ---- inline click-to-edit fields ---- */
/*
 * The card is a document you read, not a form you fill in. So a field looks
 * like plain text; hovering tints it to show it will accept a click; and the
 * editor that replaces it keeps the SAME typography, so the text appears to
 * become editable rather than the page lurching into "edit mode".
 *
 * The hover tint is painted with a spread box-shadow rather than padding, so
 * nothing shifts position when it appears.
 */
.editable {
  cursor:text; border-radius:var(--r-sm);
  box-shadow:0 0 0 0 transparent;
  transition:background .12s ease, box-shadow .12s ease;
}
.editable:hover {
  background:var(--row-hover);
  box-shadow:0 0 0 5px var(--row-hover);
}
.editable:focus-visible { outline:3px solid var(--focus); outline-offset:4px; }
.editable .placeholder { color:var(--faint); }
/* No hover ring (David: removed along with every other tag/chip hover). */
.editable-chip { cursor:pointer; border-radius:var(--r-pill); display:inline-block; }
.editable-chip:focus-visible { outline:2px solid var(--focus); outline-offset:2px; }

.fieldform { margin:0; }
.fieldform__control {
  width:100%; display:block; font-family:var(--font-body); color:var(--ink); background:var(--surface);
  border:1px solid var(--line-strong); border-radius:var(--r); padding:var(--s-4) var(--s-5);
  transition:border-color .12s ease, box-shadow .12s ease;
}
.fieldform__control:focus {
  outline:none; border-color:var(--focus); box-shadow:0 0 0 3px var(--brand-tint);
}
/* Each editor inherits the look of the text it replaced. */
.fieldform--title .fieldform__control {
  /* Matches .detail__title exactly, so the text does not resize as it becomes editable. */
  font-family:var(--font-head); font-weight:600; font-size:var(--t-h2); line-height:1.25;
  letter-spacing:.2px; color:var(--heading); padding:var(--s-2) var(--s-4);
  /* A textarea that wraps and grows to fit the whole title, never scrolling
     sideways. field-sizing:content auto-heights it where supported (Chromium);
     elsewhere it starts at one row and scrolls only as a last resort. */
  resize:none; overflow:hidden; white-space:pre-wrap; overflow-wrap:break-word; word-break:break-word;
  field-sizing:content; min-height:calc(1.25em + 10px);
}
.fieldform--desc .fieldform__control { font-size:var(--t-body); line-height:1.65; color:var(--lead); }
/* Grows to fit what's typed or pasted (render.ts's autoGrowTextarea), instead
 * of a fixed 4 rows that makes a big paste something you have to scroll
 * inside or manually drag bigger (David, 2026-09-06). Capped so a genuinely
 * huge paste scrolls internally rather than pushing the modal off-screen;
 * resize:none because auto-grow already owns the height — a manual drag
 * would just be overwritten by the next keystroke. */
.autogrow { resize:none; overflow-y:auto; max-height:55vh; }
/* align-items:stretch, not center: the date input is a 14px field and Clear
   date / Cancel are 13px buttons, so centring them left three controls at three
   heights on one row. Stretching makes every control the height of the tallest
   without pinning a number that a font change would invalidate. The hint is
   prose, not a control, so it stays centred. */
.fieldform--inline { display:flex; align-items:stretch; flex-wrap:wrap; gap:var(--s-4); }
.fieldform--inline .fieldform__hint { align-self:center; }
/* The browser's own date-picker chrome (the little calendar button, the
   segment-selection highlight) can't be fully restyled without replacing it
   outright — accent-color is the one standard hook for it, so the selected
   segment picks up the app's own brand colour instead of OS default blue. */
.fieldform--inline .fieldform__control { width:auto; font-size:var(--t-ui); padding:var(--s-3) var(--s-4); accent-color:var(--brand); }

/* ---- editable project tags ---- */
/* No padding of its own: this is a wrapper around chips that already sit in
   .cardheader's shared 8px gap, and its 4px sides were adding to that gap on
   whichever side it fell — so the header's chip gaps ran 8/12/12px instead of
   one value. The shared gap does the spacing for every chip in the row. */
.projset { display:inline-flex; flex-wrap:wrap; gap:var(--s-4); align-items:center; }

.fieldform__actions { display:flex; align-items:center; gap:var(--s-4); margin-top:var(--s-4); }
.fieldform__save {
  background:var(--invert-bg); color:var(--invert-ink); border:none; cursor:pointer;
  font-family:var(--font-body); font-weight:600; font-size:var(--t-secondary);
  padding:var(--s-3) var(--s-6); border-radius:var(--r); transition:background .12s ease;
}
.fieldform__save:hover { background:var(--invert-bg-hover); }
/* With JavaScript live, fields autosave on blur — the Save button is redundant. */
.js .fieldform__save { display:none; }
.fieldform__cancel {
  background:none; border:none; cursor:pointer;
  font-family:var(--font-body); font-weight:500; font-size:var(--t-secondary); color:var(--muted); padding:var(--s-3) var(--s-1);
}
.fieldform__cancel:hover { color:var(--brand-ink); }
.fieldform__clear {
  background:none; border:1px solid var(--line-strong); border-radius:var(--r); cursor:pointer;
  font-family:var(--font-body); font-weight:500; font-size:var(--t-secondary); color:var(--muted); padding:var(--s-3) var(--s-5);
}
.fieldform__clear:hover { color:var(--brand-ink); border-color:var(--brand-ink); }
.fieldform__hint { font-size:var(--t-caption); color:var(--faint); }
@media (max-width: 520px) { .fieldform__hint { flex-basis:100%; } }

/* ---- editing an existing session ---- */
.dsess--editable { display:block; padding:0; background:var(--bg); }
.dsess--editable > summary {
  display:flex; align-items:center; justify-content:space-between; gap:var(--s-5);
  list-style:none; cursor:pointer; padding:var(--s-4) var(--s-5);
}
.dsess--editable > summary::-webkit-details-marker { display:none; }
.dsess--editable > summary:hover { background:var(--line-soft); }
.dsess__edit { font-size:var(--t-caption); font-weight:600; color:var(--muted); }
.dsess--editable > summary:hover .dsess__edit { color:var(--brand-ink); }
.dsess__form {
  display:flex; flex-wrap:wrap; gap:var(--s-5); align-items:flex-end;
  padding:var(--s-5); border-top:1px solid var(--line); background:var(--surface);
}
.dsess__form label { display:flex; flex-direction:column; gap:var(--s-2); font-size:var(--t-caption); color:var(--muted); }
/* The name field is the primary input, so it takes the whole first row. */
.dsess__namefield { flex-basis:100%; }
.dsess__namefield input { width:100%; }
.dsess__form input, .dsess__form select {
  font-family:inherit; font-size:var(--t-ui); color:var(--ink); background:var(--surface);
  border:1px solid var(--line-strong); border-radius:var(--r); padding:var(--s-4);
}
/* Colour as colour: eleven swatches, not a dropdown of names. */
.swatches { border:none; margin:0; padding:0; display:flex; flex-wrap:wrap; gap:var(--s-3); align-items:center; flex-basis:100%; }
.swatches legend { font-size:var(--t-caption); color:var(--muted); padding:0; margin-bottom:var(--s-3); }
.swatch { position:relative; cursor:pointer; line-height:0; }
.swatch input { position:absolute; opacity:0; width:0; height:0; }
.swatch__dot {
  display:block; width:22px; height:22px; border-radius:50%;
  box-shadow:inset 0 0 0 1px var(--chip-ring);
  transition:transform .1s ease, box-shadow .1s ease;
}
.swatch:hover .swatch__dot { transform:scale(1.12); }
.swatch input:checked + .swatch__dot { box-shadow:0 0 0 2px var(--surface), 0 0 0 4px var(--heading); }
.swatch input:focus-visible + .swatch__dot { box-shadow:0 0 0 2px var(--surface), 0 0 0 4px var(--focus); }
.swatch__name { position:absolute; width:1px; height:1px; overflow:hidden; clip:rect(0 0 0 0); white-space:nowrap; }

.dsess__cancel { padding:0 var(--s-5) var(--s-5); background:var(--surface); }
.dsess__cancel button {
  background:none; border:none; cursor:pointer; padding:var(--s-3) 0;
  font-family:inherit; font-size:var(--t-caption); font-weight:600; color:var(--muted);
}
.dsess__cancel button:hover { color:var(--brand-ink); }

.dsess__actions { margin-left:auto; }
.dsess__actions .btn { padding:var(--s-4) var(--s-6); font-size:var(--t-secondary); }
.dsess__hint { flex-basis:100%; font-size:var(--t-caption); color:var(--faint); margin:0; line-height:1.5; }

/* ---- scheduling (Phase 5) ---- */
/* Find a slot, then book it. The proposal is shown before anything is written
   to the calendar, so nothing is created that David has not actually seen. */
/* Collapsed by default (David, 2026-09-07) — a permanently-open "add" form
 * sitting beside collapsed "edit" rows for existing sessions read as
 * confusing, so it now opens the same way any of them do: click to reveal.
 * Styled exactly like .newcard__sched's own "+ Session options" toggle
 * (David: "don't invent new UI, use what we have, just a button is fine") —
 * plain text with a +/− prefix, not a new bordered-pill design. */
.sched { margin-top:var(--s-5); padding-top:var(--s-5); border-top:1px dashed var(--line); }
.sched__toggle { cursor:pointer; font-weight:600; font-size:var(--t-ui); color:var(--brand-ink); list-style:none; }
.sched__toggle::-webkit-details-marker { display:none; }
.sched__toggle::before { content:"＋"; margin-right:var(--s-4); font-weight:600; }
.sched[open] > .sched__toggle::before { content:"－"; }
.sched[open] > .sched__toggle { margin-bottom:var(--s-5); }
.sched .dsess__form { margin-top:var(--s-1); }
.sched__error { font-size:var(--t-secondary); color:var(--brand-ink); background:var(--brand-tint); border-radius:var(--r); padding:var(--s-4) var(--s-5); margin:var(--s-4) 0 0; line-height:1.5; }

/* standalone card page backdrop (direct nav / no-JS) */
.cardpage-back { max-width:var(--shell); margin:0 auto; padding:clamp(32px,5vw,56px) 24px; }
.cardpage-back a { font-weight:600; }

/* ---- card history (the operation log, per card) ---- */
/* A quiet timeline. The dot is actor-coded so "what wasn't me" reads at a
   glance — that is the question this section exists to answer. */
.hist__more { margin:0 0 var(--s-2); }
.hist__more > summary {
  list-style:none; cursor:pointer; display:inline-block;
  font-size:var(--t-caption); font-weight:600; color:var(--muted);
  padding:var(--s-2) var(--s-4); border:1px solid var(--line); border-radius:var(--r-pill); background:var(--surface);
  transition:color .12s, border-color .12s, background .12s;
}
.hist__more > summary::-webkit-details-marker { display:none; }
.hist__more > summary:hover { color:var(--brand-ink); border-color:var(--blush-line); background:var(--brand-tint); }
.hist__more[open] > summary { margin-bottom:var(--s-3); }

.hist { list-style:none; margin:0; padding:0; }
.hist__item {
  position:relative; display:flex; align-items:baseline; justify-content:space-between; gap:var(--s-5);
  padding:var(--s-4) 0 var(--s-4) var(--s-7); font-size:var(--t-ui); border-left:1px solid var(--line); margin-left:var(--s-2);
}
.hist__item::before {
  content:""; position:absolute; left:-4.5px; top:15px;
  width:8px; height:8px; border-radius:50%; background:var(--idle); border:2px solid var(--surface);
}
.hist__item--david::before  { background:var(--brand-ink); }
.hist__item--claude::before { background:var(--navy-ink); }
.hist__item--system::before { background:var(--idle); }
.hist__item:last-child { border-left-color:transparent; }
.hist__what { color:var(--lead); min-width:0; }
.hist__meta { display:flex; align-items:baseline; gap:var(--s-4); flex:none; }
.hist__who { font-size:var(--t-caption); font-weight:600; color:var(--muted); }
.hist__item--claude .hist__who { color:var(--navy-ink); }
.hist__item--david .hist__who { color:var(--brand-ink); }
.hist__when { font-size:var(--t-caption); color:var(--faint); white-space:nowrap; font-variant-numeric:tabular-nums; }
@media (max-width: 560px) {
  .hist__item { flex-direction:column; gap:var(--s-2); }
}

/* ------------------------------------------------------------------ confirm */
/* A considered decision should look considered. The native confirm() reads as
   an error message from the browser; this reads as part of the app. */
.confirm {
  border:none; padding:0; background:transparent;
  max-width:min(440px, calc(100vw - 40px)); width:100%;
  color:var(--ink);
}
.confirm::backdrop { background:var(--scrim); }
.confirm__inner {
  background:var(--overlay); border:1px solid var(--line); border-radius:var(--r-lg); padding:var(--s-8) 28px var(--s-7);
  box-shadow:var(--shadow-modal);
  animation:confirmIn .16s cubic-bezier(.22,.61,.36,1) both;
}
.confirm__title {
  font-family:var(--font-head); font-weight:600; font-size:var(--t-h2); line-height:1.3;
  color:var(--heading); margin:0 0 var(--s-4); letter-spacing:.2px;
}
.confirm__body { font-size:var(--t-ui); line-height:1.6; color:var(--muted); margin:0 0 var(--s-7); }
.confirm__actions { display:flex; justify-content:flex-end; align-items:center; gap:var(--s-4); }
.confirm__cancel {
  background:none; border:none; cursor:pointer; font-family:inherit;
  font-size:var(--t-ui); font-weight:500; color:var(--muted); padding:var(--s-4) var(--s-5); border-radius:var(--r);
}
.confirm__cancel:hover { color:var(--heading); background:var(--bg); }
.confirm__ok {
  cursor:pointer; border:none; font-family:inherit; font-weight:600; font-size:var(--t-ui);
  padding:var(--s-4) var(--s-6); border-radius:var(--r); background:var(--invert-bg); color:var(--invert-ink);
  transition:background .15s ease;
}
.confirm__ok:hover { background:var(--invert-bg-hover); }
/* Destructive asks wear the alarm colour — but only those. */
.confirm__ok--danger { background:var(--brand); }
.confirm__ok--danger:hover { background:var(--brand-dark); }
@keyframes confirmIn { from { opacity:0; transform:translateY(8px) scale(.98); } to { opacity:1; transform:none; } }
@media (prefers-reduced-motion: reduce) { .confirm__inner { animation:none; } }

/* ------------------------------------------------------------------ toasts */
/* Confirmation of something the server actually did. Bottom-right, out of the
   way of the content, above the modal scrim so it is readable either way. */
.toasts {
  position:fixed; right:20px; bottom:20px; z-index:60;
  display:flex; flex-direction:column; gap:var(--s-4); align-items:flex-end;
  pointer-events:none; max-width:min(420px, calc(100vw - 40px));
}
.toast {
  pointer-events:auto; position:relative;
  background:var(--toast-bg); color:#eef2f4; border:1px solid var(--toast-line); border-radius:var(--r-lg);
  padding:var(--s-5) 40px var(--s-5) var(--s-6); font-size:var(--t-ui); line-height:1.5;
  box-shadow:var(--shadow-popover);
  border-left:3px solid var(--ok);
  animation:toastIn .22s cubic-bezier(.22,.61,.36,1) both;
}
.toast--error { border-left-color:#e5677f; }
.toast--out { animation:toastOut .25s ease forwards; }
.toast__x {
  position:absolute; top:8px; right:8px; background:none; border:none; cursor:pointer;
  color:#8d99a1; font-size:var(--ico); line-height:1; padding:var(--s-2) var(--s-3); border-radius:var(--r-sm);
}
.toast__x:hover { color:#fff; background:rgba(255,255,255,.12); }
@keyframes toastIn  { from { opacity:0; transform:translateY(10px); } to { opacity:1; transform:none; } }
@keyframes toastOut { to { opacity:0; transform:translateY(6px); } }

/* ------------------------------------------------------------------ motion */
@keyframes psFocus { 0% { opacity:0; transform:scale(1.35); } 100% { opacity:1; transform:scale(1); } }
@keyframes rise { 0% { opacity:0; transform:translateY(14px); } 100% { opacity:1; transform:translateY(0); } }
.reveal { animation:rise .5s cubic-bezier(.22,.61,.36,1) both; }

/* Modal open / in-place re-render / close, via View Transitions. The default
   cross-fade already morphs the modal's size, so adding a session grows the
   panel smoothly instead of jumping; a calm duration is all that is needed. */
::view-transition-group(cardmodal) { animation-duration:.3s; animation-timing-function:cubic-bezier(.22,.61,.36,1); }

/* Top progress bar — the in-flight state for every htmx request. */
.progress {
  position:fixed; top:0; left:0; height:2px; width:0; z-index:60; pointer-events:none;
  background:linear-gradient(to right, var(--brand), #b03050);
  opacity:0; transition:width .3s ease, opacity .25s ease;
  box-shadow:0 0 8px rgba(128,0,32,.4);
}
.progress.on { opacity:1; }

/* In-flight buttons: dimmed and inert while their request runs, so a click reads
   as "received" and cannot be fired twice. htmx sets .htmx-request on the
   element that triggered the request. */
.htmx-request.btn, .htmx-request button, button.htmx-request,
.htmx-request.cardmenu__item, .htmx-request.fieldform__save { opacity:.55; pointer-events:none; }

@keyframes spin { to { transform:rotate(360deg); } }
/* .modal__savestatus's two states (David, 2026-09-08 wanted a "saving…"
 * indicator; 2026-09-09: it must live in ONE place and never move anything).
 * JS (render.ts) toggles these classes for every title/description/due-date
 * save; the element sits in the fixed-height .modal__foot bar, so the only
 * thing that ever changes here is its own opacity and text. */
@keyframes saveIn { from { opacity:0; transform:translateY(3px); } to { opacity:1; transform:none; } }
.modal__savestatus {
  display:inline-flex; align-items:center; gap:var(--s-3); font-size:var(--t-caption); font-weight:600;
  color:var(--faint); opacity:0; transition:opacity .15s ease;
}
.modal__savestatus.is-saving, .modal__savestatus.is-saved { opacity:1; }
.modal__savestatus.is-saving::before {
  content:""; display:block; width:12px; height:12px; border-radius:50%;
  border:2px solid var(--line-strong); border-top-color:var(--brand-ink); animation:spin .6s linear infinite;
}
.modal__savestatus.is-saved { color:var(--ok); animation:saveIn .18s ease both; }
.modal__savestatus.is-saved::before { content:"\2713"; font-weight:700; }

/* View Transitions (globalViewTransitions): let the root cross-fade run at a
   calm pace. The fixed chrome gets its own stable transition groups so it holds
   still while page content swaps beneath it, rather than flickering as part of
   the root snapshot. */
::view-transition-group(root), ::view-transition-old(root), ::view-transition-new(root) { animation-duration:.28s; }
.sidebar { view-transition-name:sidebar; }
.pagehead { view-transition-name:pagehead; }
.progress { view-transition-name:progressbar; }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior:auto; }
  .reveal, .modal, .modal-scrim, .scrim, .psmark span, .check__fill, .toast, .progress { animation:none !important; transition:none !important; }
  ::view-transition-group(*), ::view-transition-old(*), ::view-transition-new(*) { animation:none !important; }
}

/* ------------------------------------------------------------------ responsive */
@media (max-width: 960px) {
  .cockpit { grid-template-columns:minmax(0,1fr); }
  /* Once the two columns stack, DOM order becomes visual order — and the
     source order (Today, This week, Next week, then Overdue/Waiting/Unmatched
     in the second column) put the browseable week-ahead lists ahead of the
     things that actually need attention today. David, 2026-09-09: "move this
     week and next week to the bottom of the dashboard" on mobile. `.col`
     unwraps to `display:contents` so every panel becomes a direct grid item
     of .cockpit itself — only then can each one's own `order` take effect;
     `order` has no power across a nested container it isn't a sibling within. */
  .col { display:contents; }
  .cockpit__today { order:1; }
  .cockpit__overdue { order:2; }
  .cockpit__waiting { order:3; }
  .cockpit__inbox { order:4; }
  .weekpanel { order:5; }
}
@media (max-width: 720px) {
  .pagehead { padding:var(--s-5) var(--s-6); }
  .modal__body { padding:var(--s-7) var(--s-7) 36px; }
  .modal__head { padding-left:var(--s-7); }
}

/* ---- session link + free/busy ---- */
.dsess__right { display:flex; align-items:center; gap:var(--s-4); }
.dsess__link {
  font-size:var(--t-caption); font-weight:600; color:var(--navy-ink); text-decoration:none;
  padding:var(--s-2) var(--s-4); border:1px solid var(--band-cool); border-radius:var(--r-pill); background:var(--surface);
  white-space:nowrap; transition:background .12s, border-color .12s;
}
.dsess__link:hover { background:var(--band-cool); border-color:var(--band-cool-line); color:var(--navy-ink); }

.inboxrow__kind { flex:none; font-size:var(--t-micro); font-weight:700; text-transform:uppercase; letter-spacing:.08em; color:var(--faint-soft); white-space:nowrap; }

/* Settings page — calendar ignore rules (David, 2026-09-07), reusing the old
 * Inbox "Ignored" list's row design now that it lives on its own page. */
.settingsrow {
  display:flex; align-items:center; justify-content:space-between; gap:var(--s-4);
  background:var(--bg); border:1px solid var(--line-soft); border-radius:var(--r); padding:var(--s-4) var(--s-5);
}
.settingsrow__name { font-size:var(--t-secondary); color:var(--ink); display:flex; align-items:center; gap:var(--s-4); }
.settingsrow__tag { font-size:var(--t-micro); text-transform:uppercase; letter-spacing:.08em; color:var(--faint-soft); }
.settingsrow__block { display:flex; flex-direction:column; gap:2px; min-width:0; }
.settingsrow__meta { font-size:var(--t-micro); color:var(--faint-soft); }

/* Settings — change password (David, 2026-09-30). */
.settingsform label { display:block; font-size:var(--t-secondary); font-weight:600; color:var(--ink); margin-bottom:var(--s-4); }
.settingsform label:not(:first-child) { margin-top:var(--s-5); }
.settingsform input[type="password"] { width:100%; max-width:320px; font-size:var(--t-ui); color:var(--ink); border:1px solid var(--line-strong); border-radius:var(--r); padding:var(--s-4) var(--s-5); background:var(--surface); }
.settingsform input[type="password"]:focus-visible { outline:2px solid var(--focus); outline-offset:1px; }
.settingsform__actions { margin-top:var(--s-6); }

/* ---- today's agenda (the real calendar) ---- */
.sess--plain { cursor:default; color:var(--muted); }
.sess--plain:hover { box-shadow:none; border-color:var(--line); }

/* ------------------------------------------------------------ touch pass (2026-09-09)
 * A device with no pointer that can hover never satisfies :hover — not "hovers
 * briefly on tap" the way some mobile browsers fake for a plain <a>, but never
 * at all for a styled trigger like these. Four controls in the app were built
 * opacity:0-until-:hover as their ONLY reveal (confirmed live at 375px, mobile
 * emulation with hover disabled): the backlog/search card's quick-schedule
 * button, the Linked-cards unlink ×, and the checklist row's edit/delete/
 * reorder cluster. Each is still technically tappable at opacity 0 — pointer-
 * events aren't disabled — but invisible, so a phone user has no way to know
 * it's there to tap. (hover: none) is the right test, not a width breakpoint:
 * it is specifically "does this pointer support hovering", true for a mouse
 * regardless of window width and false for touch regardless of screen size,
 * so a touch laptop at a wide viewport is fixed too and a narrow desktop
 * browser window keeps the quieter hover-reveal it already had. Placed last so
 * it wins the cascade over each rule above at equal specificity. */
@media (hover: none) {
  .qsched__btn,
  .link__del,
  .check__del,
  .check__edit,
  .check__reorder {
    opacity: 1;
  }
}

/* The Projects/"Add project" colour picker's swatches are 22×22px with a
 * ~4px gap — confirmed live at 375px, a real mis-tap risk between two
 * adjacent swatches for a fingertip. `.swpick__row` already wraps
 * (flex-wrap:wrap), so growing the swatches just adds a row rather than
 * overflowing — there was room to fix this without a layout rethink. Not
 * pushed all the way to 44px: at that size the ~15-colour row would wrap
 * three or four deep inside the "···" project menu's already-tall panel, a
 * worse trade than a still-much-improved 34px. */
@media (max-width: 640px) {
  .swpick { width:34px; height:34px; }
  .swpick__row { gap:var(--s-4); }
}

/* Boards: the in-place date field on rows of a stage listed by date
   (David, 2026-10-02: dates there "constantly shift"). A native date input
   dressed as the date chip so changing a date is one click and a pick. */
.planrow__datefield { margin:0; }
.planrow__datefield input[type="date"] {
  font:inherit; font-size:var(--t-caption); font-weight:600; color:var(--muted);
  background:var(--neutral-fill); border:1px solid transparent; border-radius:var(--r-pill);
  padding:var(--s-2) var(--s-5); width:auto; cursor:pointer;
}
.planrow__datefield input[type="date"]:hover { border-color:var(--line-strong); }
.planrow__datefield input[type="date"]:focus-visible { outline:2px solid var(--focus); outline-offset:1px; }
.planrow__datefield input[type="date"]:invalid, .planrow__datefield input[type="date"][value=""] { color:var(--faint); background:transparent; border:1px dashed var(--dash-line); }
.planrow__datefield--late input[type="date"] { background:var(--brand); color:#fff; }

/* ------------------------------------------------------------ boards (David, 2026-10-02)
 * The Settings form controls and the board editor. The editor is a disclosure
 * in the same shape as Published/Dropped, holding plain forms: names and end
 * labels, then one row per stage (order arrows, name, list-by-date, save, make
 * intake, remove), then an add-stage row and a quiet delete. */
.settingsform input[type="text"],
.settingsform select { width:100%; max-width:320px; font-size:var(--t-ui); color:var(--ink); border:1px solid var(--line-strong); border-radius:var(--r); padding:var(--s-4) var(--s-5); background:var(--surface); }
.settingsform input[type="text"]:focus-visible,
.settingsform select:focus-visible { outline:2px solid var(--focus); outline-offset:1px; }
.settingsform .hint { font-weight:400; color:var(--faint); }
.boardedit { margin-top:var(--s-4); }
.boardedit__body { margin-top:var(--s-5); padding:var(--s-6); background:var(--surface); border:1px solid var(--line-soft); border-radius:var(--r); display:flex; flex-direction:column; gap:var(--s-5); }
.boardedit__error { color:var(--brand-ink); font-weight:600; margin:0; }
.boardedit__h { font-family:var(--font-head); font-size:var(--t-body); color:var(--heading); margin:var(--s-4) 0 0; }
.boardedit__stages { display:flex; flex-direction:column; gap:var(--s-4); }
.boardedit__stage { display:flex; align-items:center; flex-wrap:wrap; gap:var(--s-4) var(--s-5); padding:var(--s-4) var(--s-5); background:var(--bg); border:1px solid var(--line-soft); border-radius:var(--r); }
.boardedit__order { display:flex; flex:none; }
.boardedit__order form { display:flex; }
.iconbtn--off { display:inline-block; width:calc(var(--ico) + var(--s-4) * 2); }
.boardedit__fields { display:flex; align-items:center; flex-wrap:wrap; gap:var(--s-4) var(--s-5); flex:1 1 280px; margin:0; }
.boardedit__fields input[type="text"] { flex:1 1 160px; min-width:0; font-size:var(--t-ui); color:var(--ink); border:1px solid var(--line-strong); border-radius:var(--r); padding:var(--s-3) var(--s-4); background:var(--surface); }
.boardedit__fields input[type="text"]:focus-visible { outline:2px solid var(--focus); outline-offset:1px; }
.boardedit__chk { display:inline-flex; align-items:center; gap:var(--s-3); font-size:var(--t-secondary); color:var(--muted); white-space:nowrap; cursor:pointer; }
.boardedit__extra { display:flex; align-items:center; flex-wrap:wrap; gap:var(--s-3) var(--s-5); flex:1 1 100%; justify-content:flex-end; }
.boardedit__extra form { margin:0; display:flex; align-items:center; gap:var(--s-4); }
.boardedit__intake { display:inline-flex; align-items:center; gap:var(--s-3); font-size:var(--t-caption); font-weight:600; color:var(--ok); }
.boardedit__intake .ico { font-size:14px; }
.boardedit__moveto { display:inline-flex; align-items:center; gap:var(--s-3); font-size:var(--t-caption); color:var(--muted); }
.boardedit__moveto select { font-size:var(--t-secondary); border:1px solid var(--line-strong); border-radius:var(--r); padding:var(--s-2) var(--s-3); background:var(--surface); color:var(--ink); }
.boardedit__add { padding:var(--s-4) var(--s-5); border:1px dashed var(--line-strong); border-radius:var(--r); }
.boardedit__danger { margin-top:var(--s-4); }
.boardedit__danger > summary { cursor:pointer; font-size:var(--t-caption); font-weight:600; color:var(--faint); list-style:none; padding:var(--s-3) 0; }
.boardedit__danger > summary::-webkit-details-marker { display:none; }
.boardedit__danger > summary:hover { color:var(--brand-ink); }
.boardedit__danger form { margin-top:var(--s-4); }
@media (max-width: 640px) {
  .boardedit__body { padding:var(--s-4); }
  .boardedit__extra { justify-content:flex-start; }
}
.settingsrow .hint { font-size:var(--t-caption); color:var(--faint); text-align:right; }

/* Board icon picker (David, 2026-10-04): the curated Material icons as a grid of
 * radio buttons. The radio itself is visually hidden but stays focusable and
 * keyboard-operable (arrow keys move through the group); the chosen face is
 * ringed, and focus shows the same ring. */
.sr-only { position:absolute; width:1px; height:1px; margin:-1px; padding:0; overflow:hidden; clip:rect(0 0 0 0); white-space:nowrap; border:0; }
.iconpick { border:none; padding:0; margin:var(--s-5) 0 0; min-width:0; }
.iconpick legend { font-size:var(--t-secondary); font-weight:600; color:var(--ink); padding:0; margin-bottom:var(--s-4); }
.iconpick__grid { display:grid; grid-template-columns:repeat(auto-fill, minmax(40px, 1fr)); gap:var(--s-3); max-width:520px; }
.iconpick__opt { position:relative; display:block; margin:0 !important; cursor:pointer; }
.iconpick__opt input { position:absolute; opacity:0; inset:0; margin:0; cursor:pointer; }
.iconpick__face { display:flex; align-items:center; justify-content:center; height:40px; border:1px solid var(--line-soft); border-radius:var(--r); background:var(--bg); color:var(--muted); font-size:22px; transition:background .12s, color .12s, border-color .12s; }
.iconpick__opt:hover .iconpick__face { background:var(--line-soft); color:var(--heading); }
.iconpick__opt input:checked + .iconpick__face { border-color:var(--brand); background:var(--surface); color:var(--brand-ink); box-shadow:0 0 0 1px var(--brand); }
.iconpick__opt input:focus-visible + .iconpick__face { outline:2px solid var(--focus); outline-offset:2px; }
.settingsrow__name .ico { font-size:16px; color:var(--muted); }

/* Live updates (David, 2026-10-05): the note shown when a refresh is being held
 * back because someone is typing. A polite live region that always exists and is
 * hidden while empty; centred along the bottom (the toasts sit bottom-right, a
 * card's save status bottom-right of its modal). Centred with margin:auto rather
 * than a transform, which the entrance animations elsewhere would override. */
.livenote {
  position:fixed; left:0; right:0; bottom:20px; margin:0 auto; z-index:61; width:max-content; max-width:calc(100vw - 40px);
  background:var(--toast-bg); color:#eef2f4; border:1px solid var(--toast-line); border-radius:var(--r-lg);
  padding:var(--s-4) var(--s-6); font-size:var(--t-ui); line-height:1.5; box-shadow:var(--shadow-popover);
  border-left:3px solid var(--ok);
}
.livenote:empty { display:none; }
.livenote__btn { background:none; border:none; padding:0 0 0 var(--s-3); font:inherit; font-weight:600; color:#fff; text-decoration:underline; cursor:pointer; }
.livenote__btn:hover { color:#e5c07b; }
.livenote__btn:focus-visible { outline:2px solid #fff; outline-offset:2px; border-radius:2px; }

/* A refused stale save (David, 2026-10-05): the form re-opens with the person's own
 * text and this line explaining why. Brand-ink on a left rule, like the other
 * form-level messages; the form itself keeps its layout. */
.fieldform__stale { margin:0 0 var(--s-4); padding:var(--s-3) var(--s-4); border-left:3px solid var(--brand); background:var(--surface); color:var(--brand-ink); font-size:var(--t-secondary); line-height:1.45; border-radius:0 var(--r-sm) var(--r-sm) 0; }
.fieldform--inline .fieldform__stale { flex:1 1 100%; }
