/* LUCID — every rule in the app.
 *
 * Lifted out of index.html on 24 August 2026, unchanged. The file was
 * 29,699 lines with 4,500 of them CSS, and finding a rule meant scrolling
 * past a fifth of the application to reach the markup that uses it.
 *
 * NO BUILD STEP. Matt deploys by dragging a folder into Netlify, so this is
 * a plain stylesheet the page links to — not something a bundler assembles.
 * Splitting it any further would mean more requests on a phone for no gain.
 */
  :root{
    /* DARK (default) — muted-card + contained-glow language: each hue stays
       flat/desaturated as a surface color, with a small bright glow pool
       anchored near it rather than washing the whole element. */
    --bg-mid:#080A1A; --bg-base:#050710; --bg-deep:#020306;
    --bloom-1: rgba(61,107,255,0.12); --bloom-2: rgba(160,125,255,0.08);
    --bloom-3: rgba(255,154,68,0.06); --bloom-4: rgba(184,225,74,0.06);
    --glass-bg: rgba(28,40,86,0.44);
    --glass-border: rgba(255,255,255,0.09); --glass-border-hi: rgba(255,255,255,0.20);
    --glass-shadow: 0 1px 2px rgba(0,0,0,0.35), 0 8px 18px -6px rgba(0,0,0,0.4), 0 28px 56px -16px rgba(0,0,0,0.55), inset 0 1px 0 rgba(255,255,255,0.06);
    --glass-sheen: linear-gradient(180deg, rgba(255,255,255,0.09), rgba(255,255,255,0) 42%);
    --glass-blur: 24px; --glass-sat: 160%;
    /* ---- MOTION SYSTEM ----
       Everything that moves now resolves to these six values. Before this there
       were five ad-hoc durations (.1/.15/.2/.25/.28s) and easing was whatever
       `ease` happens to be — which is why nothing felt related to anything
       else: every component accelerated on its own curve. One decelerating
       curve for entrances (things arriving should slow as they land), one
       symmetric curve for travel between two states, one slight-overshoot
       spring for the few playful bits. */
    --ease-out: cubic-bezier(0.22, 1, 0.36, 1);
    --ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
    --ease-spring: cubic-bezier(0.34, 1.4, 0.64, 1);
    --dur-fast: 130ms;
    --dur-base: 220ms;
    --dur-slow: 320ms;
    /* The dissolve between pages. Deliberately shorter than --dur-base: a page
       transition is the one animation the eye is waiting on, so it should be
       over before it is noticed. Kept in step with VIEW_FADE_MS in JS. */
    --dur-view: 150ms;
    --text:#F4F6FD; --text-dim:#A6AFD4; --text-faint:#8C94BC;
    --ring: rgba(255,255,255,0.42); --ring-soft: rgba(255,255,255,0.22);
    --blue:#3D6BFF; --blue-2:#7C9CFF; --blue-glow: rgba(61,107,255,0.5);
    --green:#B8E14A; --green-2:#D3EE8E; --green-glow: rgba(184,225,74,0.45);
    --violet:#A07DFF; --violet-2:#D3C2FF; --violet-glow: rgba(160,125,255,0.42);
    --orange:#FF9A44; --orange-2:#FFBF87; --orange-glow: rgba(255,154,68,0.45);
    --pink:#FF5A47; --pink-2:#FF9A87; --pink-glow: rgba(255,90,71,0.42);
    --teal:#2DD4BF; --teal-2:#8FEFE3; --teal-glow: rgba(45,212,191,0.42);
    /* Contact/Organization category colors — deliberately separate from
       the shared account-color palette above (which Mail/Calendar use).
       Deeper jewel-tones instead of the brighter neon set: the glow itself
       provides the "pop," so the base fill doesn't need to be as vivid —
       reads as premium rather than a rave with six saturated hues glowing
       across a page of cards at once. */
    --cat-blue:#2954D9; --cat-blue-2:#6E8CE8; --cat-blue-glow: rgba(41,84,217,0.45);
    --cat-green:#3F9B6B; --cat-green-2:#7EC49E; --cat-green-glow: rgba(63,155,107,0.4);
    --cat-violet:#7B5FD9; --cat-violet-2:#AD98E8; --cat-violet-glow: rgba(123,95,217,0.4);
    --cat-orange:#D97D2E; --cat-orange-2:#EAA766; --cat-orange-glow: rgba(217,125,46,0.42);
    --cat-red:#B8382E; --cat-red-2:#D97066; --cat-red-glow: rgba(184,56,46,0.42);
    --cat-teal:#1F9E8E; --cat-teal-2:#5FC4B6; --cat-teal-glow: rgba(31,158,142,0.4);
    --ink:#031225;
    --pulse-lo: color-mix(in srgb, var(--blue) 25%, transparent);
    --pulse-hi: color-mix(in srgb, var(--blue) 48%, transparent);
    --neutral-1:#EDEFFC; --neutral-2:#ffffff; --neutral-ink:#0A0E1F; --neutral-glow: rgba(255,255,255,.2);
    --radius:16px;
    /* Accent — buttons/highlights follow the selected account's color;
       defaults to blue when "All accounts" is selected. Set at runtime
       by applyAccentColor() in the main script. */
    --accent: var(--blue); --accent-2: var(--blue-2); --accent-glow: var(--blue-glow);
  }

  /* ============================================================
     SURFACE TREATMENTS — three dark variants beyond the default.

     The DEFAULT dark theme separates card from page by LUMINANCE: the card
     is simply lighter than what's behind it. The two "edge-lit" themes below
     (midnight, obsidian) deliberately throw that away. Their cards sit at
     almost exactly the same darkness as the page, and depth comes entirely
     from modelled light instead:

       1. A bright top border (--glass-border-hi) acting as a rim/specular
          highlight, as if a light source sits above and in front.
       2. A directional light POOL replacing the old flat top sheen —
          --glass-sheen becomes a radial gradient anchored off the card's
          top-left corner, so every card looks lit from a consistent
          direction rather than uniformly tinted.
       3. A much deeper cast shadow, which is what actually sells elevation
          once the brightness cue is gone.

     FROSTED goes the opposite way: it keeps the ambient bloom HIGH (you need
     something behind the glass for frost to be frosted *of*), makes the card
     fill very translucent, and pushes backdrop blur from 24px to 44px so the
     background reads through as diffused colour.

     All four inherit every other token from dark — accents, the --cat-*
     jewel tones, all text — so nothing but the surfaces change.
     ============================================================ */

  /* MIDNIGHT — deep blue room, cards the same darkness as the room, edge-lit. */
  :root[data-theme="midnight"]{
    --bg-mid:#030718; --bg-base:#01040E; --bg-deep:#000206;
    --bloom-1: rgba(61,107,255,0.06); --bloom-2: rgba(160,125,255,0.035);
    --bloom-3: rgba(255,154,68,0.02); --bloom-4: rgba(184,225,74,0.02);
    --glass-bg: rgba(14,24,58,0.62);
    --glass-border: rgba(150,180,255,0.10); --glass-border-hi: rgba(150,185,255,0.44);
    --glass-shadow: 0 1px 2px rgba(0,0,0,0.6), 0 12px 26px -6px rgba(0,0,0,0.66), 0 40px 80px -20px rgba(0,0,0,0.85), 0 0 40px -12px rgba(61,107,255,0.16), inset 0 1px 0 rgba(160,190,255,0.14);
    --glass-sheen: radial-gradient(130% 90% at 14% -12%, rgba(140,175,255,0.17), rgba(140,175,255,0.045) 38%, rgba(255,255,255,0) 72%);
    --glass-blur: 26px; --glass-sat: 150%;
  }

  /* OBSIDIAN — the same idea with the blue taken out. Near-black, neutral,
     maximum separation between "unlit surface" and "lit edge". */
  :root[data-theme="obsidian"]{
    --bg-mid:#0A0A0D; --bg-base:#050506; --bg-deep:#000000;
    --bloom-1: rgba(61,107,255,0.035); --bloom-2: rgba(160,125,255,0.025);
    --bloom-3: rgba(255,154,68,0.015); --bloom-4: rgba(184,225,74,0.015);
    --glass-bg: rgba(23,24,29,0.66);
    --glass-border: rgba(255,255,255,0.07); --glass-border-hi: rgba(255,255,255,0.34);
    --glass-shadow: 0 1px 2px rgba(0,0,0,0.7), 0 14px 30px -8px rgba(0,0,0,0.75), 0 46px 90px -22px rgba(0,0,0,0.92), inset 0 1px 0 rgba(255,255,255,0.10);
    --glass-sheen: radial-gradient(130% 90% at 14% -12%, rgba(255,255,255,0.13), rgba(255,255,255,0.035) 38%, rgba(255,255,255,0) 72%);
    --glass-blur: 22px; --glass-sat: 130%;
  }

  /* ============================================================
     REGAL — the one that is a different REGISTER, not another palette.
     ============================================================
     Matt sent a reference and said: "I absolutely love this look… call it
     elegant or regal." What he liked was not a colour. It was restraint —
     deep bottle-green glass, a warm bloom well away from the content, hairline
     rules instead of borders, and a great deal of air.

     So this theme changes three things the other four do not:

       1. THE GROUND IS GREEN, not blue or neutral. Deep bottle glass. It is the
          only theme here whose ground has a hue you would name.
       2. ONE ACCENT, and it is BRASS. Every other theme inherits Lucid's five
          jewel accents and uses whichever the surface calls for. This one
          overrides --accent to aged brass and leaves it there, so a page has a
          single warm note on a lot of quiet instead of five competing ones.
          That is the whole difference between "elegant" and "colourful".
       3. HAIRLINES, NOT EDGES. The border drops to a barely-there warm line and
          the lit edge is halved. Depth comes from the shadow and the bloom
          rather than from a drawn outline.

     The bloom is warm and deliberately weak — it sits behind the glass as a
     hint of a lamp somewhere off-page, which is what stops a dark green room
     from reading as a terminal.

     Text tokens are NOT overridden. They were measured against every theme and
     this ground is darker than dark's, so they pass here by construction — see
     tests/test-readability.js, which now runs this theme too. */
  :root[data-theme="regal"]{
    --bg-mid:#0A1512; --bg-base:#05100D; --bg-deep:#020706;
    /* Green-tinted ink. The inherited lilac dim measured 4.46:1 here — under
       the 4.5 floor — so the ramp is re-stepped against this ground, not
       borrowed. #B0BEB5 measures 5.0:1 on it. Dark ink on the brass fill,
       because brass is light and white on it measured 3.51:1. */
    --text:#F2F6F1; --text-dim:#B0BEB5; --text-faint:#98A79D;
    --on-accent:#0A1512;
    /* Warm, faint, and placed away from the middle: a lamp off-page rather
       than a light behind the content. */
    --bloom-1: rgba(214,168,96,0.055); --bloom-2: rgba(120,180,150,0.035);
    --bloom-3: rgba(214,168,96,0.030); --bloom-4: rgba(80,140,120,0.020);
    --glass-bg: rgba(16,36,30,0.62);
    /* A hairline, not a border. Half the lit edge of the other dark themes. */
    --glass-border: rgba(220,200,160,0.085); --glass-border-hi: rgba(226,201,148,0.26);
    --glass-shadow: 0 1px 2px rgba(0,0,0,0.62), 0 14px 32px -8px rgba(0,0,0,0.7),
                    0 48px 96px -24px rgba(0,0,0,0.9), 0 0 46px -14px rgba(214,168,96,0.10),
                    inset 0 1px 0 rgba(226,201,148,0.09);
    --glass-sheen: radial-gradient(130% 90% at 16% -14%, rgba(226,201,148,0.10),
                    rgba(226,201,148,0.028) 40%, rgba(255,255,255,0) 74%);
    --glass-blur: 28px; --glass-sat: 140%;

    /* ONE ACCENT. Brass, and only brass. */
    --brass:#D6A860; --brass-2:#EFD6A6; --brass-glow: rgba(214,168,96,0.40);
    --accent: var(--brass); --accent-2: var(--brass-2); --accent-glow: var(--brass-glow);
    /* The two that read as "this is happening now" follow the accent, or the
       page grows a second warm colour that means nothing different. */
    --pulse-lo: color-mix(in srgb, var(--brass) 22%, transparent);
    --pulse-hi: color-mix(in srgb, var(--brass) 44%, transparent);
    /* Blue is Lucid's default accent and it is wrong in this room — it is the
       one hue that fights bottle green. Warmed toward the brass rather than
       replaced, so anything hard-coded to --blue still belongs here. */
    --blue:#8FB9A8; --blue-2:#BFDCCF; --blue-glow: rgba(143,185,168,0.34);
    --violet:#B9A2D8; --violet-2:#DCCEEE; --violet-glow: rgba(185,162,216,0.30);
    --teal:#6FBFA8; --teal-2:#A9DCCC; --teal-glow: rgba(111,191,168,0.32);
    --green:#A8C878; --green-2:#CEE0AE; --green-glow: rgba(168,200,120,0.32);
    --pink:#D98C74; --pink-2:#EEBBAB; --pink-glow: rgba(217,140,116,0.32);
  }

  /* FROSTED — real frosted glass. Bloom stays high on purpose; the card fill
     is barely there and the heavy blur does the work. */
  :root[data-theme="frosted"]{
    --bg-mid:#0B1030; --bg-base:#070B22; --bg-deep:#03050F;
    --bloom-1: rgba(61,107,255,0.20); --bloom-2: rgba(160,125,255,0.15);
    --bloom-3: rgba(255,154,68,0.09); --bloom-4: rgba(184,225,74,0.08);
    /* A 10% wash of a LIGHT blue over a 190%-saturated 44px blur made this
       theme's panels far brighter than any other dark theme's — bright enough
       that the shared quiet-text tokens landed at 3.1–3.7:1 on them. Deepening
       the fill rather than lifting the text is the fix that keeps a hierarchy:
       pushing "faint" up to pass here would have made it brighter than "dim". */
    --glass-bg: rgba(22,32,76,0.42);
    --glass-border: rgba(255,255,255,0.16); --glass-border-hi: rgba(255,255,255,0.40);
    --glass-shadow: 0 1px 2px rgba(0,0,0,0.3), 0 10px 24px -8px rgba(0,0,0,0.4), 0 32px 64px -18px rgba(0,0,0,0.5), inset 0 1px 0 rgba(255,255,255,0.18);
    --glass-sheen: linear-gradient(180deg, rgba(255,255,255,0.16), rgba(255,255,255,0.02) 46%, rgba(255,255,255,0) 100%);
    --glass-blur: 44px; --glass-sat: 190%;
    /* Frosted's panels are a 44px blur at 190% saturation over a 10% fill —
       far brighter than the other dark themes' glass, and the shared faint
       token measured 3.07:1 against them. A brighter ground needs lighter
       lettering; this is the only theme where that is true in this direction. */
    --text-dim:#B6BDDA; --text-faint:#A9B1D2;
  }
  :root[data-theme="light"]{
    --bg-mid:#FBF7F0; --bg-base:#F5EFE4; --bg-deep:#EFE7D8;
    --bloom-1: rgba(110,160,255,0.16); --bloom-2: rgba(180,150,255,0.14);
    --bloom-3: rgba(255,180,120,0.13); --bloom-4: rgba(150,210,120,0.14);
    --glass-bg: rgba(255,255,255,0.55);
    --glass-border: rgba(30,24,20,0.08); --glass-border-hi: rgba(30,24,20,0.16);
    --glass-shadow: 0 1px 2px rgba(60,45,80,0.05), 0 6px 14px -5px rgba(60,45,80,0.1), 0 22px 44px -12px rgba(60,45,80,0.16), inset 0 1px 0 rgba(255,255,255,0.75);
    --glass-sheen: linear-gradient(180deg, rgba(255,255,255,0.65), rgba(255,255,255,0) 46%);
    --glass-blur: 24px; --glass-sat: 160%;
    --text:#241E30; --text-dim:#655D74; --text-faint:#666275;
    --ring: rgba(35,28,45,0.32); --ring-soft: rgba(35,28,45,0.16);
    --blue:#647653; --blue-2:#C3D6AD; --blue-glow: rgba(195,214,173,0.38);
    --green:#537A2C; --green-2:#B7E08C; --green-glow: rgba(183,224,140,0.35);
    --violet:#7C51DE; --violet-2:#CBB4FF; --violet-glow: rgba(203,180,255,0.32);
    --orange:#A55B14; --orange-2:#FFC98A; --orange-glow: rgba(255,201,138,0.32);
    --pink:#C63C2A; --pink-2:#FFB09E; --pink-glow: rgba(255,176,158,0.32);
    --teal:#0C7B71; --teal-2:#9EE5DC; --teal-glow: rgba(158,229,220,0.35);
    --cat-blue:#3E5FB0; --cat-blue-2:#8FA5D6; --cat-blue-glow: rgba(143,165,214,0.35);
    --cat-green:#4A7D42; --cat-green-2:#9BC08E; --cat-green-glow: rgba(155,192,142,0.32);
    --cat-violet:#6B4FBD; --cat-violet-2:#B39FE0; --cat-violet-glow: rgba(179,159,224,0.3);
    --cat-orange:#A65A1C; --cat-orange-2:#E0A363; --cat-orange-glow: rgba(224,163,99,0.32);
    --cat-red:#A8402E; --cat-red-2:#D6907F; --cat-red-glow: rgba(214,144,127,0.3);
    --cat-teal:#1C7A6E; --cat-teal-2:#7FBBB1; --cat-teal-glow: rgba(127,187,177,0.32);
    --pulse-lo: rgba(195,214,173,0.24); --pulse-hi: rgba(195,214,173,0.42);
    --ink:#031225;
    --neutral-1:#3A3345; --neutral-2:#221C2E; --neutral-ink:#FBF7F0; --neutral-glow: rgba(40,32,50,.14);
  }

  /* ============================================================
     SERENE — the light themes.
     ============================================================

     The brief was "pastels, light creamy goodness", and the trap it named was
     the obvious approach: invert Midnight's tokens and ship it. That produces
     what every app's light mode looks like — grey-white, cold, clinical. It
     would earn the name Serene about as well as a hospital corridor does.

     Midnight works because it has a language, not just a darkness: cards the
     same value as the room, depth from a lit top edge and a contained pool of
     light in one corner. Serene needs its own language rather than a negation
     of that one, and it is this:

       WARMTH INSTEAD OF WHITE.  Every surface sits on the warm side of
       neutral. Nothing here is #fff. Warmth is most of what separates
       "serene" from "blank", and it is the first thing lost by inverting.

       SHADOW INSTEAD OF GLOW.  Midnight's contained glow pools have no light
       equivalent — light does not pool on paper. The counterpart is a soft,
       warm, low-contrast cast shadow: cards as paper resting on paper. The
       shadows are TINTED, never grey. A neutral shadow on a warm ground reads
       as dirt.

       PASTELS CARRY THE HUE, NOT THE CONTRAST.  This is the part that goes
       wrong. Pastel-on-cream is where accessibility dies, so the pastel lives
       in the -2 and -glow tokens — gradients, halos, fills behind dark text —
       while the base token stays saturated enough to be read as text on
       cream. Every base below clears 4.5:1 against its own background;
       tests/test-serene-contrast.js fails the build if one stops.

     THREE OF THEM, because the brief asked for interpretations to compare
     rather than one "correct" answer, and because "serene" is a feeling that
     could reasonably mean any of these three. Each block is deliberately
     self-contained — some duplication, but each can be adjusted without
     working out what it inherits.
  */

  /* ------------------------------------------------------------------
     TWO AXES, NOT THREE THEMES.

     Serene shipped as three fixed themes. Matt's reaction was the useful
     kind: "the first one is the base one, but I also really like the third
     one in green" — which is not indecision, it is two separate preferences
     that had been welded together.

     They are separate things. The GROUND is warmth, texture and the colour
     of the shadow a card casts. The PALETTE is which six hues the accounts
     and categories draw from. Nothing makes them one choice except that they
     were written as one block.

     So they are now two attributes, and any of the nine combinations works.
     The three original themes survive as the three matched pairs — they are
     what the presets select — and Linen paper with the Cool palette, which
     is the one he actually asked for, is simply another of the nine.

     Every combination is checked: tests/test-serene-contrast.js walks the
     whole 3x3 grid rather than the three presets, because a palette that
     passes on its own ground and fails on another would be a failure nobody
     would find until it was picked.
     ------------------------------------------------------------------ */

  /* LINEN — unbleached paper. The quietest ground: warm ivory, and shadows
     tinted toward its own brown so a card reads as paper resting on paper. */
  :root[data-paper="linen"]{
    --bg-mid: #FAF6EC;
    --bg-base: #F4EEE0;
    --bg-deep: #ECE4D2;
    --bloom-1: rgba(126,146,181,0.10);
    --bloom-2: rgba(168,146,186,0.09);
    --bloom-3: rgba(214,166,116,0.10);
    --bloom-4: rgba(150,175,138,0.09);
    --glass-bg: rgba(255,253,248,0.78);
    --glass-border: rgba(74,58,40,0.10);
    --glass-border-hi: rgba(74,58,40,0.18);
    --glass-shadow: 0 1px 2px rgba(92,72,48,0.07), 0 4px 10px -3px rgba(92,72,48,0.09), 0 18px 36px -12px rgba(92,72,48,0.14), inset 0 1px 0 rgba(255,255,255,0.85);
    --glass-sheen: linear-gradient(180deg, rgba(255,255,255,0.55), rgba(255,255,255,0) 52%);
    --glass-blur: 18px;
    --glass-sat: 128%;
    --text: #2E2620;
    --text-dim: #6B5F52;
    --text-faint: #6F6559;
    --ring: rgba(46,38,32,0.30);
    --ring-soft: rgba(46,38,32,0.14);
    --ink: #2E2620;
    --neutral-1: #4A3F36;
    --neutral-2: #2E2620;
    --neutral-ink: #FAF6EC;
    --neutral-glow: rgba(74,63,54,.14);
    --orb-halo: rgba(150,130,110,0.20);
    --orb-shadow: 0 8px 20px rgba(92,72,48,0.20), 0 2px 5px rgba(92,72,48,0.14);
  }

  /* BLUSH — the warmest ground, and the closest to the words "creamy
     goodness". Off ivory toward peach, with the shadow tinted further into
     red so it stays part of the same page rather than sitting in grey. */
  :root[data-paper="blush"]{
    --bg-mid: #FDF5EF;
    --bg-base: #F9EBE1;
    --bg-deep: #F2DFD2;
    --bloom-1: rgba(198,156,146,0.12);
    --bloom-2: rgba(186,152,178,0.10);
    --bloom-3: rgba(232,176,128,0.13);
    --bloom-4: rgba(158,182,150,0.09);
    --glass-bg: rgba(255,252,249,0.80);
    --glass-border: rgba(96,58,44,0.09);
    --glass-border-hi: rgba(96,58,44,0.17);
    --glass-shadow: 0 1px 2px rgba(122,74,54,0.07), 0 4px 10px -3px rgba(122,74,54,0.10), 0 18px 38px -12px rgba(122,74,54,0.15), inset 0 1px 0 rgba(255,255,255,0.88);
    --glass-sheen: linear-gradient(180deg, rgba(255,255,255,0.60), rgba(255,255,255,0) 52%);
    --glass-blur: 20px;
    --glass-sat: 136%;
    --text: #33241E;
    --text-dim: #755E52;
    --text-faint: #736255;
    --ring: rgba(51,36,30,0.30);
    --ring-soft: rgba(51,36,30,0.14);
    --ink: #33241E;
    --neutral-1: #54403A;
    --neutral-2: #33241E;
    --neutral-ink: #FDF5EF;
    --neutral-glow: rgba(84,64,58,.14);
    --orb-halo: rgba(190,140,120,0.22);
    --orb-shadow: 0 8px 22px rgba(122,74,54,0.22), 0 2px 5px rgba(122,74,54,0.15);
  }

  /* MIST — the cool argument. Pale sage and stone rather than cream: the
     calm of an early morning room rather than of paper. Included because the
     brief assumed warmth, and that was worth testing rather than agreeing
     with — and it is the one Matt picked the green out of. */
  :root[data-paper="mist"]{
    --bg-mid: #F6F7F2;
    --bg-base: #EDF0E9;
    --bg-deep: #E2E7DD;
    --bloom-1: rgba(130,158,178,0.12);
    --bloom-2: rgba(158,150,186,0.09);
    --bloom-3: rgba(206,180,140,0.07);
    --bloom-4: rgba(146,178,146,0.12);
    --glass-bg: rgba(253,254,251,0.80);
    --glass-border: rgba(42,58,48,0.10);
    --glass-border-hi: rgba(42,58,48,0.18);
    --glass-shadow: 0 1px 2px rgba(52,72,60,0.07), 0 4px 10px -3px rgba(52,72,60,0.09), 0 18px 36px -12px rgba(52,72,60,0.14), inset 0 1px 0 rgba(255,255,255,0.90);
    --glass-sheen: linear-gradient(180deg, rgba(255,255,255,0.58), rgba(255,255,255,0) 52%);
    --glass-blur: 20px;
    --glass-sat: 124%;
    --text: #232B26;
    --text-dim: #5C6660;
    --text-faint: #616762;
    --ring: rgba(35,43,38,0.30);
    --ring-soft: rgba(35,43,38,0.14);
    --ink: #232B26;
    --neutral-1: #3C4740;
    --neutral-2: #232B26;
    --neutral-ink: #F6F7F2;
    --neutral-glow: rgba(60,71,64,.14);
    --orb-halo: rgba(120,145,140,0.20);
    --orb-shadow: 0 8px 20px rgba(52,72,60,0.20), 0 2px 5px rgba(52,72,60,0.14);
  }

  /* SOFT — chalky and dusty, like gouache on a warm ground. Pigment rather
     than ink. Pairs most naturally with Linen, which is where it came from. */
  :root[data-palette="soft"]{
    --blue: #4A6B93;
    --blue-2: #AFC4DC;
    --blue-glow: rgba(175,196,220,0.42);
    --green: #537648;
    --green-2: #B4CDA6;
    --green-glow: rgba(180,205,166,0.42);
    --violet: #6E5A96;
    --violet-2: #C3B4DC;
    --violet-glow: rgba(195,180,220,0.40);
    --orange: #99612D;
    --orange-2: #E5C29A;
    --orange-glow: rgba(229,194,154,0.44);
    --pink: #A85462;
    --pink-2: #E0B4BC;
    --pink-glow: rgba(224,180,188,0.40);
    --teal: #3A7973;
    --teal-2: #A5CBC6;
    --teal-glow: rgba(165,203,198,0.42);
    --cat-blue: #4A6B93;
    --cat-blue-2: #AFC4DC;
    --cat-blue-glow: rgba(175,196,220,0.36);
    --cat-green: #537648;
    --cat-green-2: #B4CDA6;
    --cat-green-glow: rgba(180,205,166,0.36);
    --cat-violet: #6E5A96;
    --cat-violet-2: #C3B4DC;
    --cat-violet-glow: rgba(195,180,220,0.34);
    --cat-orange: #99612D;
    --cat-orange-2: #E5C29A;
    --cat-orange-glow: rgba(229,194,154,0.36);
    --cat-red: #A14A3E;
    --cat-red-2: #DDB0A6;
    --cat-red-glow: rgba(221,176,166,0.34);
    --cat-teal: #3A7973;
    --cat-teal-2: #A5CBC6;
    --cat-teal-glow: rgba(165,203,198,0.36);
    --pulse-lo: rgba(175,196,220,0.26);
    --pulse-hi: rgba(175,196,220,0.46);
  }

  /* WARM — the same hues pushed toward apricot, rose and clay. The two cool
     hues are held further from the rest here than in the other palettes,
     because a warm palette is the one at risk of collapsing into one blur. */
  :root[data-palette="warm"]{
    --blue: #4F6A96;
    --blue-2: #B6C7E0;
    --blue-glow: rgba(182,199,224,0.44);
    --green: #55764C;
    --green-2: #B8CDAC;
    --green-glow: rgba(184,205,172,0.42);
    --violet: #7A5A93;
    --violet-2: #CFB6DC;
    --violet-glow: rgba(207,182,220,0.42);
    --orange: #A55B21;
    --orange-2: #F0C199;
    --orange-glow: rgba(240,193,153,0.48);
    --pink: #B04A57;
    --pink-2: #EFB0B7;
    --pink-glow: rgba(239,176,183,0.44);
    --teal: #377872;
    --teal-2: #A8CDC8;
    --teal-glow: rgba(168,205,200,0.42);
    --cat-blue: #4F6A96;
    --cat-blue-2: #B6C7E0;
    --cat-blue-glow: rgba(182,199,224,0.38);
    --cat-green: #55764C;
    --cat-green-2: #B8CDAC;
    --cat-green-glow: rgba(184,205,172,0.36);
    --cat-violet: #7A5A93;
    --cat-violet-2: #CFB6DC;
    --cat-violet-glow: rgba(207,182,220,0.36);
    --cat-orange: #A55B21;
    --cat-orange-2: #F0C199;
    --cat-orange-glow: rgba(240,193,153,0.40);
    --cat-red: #A8453F;
    --cat-red-2: #E8ADA6;
    --cat-red-glow: rgba(232,173,166,0.36);
    --cat-teal: #377872;
    --cat-teal-2: #A8CDC8;
    --cat-teal-glow: rgba(168,205,200,0.36);
    --pulse-lo: rgba(240,193,153,0.28);
    --pulse-hi: rgba(240,193,153,0.48);
  }

  /* COOL — sage, slate and deep teal. This is the green Matt picked out of
     Mist, and it is now available on any of the three papers. */
  :root[data-palette="cool"]{
    --blue: #41688C;
    --blue-2: #A8C3DB;
    --blue-glow: rgba(168,195,219,0.42);
    --green: #4F7A46;
    --green-2: #A9C8A0;
    --green-glow: rgba(169,200,160,0.42);
    --violet: #65588F;
    --violet-2: #BAB2D8;
    --violet-glow: rgba(186,178,216,0.40);
    --orange: #98642F;
    --orange-2: #DDBE96;
    --orange-glow: rgba(221,190,150,0.42);
    --pink: #9E5060;
    --pink-2: #D9AFB8;
    --pink-glow: rgba(217,175,184,0.40);
    --teal: #2E7971;
    --teal-2: #9AC8C2;
    --teal-glow: rgba(154,200,194,0.42);
    --cat-blue: #41688C;
    --cat-blue-2: #A8C3DB;
    --cat-blue-glow: rgba(168,195,219,0.36);
    --cat-green: #4F7A46;
    --cat-green-2: #A9C8A0;
    --cat-green-glow: rgba(169,200,160,0.36);
    --cat-violet: #65588F;
    --cat-violet-2: #BAB2D8;
    --cat-violet-glow: rgba(186,178,216,0.34);
    --cat-orange: #98642F;
    --cat-orange-2: #DDBE96;
    --cat-orange-glow: rgba(221,190,150,0.36);
    --cat-red: #96453C;
    --cat-red-2: #D2A69E;
    --cat-red-glow: rgba(210,166,158,0.34);
    --cat-teal: #2E7971;
    --cat-teal-2: #9AC8C2;
    --cat-teal-glow: rgba(154,200,194,0.36);
    --pulse-lo: rgba(168,195,219,0.26);
    --pulse-hi: rgba(168,195,219,0.46);
  }

  /* Swatch colours. Deliberately literal rather than var(--bg-mid): a swatch
     has to show the paper you are NOT currently using, so it cannot read from
     the tokens that are currently applied. These are the only duplicated
     values in the theme system, and test-serene-contrast.js checks they still
     match the blocks they represent. */
  [data-sw-paper="linen"]{ background:linear-gradient(135deg,#FAF6EC,#ECE4D2); }
  [data-sw-paper="blush"]{ background:linear-gradient(135deg,#FDF5EF,#F2DFD2); }
  [data-sw-paper="mist"] { background:linear-gradient(135deg,#F6F7F2,#E2E7DD); }
  [data-sw-palette="soft"]{ background:linear-gradient(100deg,#4A6B93 0 33%,#537648 33% 66%,#99612D 66%); }
  [data-sw-palette="warm"]{ background:linear-gradient(100deg,#4F6A96 0 33%,#B04A57 33% 66%,#A55B21 66%); }
  [data-sw-palette="cool"]{ background:linear-gradient(100deg,#41688C 0 33%,#4F7A46 33% 66%,#2E7971 66%); }

  /* ---- LITE: the same app, without the compositor melting -------------
     Only three things change, and none of them is layout or colour:
       · backdrop blurs become flat translucent fills
       · the orb's halo stops breathing
       · continuous decorative animation stops
     Everything keeps its position, its size and its palette, so switching
     between the two is close to invisible in a still frame. */
  /* A universal selector on purpose. The first version listed the six classes
     that were known to use a backdrop blur, and sixteen elements still had one
     — a panel style here, a `style="backdrop-filter:…"` on a floating button
     there. In a mode whose entire job is "draw nothing expensive", a list that
     can be incomplete is the wrong shape. This one cannot miss, it beats
     inline styles, and it costs nothing at all in the normal mode because the
     attribute is not there. */
  :root[data-fx="lite"] *{
    backdrop-filter:none !important; -webkit-backdrop-filter:none !important;
  }
  /* The blur was doing two jobs: softening what is behind the panel, and
     making the panel read as a distinct surface. Losing the first is fine;
     losing the second is not, so the fill is made more opaque to compensate —
     which is what keeps the two modes looking like the same app. */
  :root[data-fx="lite"] .glass{
    /* The sheen layer is repeated here because this rule replaces the whole
       `background` shorthand — dropping it would make cheap mode a visibly
       flatter panel rather than the same panel drawn cheaply. */
    background:var(--glass-sheen), color-mix(in srgb, var(--glass-bg) 100%, var(--bg-base) 55%);
  }
  :root[data-fx="lite"] .modal-backdrop{ background:color-mix(in srgb, var(--bg-deep) 78%, transparent); }
  :root[data-fx="lite"] .chat-pill-aurora{ animation:none; }
  /* Anything decorative that loops for ever. Deliberately NOT a blanket
     `animation:none` — the view cross-fade, toasts and the send whoosh are
     feedback, and an app whose transitions vanish reads as broken rather than
     as fast. */
  :root[data-fx="lite"] .pulse-ring,
  :root[data-fx="lite"] [class*="breathe"]{ animation:none !important; }

  /* Someone who has asked their system for less motion gets lite as the
     starting point, before any measurement happens. */
  @media (prefers-reduced-motion: reduce){
    :root:not([data-fx="full"]) .glass{ backdrop-filter:none; -webkit-backdrop-filter:none; }
  }

  *{ box-sizing:border-box; }
  html,body{ height:100%; margin:0; }

  /* ---- Dark-mode-only "muted card + contained glow" pass. Every surface
     stays flat/desaturated; color shows up as a glow anchored at one edge,
     not a wide wash — so the black void stays visibly dominant. Uses
     box-shadow directly on the element rather than a ::before with negative
     z-index: the first version put the glow in a ::before, which — because
     these rows live inside glass panels using backdrop-filter (itself a
     stacking-context trigger) — got trapped behind the panel's own
     background instead of showing on top of it. box-shadow paints as part
     of the element's own layer, so it can't get hidden that way. Scoped
     off in light mode via :not([data-mode="light"]) rather than touching
     the light block above. */
  .mail-unread-dot{ width:6px; height:6px; border-radius:50%; flex:none; display:inline-block; margin-right:6px; background:var(--accent); vertical-align:1px; }

  /* WHY THIS IS data-mode AND NOT data-theme.
     Every one of these guards used to read :not([data-theme="light"]) — one
     theme name, hard-coded, thirty times. Adding a second light theme would
     have handed it the whole dark-mode glow treatment: contained glow pools on
     cream, which is not a design choice, it is a smudge.
     setTheme now stamps data-mode="light" or "dark" alongside the name, so
     "is this a light theme" is a question with one answer in one place. */
  :root:not([data-mode="light"]) .nav-item.active{
    box-shadow:0 0 0 1px color-mix(in srgb, var(--accent) 30%, transparent), 0 0 22px 2px var(--accent-glow);
  }
  :root:not([data-mode="light"]) .mail-row{ box-shadow:0 0 14px 0 var(--row-glow, transparent); }
  :root:not([data-mode="light"]) .mail-row:hover{ box-shadow:0 0 18px 1px var(--row-glow, var(--accent-glow)); }
  :root:not([data-mode="light"]) .mail-row.selected{
    box-shadow:0 0 0 1px color-mix(in srgb, var(--row-color, var(--accent)) 35%, transparent), 0 0 28px 4px var(--row-glow, var(--accent-glow));
  }
  :root:not([data-mode="light"]) .cal-event{ box-shadow:0 0 18px 2px var(--row-glow, var(--green-glow)); }
  :root:not([data-mode="light"]) .list-check.done{ box-shadow:0 0 12px 2px var(--green-glow); }
  :root:not([data-mode="light"]) .mail-unread-dot{ box-shadow:0 0 8px 2px var(--accent-glow); }
  :root:not([data-mode="light"]) .contact-row{
    background-image: linear-gradient(90deg, color-mix(in srgb, var(--row-glow, transparent) 65%, transparent) 0%, transparent 45%);
    box-shadow:
      0 1px 2px rgba(0,0,0,.35),
      0 10px 20px -6px rgba(0,0,0,.5),
      0 28px 56px -18px rgba(0,0,0,.55),
      0 0 26px -8px var(--row-glow, transparent);
  }
  :root:not([data-mode="light"]) .contact-row:hover{
    transform:translateY(-3px);
    background-image: linear-gradient(90deg, color-mix(in srgb, var(--row-glow, transparent) 80%, transparent) 0%, transparent 50%);
    box-shadow:
      0 2px 4px rgba(0,0,0,.4),
      0 14px 28px -6px rgba(0,0,0,.55),
      0 36px 72px -20px rgba(0,0,0,.6),
      0 0 38px -4px var(--row-glow, transparent);
  }
  :root:not([data-mode="light"]) .rel-strong{ box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--cat-green) 35%, transparent), 0 0 16px 0 var(--cat-green-glow); }
  :root:not([data-mode="light"]) .rel-stable{ box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--cat-violet) 30%, transparent), 0 0 16px 0 var(--cat-violet-glow); }
  :root:not([data-mode="light"]) .rel-cooling, :root:not([data-mode="light"]) .rel-stale{ box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--cat-blue) 40%, transparent), 0 0 16px 0 var(--cat-blue-glow); }
  :root:not([data-mode="light"]) .rel-risk{ box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--cat-orange) 40%, transparent), 0 0 16px 0 var(--cat-orange-glow); }
  :root:not([data-mode="light"]) .project-card:hover{ box-shadow:0 0 24px 3px var(--accent-glow); }

  body{
    position:relative;
    color:var(--text); font-family:'Public Sans', sans-serif; -webkit-font-smoothing:antialiased; overflow:hidden;
    background:
      radial-gradient(900px 620px at 12% -8%, var(--bloom-1), transparent 60%),
      radial-gradient(1000px 760px at 108% 18%, var(--bloom-2), transparent 58%),
      radial-gradient(760px 620px at 90% 90%, var(--bloom-3), transparent 55%),
      radial-gradient(760px 620px at 10% 118%, var(--bloom-4), transparent 60%),
      linear-gradient(180deg, var(--bg-mid), var(--bg-base) 55%, var(--bg-deep));
    transition:background var(--dur-base) var(--ease-out), color var(--dur-base) var(--ease-out);
  }
  /* Subtle film-grain over the bloom gradients — without it, large soft radial
     glows read as flat/plasticky (banding, "generic AI dashboard" look). A
     faint noise layer is what most premium dark UIs use to make a gradient
     feel like it has material, not just color. Kept very low-opacity so it
     reads as texture, not visible speckle. */
  body::before{
    content:''; position:fixed; inset:0; z-index:0; pointer-events:none;
    opacity:.035; mix-blend-mode:overlay;
    background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  }
  .app{ position:relative; z-index:1; }
  /* ---- "Starting now" ---------------------------------------------------
     This was a 13px pill and Matt kept missing it, which for the one thing on
     screen whose whole job is to be noticed is a complete failure. It is now
     roughly half again as large in every dimension, sits further off the
     bottom edge, and carries a green rim-light so it separates from whatever
     is behind it. It is still a banner and not a modal — it must never block
     the thing it is telling you about. */
  .meeting-banner{
    position:fixed; bottom:34px; left:50%; z-index:998;
    transform:translateX(-50%) translateY(140%); opacity:0; pointer-events:none;
    transition:transform .28s cubic-bezier(.2,.8,.3,1), opacity var(--dur-slow) var(--ease-out);
    display:flex; align-items:center; gap:16px; padding:16px 18px 16px 24px;
    border-radius:18px; border-top-color:var(--green);
    max-width:min(760px, calc(100vw - 48px));
    box-shadow: var(--glass-shadow), 0 0 26px -6px var(--green-glow, rgba(80,220,150,.5));
  }
  .meeting-banner.show{ transform:translateX(-50%) translateY(0); opacity:1; pointer-events:auto; }
  .mb-dot{ width:12px; height:12px; border-radius:50%; background:var(--green); box-shadow:0 0 14px 2px var(--green-glow); flex:none; animation:breathe 1.6s ease-in-out infinite; }
  .mb-text{ font-size:18px; font-weight:700; letter-spacing:-.01em; line-height:1.25;
            overflow:hidden; text-overflow:ellipsis; white-space:nowrap; min-width:0; }
  .meeting-banner .btn.primary{ font-size:15px; padding:10px 22px !important; border-radius:11px; }
  .mb-dismiss{ cursor:pointer; color:var(--text-faint); font-size:24px; line-height:1; padding:2px 6px; }
  .mb-dismiss:hover{ color:var(--text); }
  /* On a phone the title is what matters; the banner goes full width and the
     text is allowed to wrap rather than being cut off mid-word. */
  @media (max-width:640px){
    .meeting-banner{ left:16px; right:16px; bottom:20px; transform:translateY(140%);
                     max-width:none; padding:14px 14px 14px 18px; gap:12px; }
    .meeting-banner.show{ transform:translateY(0); }
    .mb-text{ font-size:16px; white-space:normal; }
  }
  .display{ font-family:'Fraunces', serif; }
  .mono{ font-family:'IBM Plex Mono', monospace; }
  /* ==========================================================
     WHY THERE IS A "LITE" MODE, AND WHAT IT TURNS OFF
     ==========================================================

     Matt: "the whole UI is all herky jerky, like it is shorting out."

     It was not JavaScript. Measured over ten seconds of an idle app, the main
     thread was 5% busy and there were zero layouts. It was the GPU, and the
     count is the whole story: THIRTY-SIX visible elements with a live
     backdrop-filter — twenty of them at blur(26px) saturate(1.5).

     backdrop-filter is the most expensive thing on this page by a distance.
     Each one makes the compositor snapshot everything behind it, blur it, and
     saturate it — every frame, per element, and the cost scales with the pixel
     area. Twenty of them on a high-DPI screen is a lot of full-screen blurs
     sixty times a second. Add a WebGL orb animating continuously and a
     readPixels() that stalls the pipeline on a timer, and "shorting out" is a
     fair description.

     None of it is new. It has been creeping up with every panel added, and it
     has now crossed the line on his machine.

     So: the effects are adaptive. The app measures its own frame rate a moment
     after load, and if the machine cannot hold a smooth one it drops to
     data-fx="lite" — the same layout and the same colours, with the blurs
     replaced by flat translucency. It is barely distinguishable in a
     screenshot and it is the difference between 12fps and 60.

     Settings lets him force it either way, because a measurement taken during
     a busy first second can be wrong in both directions and he should not have
     to argue with it.
     ========================================================== */
  .glass{
    position:relative; overflow:hidden;
    /* THE SHEEN IS A BACKGROUND LAYER, NOT AN OVERLAY. THIS IS THE
       LIGHT-MODE READABILITY BUG.
       It used to be `.glass::before{ position:absolute; inset:0 }` — a
       positioned pseudo-element with no z-index, which paints ABOVE every
       non-positioned child. So the sheen was not a highlight on the glass; it
       was a sheet of translucent white laid over the text.
       In the dark themes the sheen is rgba(255,255,255,0.09–0.16) and washing
       already-pale text a little paler is invisible. In Serene it is 0.55–0.65,
       and it covered the TOP HALF of every panel and every popup at about 52%
       — which is why a weekday letter set to #796F63 rendered as
       rgb(185,179,173), and why the popups were "impossible to read when
       white/grey". Measured, not guessed: that pixel is exactly #796F63
       composited at alpha .52 over the panel.
       No amount of darkening the token could have fixed it. 52% of a darker
       colour is still 52%.
       As a background layer the sheen sits above the fill and below the
       content: the look is unchanged and it is one less painted layer. */
    background:var(--glass-sheen), var(--glass-bg);
    /* Blur/saturation are tokens now rather than hardcoded, because the
       "frosted" theme's whole identity is a much heavier blur — that is the
       one property that separates a frosted pane from a tinted one. */
    backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-sat));
    -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-sat));
    border:1px solid var(--glass-border); border-top-color: var(--glass-border-hi); border-radius:var(--radius);
    box-shadow: var(--glass-shadow); transition:background var(--dur-base) var(--ease-out), border-color var(--dur-base) var(--ease-out);
  }
  /* (.glass::before is deliberately gone — see the background layer above.) */

  /* A PANEL YOU GLANCE PAST CAN BE GLASS. A PANEL YOU READ HAS TO BE PAPER.
     ---------------------------------------------------------------------
     The sheen fix above stopped a translucent sheet being painted OVER the
     text. This is the other half of the same complaint — "the Ask window is
     too transparent to read" — and it is not the sheen, it is the fill.
     Measured with a red sheet behind the open Ask card: 42% of it came
     through. Behind that card in real use is the mail list, the home grid,
     and now a turning vortex. After this rule: 12%.

     Contrast alone does not catch this, and the readability harness was right
     to pass it: the text IS legible against the composited pixels. What it
     cannot measure is that the pixels keep MOVING. Reading a paragraph over a
     scrolling backdrop is work, and no amount of darkening the ink fixes it.

     So the fill gets an almost-opaque backing layer underneath it. The glass
     tint and the sheen sit on top of that exactly as designed, so the panel
     looks like the same panel — it simply has paper behind it now. Twelve per
     cent is deliberately not zero: the frosted theme's whole identity is what
     shows through, and a completely solid card would make it a different
     theme rather than a readable one.

     Applied only to the things that are READ: the Ask card, every modal, the
     dialog, and the search results. Tiles, the rail and the banner are
     glanced at and keep the full glass.

     ONE SYNTAX TRAP, WORTH THE COMMENT. In `background: A, B, C` only the
     LAST layer may be a colour. Writing the glass tint as the middle layer —
     which is what it is in .glass, where it happens to be last — makes the
     whole declaration invalid, and an invalid background is not a slightly
     wrong panel — it is a panel with NO background at all. The first version
     of this rule did exactly that, and the same red sheet went from 42%
     through to 94%: the fix made it twice as bad and looked plausible. So the
     tint is wrapped in a flat gradient, which is a legal image layer, and the
     paper goes last where a colour is allowed. */
  .chat-card,
  .compose-modal.glass,
  #dlgCard.glass,
  .omni-card{
    background:var(--glass-sheen),
               linear-gradient(var(--glass-bg), var(--glass-bg)),
               color-mix(in srgb, var(--bg-base) 88%, transparent);
  }
  /* The meeting banner carries BOTH .meeting-banner and .glass. Both set
     `position`, both have identical specificity, and .glass is declared
     later — so .glass's `position:relative` was silently winning. The
     banner therefore sat in normal document flow at the very top of the
     page: invisible (opacity:0) but 53px tall. That pushed the whole app
     down 53px and, because .app is height:100vh, pushed the same 53px off
     the bottom of the screen. THIS is why every page "sat too low".
     Restated here with higher specificity so it beats .glass on source
     order without needing !important. */
  .meeting-banner.glass{ position:fixed; }
  /* Fills #appZoomRoot, which is the thing that knows about zoom. Using vh/vw
     here instead is what caused the shrink-into-the-corner bug. */
  .app{ display:flex; height:100%; width:100%; }
  #appZoomRoot{ height:100vh; width:100vw; }
  @keyframes breathe{ 0%,100%{opacity:1;} 50%{opacity:.35;} }

  /* SIDEBAR / SPINE */
  .sidebar{ width:216px; flex:none; margin:14px 0 14px 14px; padding:16px 12px; display:flex; flex-direction:column; transition:width var(--dur-base) var(--ease-out), padding var(--dur-base) var(--ease-out); overflow-y:auto; overflow-x:hidden; }
  .sidebar.collapsed{ width:70px; padding:16px 10px; }
  .sidebar-head{ display:flex; align-items:center; justify-content:space-between; padding:2px 6px 20px; }
  .brand{ font-family:'Fraunces', serif; font-size:20px; font-weight:700; display:flex; align-items:center; gap:8px; white-space:nowrap; }
  /* The mark carries the brand alone whenever the rail is collapsed, since
     .nav-label (the "Lucid" wordmark) is hidden in that state — so it goes
     slightly LARGER when collapsed, not smaller. */
  .brand-mark{ width:24px; height:24px; flex:none; display:block; }
  .sidebar.collapsed .brand-mark{ width:26px; height:26px; }
  /* 70px rail − 20px padding = 50px usable. A 26px mark beside a 26px
     button overflows by 2px, so the collapse button tightens when collapsed. */
  .sidebar.collapsed .collapse-btn{ width:22px; height:22px; }
  .sidebar.collapsed .sidebar-head{ padding-left:0; padding-right:0; }
  .collapse-btn{ width:26px; height:26px; border-radius:8px; display:flex; align-items:center; justify-content:center; color:var(--text-faint); cursor:pointer; flex:none; }
  .collapse-btn:hover{ background:rgba(120,120,140,.12); color:var(--text); }
  .collapse-btn svg{ width:14px; height:14px; transition:transform .2s; }
  .sidebar.collapsed .collapse-btn svg{ transform:rotate(180deg); }
  .sidebar.collapsed .sidebar-head{ justify-content:center; }
  /* Previously the collapse-btn hid itself while collapsed, leaving no way
     back to labeled view. It now stays visible (just rotated) so the icon
     rail is never a dead end. */

  .nav-item{ display:flex; align-items:center; gap:11px; padding:11px 12px; border-radius:10px; color:var(--text-dim); font-size:14px; font-weight:500; cursor:pointer; margin-bottom:3px; transition:all var(--dur-fast) var(--ease-out); white-space:nowrap; position:relative; }
  .sidebar.collapsed .nav-item{ justify-content:center; padding:11px; border-radius:12px; }
  /* The rounded-rect outline is the rail's visual language — make it read
     more clearly rather than replacing it. */
  .sidebar.collapsed .nav-item:hover{ background:rgba(120,120,140,.16); box-shadow:inset 0 0 0 1px var(--glass-border); }
  .sidebar.collapsed .nav-item.active{ box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--accent) 45%, transparent), 0 0 18px -2px var(--accent-glow); }
  .sidebar.collapsed .nav-item .dot{ display:none; }
  .nav-item:hover{ background:rgba(120,120,140,.10); color:var(--text); }
  .nav-item.active{ background:color-mix(in srgb, var(--accent) 16%, transparent); color:var(--text); box-shadow:0 0 0 1px color-mix(in srgb, var(--accent) 35%, transparent), 0 0 20px color-mix(in srgb, var(--accent) 14%, transparent); }
  /* 18px -> 23px. In a 70px rail the icon IS the navigation, so it was
     carrying the whole burden at a size meant to sit next to a text label. */
  .nav-icon{ width:23px; height:23px; opacity:0.95; flex:none; stroke-width:2.0; transition:filter var(--dur-fast) var(--ease-out); }
  :root:not([data-mode="light"]) .nav-item.active .nav-icon{ filter:drop-shadow(0 0 4px var(--accent-glow)); }
  .nav-item .dot{ width:6px; height:6px; border-radius:50%; background:var(--accent); margin-left:auto; box-shadow:0 0 8px var(--accent-glow); animation:breathe 2.4s ease-in-out infinite; }
  .sidebar.collapsed .nav-label, .sidebar.collapsed .accounts, .sidebar.collapsed .spine-section, .sidebar.collapsed .sf-text{ display:none; }

  /* Generic hover-tooltip system — content comes from data-tooltip so any
     element can get one without separate markup to keep in sync. Sidebar
     icons show it to the right (they sit at the left edge); everything
     else shows it below by default. */
  [data-tooltip]{ position:relative; }
  [data-tooltip]::after{
    content: attr(data-tooltip); position:absolute; left:50%; top:calc(100% + 8px); transform:translateX(-50%);
    background:var(--bg-deep); color:var(--text); font-size:11px; font-weight:600; padding:5px 9px;
    border-radius:7px; white-space:nowrap; pointer-events:none; opacity:0; visibility:hidden;
    transition:opacity var(--dur-fast) var(--ease-out), visibility var(--dur-fast) var(--ease-out); z-index:80; box-shadow:0 8px 22px rgba(0,0,0,.4);
    border:1px solid var(--glass-border-hi);
  }
  [data-tooltip]:hover::after{ opacity:1; visibility:visible; }
  .nav-item[data-tooltip]::after, .theme-toggle[data-tooltip]::after{ left:calc(100% + 12px); top:50%; transform:translateY(-50%); }
  .sidebar:not(.collapsed) .nav-item::after{ display:none; } /* labels already visible — no need to duplicate them */
  /* Rail tooltips were CSS ::after pseudo-elements sitting outside the rail's
     70px width — and the sidebar clips overflow-x, so they were cut off and
     mostly never appeared at all. This is a single fixed element positioned by
     JS: outside the clipping context, and shown on mouseenter with no
     transition, so it is instant rather than fading in over 130ms. */
  .sidebar [data-tooltip]::after{ display:none !important; }
  #railTip{
    position:fixed; z-index:200; pointer-events:none; opacity:0; visibility:hidden;
    background:var(--bg-deep); color:var(--text); font-size:11.5px; font-weight:600;
    padding:6px 10px; border-radius:7px; white-space:nowrap;
    border:1px solid var(--glass-border-hi); box-shadow:0 8px 22px rgba(0,0,0,.5);
  }
  #railTip.show{ opacity:1; visibility:visible; }

  .accounts{ margin-top:20px; }
  .accounts-label{ font-size:10.5px; text-transform:uppercase; letter-spacing:0.09em; color:var(--text-faint); padding:0 11px; margin-bottom:8px; }
  /* Generic pill — still used by the mobile folder switcher (folderMobile) */
  .pill{ display:inline-flex; align-items:center; gap:6px; padding:5px 11px; border-radius:20px; border:1px solid var(--glass-border); color:var(--text-dim); font-size:12.5px; margin:0 6px 6px 0; cursor:pointer; transition:all var(--dur-fast) var(--ease-out); }
  .pill:hover{ border-color:var(--glass-border-hi); color:var(--text); }
  .pill.active{ border-color:transparent; font-weight:700; background:color-mix(in srgb, var(--accent) 22%, transparent); color:var(--text); box-shadow:0 0 14px color-mix(in srgb, var(--accent) 30%, transparent); }
  .acct-switcher{ position:relative; padding:0 4px; }
  .acct-switcher-btn{ width:100%; display:flex; align-items:center; gap:8px; padding:8px 11px; border-radius:10px; border:1px solid var(--glass-border); background:rgba(120,120,140,.08); color:var(--text); cursor:pointer; font-size:12.5px; font-family:'Public Sans', sans-serif; }
  .acct-switcher-btn:hover{ border-color:var(--glass-border-hi); }
  .acct-switcher-dot{ width:9px; height:9px; border-radius:50%; background:linear-gradient(135deg,var(--neutral-1),var(--neutral-2)); flex:none; }
  .acct-switcher-label{ flex:1; text-align:left; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
  .acct-switcher-chevron{ width:13px; height:13px; flex:none; transition:transform .15s; color:var(--text-faint); }
  .acct-switcher.open .acct-switcher-chevron{ transform:rotate(180deg); }
  .acct-switcher-panel{ display:none; position:absolute; top:calc(100% + 6px); left:4px; right:4px; z-index:60; background:var(--bg-deep); border:1px solid var(--glass-border-hi); border-radius:12px; box-shadow:0 14px 34px rgba(0,0,0,.45); padding:6px; max-height:280px; overflow-y:auto; }
  .acct-switcher.open .acct-switcher-panel{ display:block; }
  .acct-switcher-item{ display:flex; align-items:center; gap:8px; padding:8px 9px; border-radius:8px; cursor:pointer; font-size:12.5px; }
  .acct-switcher-item:hover{ background:rgba(120,120,140,.12); }
  .acct-switcher-item.active{ background:color-mix(in srgb, var(--accent) 18%, transparent); }
  .acct-switcher-item .dot{ width:8px; height:8px; border-radius:50%; flex:none; }
  .acct-switcher-item .email{ flex:1; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
  .acct-switcher-item .provider-tag{ font-size:9px; text-transform:uppercase; letter-spacing:.04em; color:var(--text-faint); border:1px solid var(--glass-border-hi); border-radius:5px; padding:1px 4px; flex:none; }
  .acct-switcher-divider{ height:1px; background:var(--glass-border); margin:6px 2px; }
  .acct-switcher-connect{ font-size:12px; color:var(--accent); font-weight:600; padding:8px 9px; cursor:pointer; border-radius:8px; }
  .acct-switcher-connect:hover{ background:rgba(120,120,140,.12); }

  .spine-section{ margin-top:18px; overflow-y:auto; min-height:0; }
  .spine-section.grow{ flex:1; }
  .spine-section.capped{ max-height:168px; flex:none; }
  .attn-mini{ display:flex; align-items:center; gap:9px; padding:9px 10px; border-radius:10px; cursor:pointer; margin-bottom:3px; }
  .attn-mini:hover{ background:rgba(120,120,140,.10); }
  .attn-mini .dot{ width:7px; height:7px; border-radius:50%; background:var(--blue); box-shadow:0 0 8px var(--blue-glow); flex:none; }
  .attn-mini-name{ font-size:12.5px; font-weight:600; flex:1; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
  .attn-mini-days{ font-family:'IBM Plex Mono',monospace; font-size:10.5px; color:var(--blue); flex:none; }

  .spine-attn-collapsed{ display:none; margin-top:20px; flex-direction:column; align-items:center; gap:4px; cursor:pointer; position:relative; }
  .sidebar.collapsed .spine-attn-collapsed{ display:flex; }
  /* A 9px unread count is the smallest type in the app and it sits on a solid
     accent. --ink is near-black, which works on the dark themes' bright blue
     and disappears on the light themes' deeper one. */
  :root[data-mode="light"] .spine-badge{ color:var(--neutral-ink); }
  .spine-badge{ position:absolute; top:-4px; right:8px; background:var(--blue); color:var(--ink); font-size:9px; font-weight:800; width:15px; height:15px; border-radius:50%; display:flex; align-items:center; justify-content:center; }

  /* ---- Needs Attention, when there IS something ------------------------
     Blue is the colour of everything else in the app, so a blue warning
     triangle read as decoration. It goes red and breathes ONLY while there
     is something overdue — a permanent red glow is just a new background
     colour, and stops being noticed within a day. Nothing overdue, nothing
     glowing. */
  .attn-alert{ color:var(--cat-red-2, #ff6b5e) !important;
               filter:drop-shadow(0 0 6px rgba(255,90,80,.75)); }
  .spine-attn-collapsed.has-overdue svg{ color:var(--cat-red-2, #ff6b5e) !important;
               filter:drop-shadow(0 0 7px rgba(255,90,80,.8));
               animation:attnPulse 2.1s ease-in-out infinite; }
  .spine-attn-collapsed.has-overdue .spine-badge{
               background:var(--cat-red, #b8382e); color:#fff;
               box-shadow:0 0 9px 1px rgba(255,90,80,.75); }
  /* THE ALARM COLOUR IS A PASTEL IN LIGHT MODE.
     --cat-red-2 is the light-on-dark red: on a near-black spine it is a hot
     coral. In the light themes it is #D6907F, and white lettering on it
     measured 1.94:1 — the one badge in the app whose entire job is to be
     noticed was the least readable thing on the screen. Light mode takes the
     solid --cat-red, and the drop-shadow glow goes with it: a glow on cream
     is a smudge, not a light. */
  :root[data-mode="light"] .attn-alert{ color:var(--cat-red) !important; filter:none; }
  :root[data-mode="light"] .spine-attn-collapsed.has-overdue svg{ color:var(--cat-red) !important; filter:none; }
  :root[data-mode="light"] .spine-attn-collapsed.has-overdue .spine-badge,
  :root[data-mode="light"] .spine-count.has-overdue{
               background:var(--cat-red); color:var(--neutral-ink); box-shadow:none; }
  .spine-count.has-overdue{
               background:var(--cat-red, #b8382e); color:#fff;
               box-shadow:0 0 10px 1px rgba(255,90,80,.7);
               animation:attnPulse 2.1s ease-in-out infinite; }
  .spine-toggle.has-overdue > span{ color:var(--cat-red-2, #ff6b5e); }
  @keyframes attnPulse{
    0%,100%{ filter:drop-shadow(0 0 5px rgba(255,90,80,.55)); }
    50%    { filter:drop-shadow(0 0 11px rgba(255,90,80,.95)); }
  }
  /* A pulsing red glow is exactly the thing someone with vestibular trouble
     asked the browser to stop doing. The colour still carries the meaning. */
  @media (prefers-reduced-motion: reduce){
    .spine-attn-collapsed.has-overdue svg, .spine-count.has-overdue{ animation:none; }
  }

  .sidebar-foot{ margin-top:24px; margin-bottom:72px; padding:8px 10px; color:var(--text-faint); font-size:11px; display:flex; flex-direction:column; align-items:flex-start; gap:10px; }
  .sidebar-foot-icons{ display:flex; flex-direction:column; gap:8px; }
  .theme-toggle{ width:36px; height:36px; border-radius:10px; display:flex; align-items:center; justify-content:center; cursor:pointer; background:rgba(120,120,140,.10); flex:none; color:var(--text-dim); position:relative; transition:all var(--dur-fast) var(--ease-out); }
  :root:not([data-mode="light"]) .theme-toggle:hover{ box-shadow:0 0 12px 0 var(--accent-glow); color:var(--text); }
  .theme-toggle:hover{ color:var(--text); }
  .theme-toggle svg{ width:15px; height:15px; }

  /* MAIN */
  .main{ flex:1; overflow-y:auto; padding:4px 30px 12px; min-width:0; }
  .view{ display:none; }
  .view.active{ display:block; animation:rise .22s cubic-bezier(.2,.8,.3,1); }
  /* Inbox and Today are the two views meant to FILL the window rather than
     scroll. They used to size themselves with hardcoded magic numbers
     (`height: calc(100vh - 176px)` and `- 158px`) that were hand-tuned back
     when the meeting-banner bug was stealing 53px off the top — so once that
     was fixed the numbers over-subtracted and left a dead band at the bottom
     of the screen. Replaced with flex, which measures itself: the view fills
     .main, the header rows take what they need, and the grid/wrap absorbs
     whatever is left. No numbers to drift out of sync again.
     `min-height:0` on both is what actually lets the inner lists scroll
     instead of overflowing their flex parent. */
  /* The Board is the third: a canvas that scrolls itself and must never make
     the page scroll. Without this it collapses to the height of its content
     and the dot grid stops halfway down the window — which also made "Fit"
     frame everything into the top-left quarter, because it was fitting to a
     box that was not the size it looked. */
  #view-inbox.view.active, #view-today.view.active, #view-board.view.active{
    display:flex; flex-direction:column; height:100%; min-height:0;
  }
  /* Was translateY(6px) scale(.99). During a cross-fade that vertical nudge
     reads as the page lurching into place. A dissolve should not also move. */
  @keyframes rise{ from{opacity:0;} to{opacity:1;} }

  /* ---- VIEW CHANGES ----
     switchView used to be a hard cut: the outgoing view got display:none in a
     single frame, then the incoming one played `rise`. Nothing eased OUT, so
     every navigation read as a pop followed by a slide.
     Now it is a genuine cross-dissolve: the content pane eases out, the swap
     happens while nothing is visible, and the new view eases back in.
     Deliberately NOT built on the View Transitions API — that defers its
     callback until the browser produces a paint frame, and under a throttled
     or backgrounded tab that leaves navigation looking like it did nothing.
     Verified in testing: it ran a full click behind. A timed opacity swap is
     boring, but it is deterministic and it cannot strand the user. */
  /* The previous attempt faded the whole content pane to nothing, swapped, then
     faded back. That is not a cross-fade, it is a blink — for 130ms the app
     showed an empty rectangle, which reads as "it broke" rather than "it moved".
     A real cross-fade needs both views on screen at once, so the outgoing view
     is lifted out of flow and fades out UNDER the incoming one, which fades in
     over the top. Nothing is ever blank. */
  .main{ position:relative; }
  .view.leaving{
    position:absolute; top:0; left:0; right:0; height:100%;
    opacity:0; pointer-events:none; z-index:0;
    transition: opacity var(--dur-view) var(--ease-out);
  }
  .view.active{ z-index:1; }

  /* Elements that had no transition at all, so their hover/selected states
     snapped between frames. Mail rows are the most-touched surface in the
     whole app and had none. */
  .mail-row{ transition: background var(--dur-fast) var(--ease-out), box-shadow var(--dur-base) var(--ease-out), border-color var(--dur-fast) var(--ease-out), transform var(--dur-fast) var(--ease-out); }
  .project-card{ transition: box-shadow var(--dur-base) var(--ease-out), border-color var(--dur-fast) var(--ease-out), transform var(--dur-fast) var(--ease-out); }
  .cd-tab{ transition: color var(--dur-fast) var(--ease-out), background var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out); }
  .vt-btn, .more-item, .tb-btn, .modal-x, .mc-day, .list-check, .collapse-btn{
    transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out), box-shadow var(--dur-base) var(--ease-out), transform var(--dur-fast) var(--ease-out);
  }

  /* Scroll jumps were instant everywhere. */
  html{ scroll-behavior:smooth; }
  .main, .mail-list, .reading-pane, .agenda-scroll, .sidebar{ scroll-behavior:smooth; }

  /* Respect the OS setting. If someone has asked for less motion, honour it —
     this also makes the whole system easy to sanity-check by toggling one
     system preference. */
  @media (prefers-reduced-motion: reduce){
    *, *::before, *::after{
      animation-duration:.01ms !important; animation-iteration-count:1 !important;
      transition-duration:.01ms !important; scroll-behavior:auto !important;
    }
  }
  .page-title{ font-family:'Fraunces', serif; font-size:27px; font-weight:700; margin:0 0 3px; }
  .page-sub{ color:var(--text-dim); font-size:13px; margin:0 0 12px; }
  /* WHY THIS WENT FROM 660 TO 1240.
     Matt, 25 Aug: "Fix all the cards (not just these) to be wide like in other
     areas. (should be site wide for consistency)."
     .maxw is worn by exactly two views — Projects and Timeline — and it was
     pinning both to 660px while every other page in Lucid runs to 1240
     (.pulse-two, .media-listen, .pj-home-queue, .today-grid). That is why those
     two screens looked like a phone layout left open on a monitor. One number,
     and it is the number the rest of the app already uses. */
  .maxw{ max-width:1240px; }
  /* Same reasoning: 11px uppercase at weight 400, with letter-spacing pulling
     the strokes further apart, rendered at roughly half its nominal contrast.
     700 costs nothing and is what makes a small label readable. */
  .section-label{ font-size:11px; font-weight:700; text-transform:uppercase; letter-spacing:0.09em; color:var(--text-faint); margin:18px 0 8px; }
  .section-label:first-of-type{ margin-top:0; }

  .today-grid{ display:grid; grid-template-columns:390px 1fr; gap:18px; max-width:1220px; flex:1; min-height:0; }
  /* The "This Week" panel is a sibling of .today-grid swapped in by inline
     display, so it needs the same fill-the-remaining-space rule or it would
     size to its content and leave the old dead band at the bottom. */
  #weekView{ flex:1; min-height:0; overflow-y:auto; }
  @media(max-width:1100px){ .today-grid{ grid-template-columns:1fr; } }
  /* Below the mobile breakpoint both views go back to normal document flow
     and scroll, so the fill-the-window behaviour must be switched off. */
  @media (max-width:780px){
    #view-inbox.view.active, #view-today.view.active{ display:block; height:auto; }
    .today-grid{ height:auto; }
  }

  .left-panel{ display:flex; flex-direction:column; padding:18px; min-height:0; }
  .agenda-scroll{ flex:1; overflow-y:auto; min-height:0; margin:0 -6px; padding:0 6px; }
  .agenda-item{ display:flex; align-items:flex-start; gap:12px; padding:11px 12px; margin-bottom:7px; cursor:pointer; border-left:3px solid transparent; border-radius:11px; }
  .agenda-item:hover{ background:rgba(120,120,140,.08); }
  .agenda-item.selected{ background:rgba(120,120,140,.10); box-shadow:0 0 0 1.5px var(--ring), 0 0 22px var(--ring-soft); }
  .agenda-time{ font-family:'IBM Plex Mono', monospace; font-size:11.5px; color:var(--text-faint); width:58px; flex:none; padding-top:2px; }
  .agenda-title{ font-size:13.5px; font-weight:600; }
  .agenda-meta{ font-size:11.5px; color:var(--text-dim); margin-top:3px; display:flex; align-items:center; gap:6px; }
  .kind-dot{ width:6px; height:6px; border-radius:50%; flex:none; }
  .kind-event{ background:var(--green); box-shadow:0 0 6px var(--green-glow); }
  .kind-task{ background:var(--violet); box-shadow:0 0 6px var(--violet-glow); }
  .kind-reminder{ background:var(--blue); box-shadow:0 0 6px var(--blue-glow); }
  .flag{ color:var(--blue); animation:breathe 2.4s ease-in-out infinite; }
  .mini-link{ font-size:10.5px; color:var(--blue); margin-top:4px; display:inline-block; cursor:pointer; }
  .mini-link:hover{ text-decoration:underline; }

  /* MINI CALENDAR */
  .mini-cal{ padding-bottom:14px; margin-bottom:6px; border-bottom:1px solid var(--glass-border); flex:none; }
  .mini-cal-head{ display:flex; align-items:center; gap:6px; margin-bottom:8px; }
  .mc-nav{ width:20px; height:20px; border-radius:6px; display:flex; align-items:center; justify-content:center; color:var(--text-dim); cursor:pointer; font-size:13px; }
  .mc-nav:hover{ background:rgba(120,120,140,.10); color:var(--text); }
  .mc-label{ font-size:12.5px; font-weight:700; flex:1; }
  :root[data-mode="light"] .mc-add{ background:linear-gradient(135deg,var(--accent),color-mix(in srgb, var(--accent) 82%, var(--neutral-2))); color:var(--neutral-ink); }
  .mc-add{ width:22px; height:22px; border-radius:7px; background:linear-gradient(135deg,var(--accent),var(--accent-2)); color:var(--ink); font-weight:800; font-size:14px; display:flex; align-items:center; justify-content:center; cursor:pointer; box-shadow:0 0 12px var(--accent-glow); }
  /* NINE PIXELS IS TOO SMALL TO HAVE A COLOUR.
     After --text-faint was darkened to a proper 4.78:1, these letters still
     measured 1.4:1 on screen — because at 9px the strokes are thinner than a
     pixel and every pixel is an antialiased blend of text and paper. The
     nominal contrast was fine; nothing was rendering at it.
     Bigger and bolder is the fix, not darker still. Weight is what small text
     actually needs: it puts more ink in each glyph rather than making the same
     one-pixel smear darker. */
  .mc-weekdays{ display:grid; grid-template-columns:repeat(7,1fr); font-size:10.5px; font-weight:700; color:var(--text-faint); text-align:center; margin-bottom:4px; }
  .mc-grid{ display:grid; grid-template-columns:repeat(7,1fr); gap:1px; }
  .mc-cell{ aspect-ratio:1; max-height:30px; display:flex; align-items:center; justify-content:center; font-size:11px; border-radius:7px; cursor:pointer; position:relative; color:var(--text-dim); }
  .mc-cell:hover{ background:rgba(120,120,140,.10); }
  .mc-pad{ color:var(--text-faint); cursor:default; }
  /* OPACITY WAS THE WRONG LEVER, in both modes. At .28 the neighbouring
     month's dates measured 1.34:1 on midnight and 1.4:1 on cream — they were
     not dimmed, they were gone; and at the ~.9 it would take to pass they no
     longer read as another month at all. The distinction is now the TOKEN:
     faint against the in-month cells' full text colour. Readable, and a
     clearer signal than a wash. */
  .mc-today{ background:color-mix(in srgb, var(--blue) 16%, transparent); color:var(--text); font-weight:700; box-shadow:0 0 0 1px color-mix(in srgb, var(--blue) 40%, transparent); }
  .mc-selected{ background:linear-gradient(135deg,var(--green),var(--green-2)); color:var(--ink); font-weight:700; box-shadow:0 0 12px var(--green-glow); }
  /* The selected day is the primary-button recipe in a 30px square, and it
     fails for the same reason: dark ink over a fill that fades to its pastel.
     Same inversion — the fill deepens, the numeral becomes the paper colour. */
  :root[data-mode="light"] .mc-selected{
    background:linear-gradient(135deg,var(--green),color-mix(in srgb, var(--green) 82%, var(--neutral-2)));
    color:var(--neutral-ink);
  }
  :root[data-mode="light"] .mc-selected .mc-dot{ background:var(--neutral-ink); }
  /* HOW BUSY EACH DAY IS, AS SHADE. The month used to be a grid of identical
     grey numbers with a dot under the days that had anything at all — which
     told you nothing about the difference between a fifteen-minute call and a
     day with six hours booked. The level is computed from real hours, see
     dayLoad().

     SPECIFICITY: these are attribute selectors at (0,2,0) and .mc-today is a
     bare class at (0,1,0), so without the two rules underneath, heat would
     paint straight over today and over the selected day. Same trap the orb
     shapes fell into. */
  .mc-cell[data-load="1"]{ background:color-mix(in srgb, var(--accent) 9%, transparent); color:var(--text); }
  .mc-cell[data-load="2"]{ background:color-mix(in srgb, var(--accent) 17%, transparent); color:var(--text); }
  .mc-cell[data-load="3"]{ background:color-mix(in srgb, var(--accent) 27%, transparent); color:var(--text); font-weight:600; }
  .mc-cell[data-load="4"]{ background:color-mix(in srgb, var(--accent) 38%, transparent); color:var(--text); font-weight:700; }
  .mc-cell.mc-today[data-load]{ background:color-mix(in srgb, var(--blue) 16%, transparent); }
  .mc-cell.mc-selected[data-load]{ background:linear-gradient(135deg,var(--green),var(--green-2)); color:var(--ink); }
  :root[data-mode="light"] .mc-cell.mc-selected[data-load]{ color:var(--neutral-ink); }
  .omni-row.omni-ask{ border:1px solid var(--glass-border-hi); border-radius:10px;
      background:linear-gradient(180deg, color-mix(in srgb, var(--accent) 10%, transparent), transparent);
      margin-bottom:8px; }
  .omni-row.omni-ask .omni-t{ font-weight:700; }
  .mc-legend{ display:flex; align-items:center; gap:5px; margin-top:8px; font-size:10px;
      color:var(--text-faint); font-weight:600; letter-spacing:.03em; }
  .mc-legend i{ width:11px; height:11px; border-radius:4px; display:block; font-style:normal;
      background:color-mix(in srgb, var(--accent) 9%, transparent); }
  .mc-legend i.l2{ background:color-mix(in srgb, var(--accent) 17%, transparent); }
  .mc-legend i.l3{ background:color-mix(in srgb, var(--accent) 27%, transparent); }
  .mc-legend i.l4{ background:color-mix(in srgb, var(--accent) 38%, transparent); }
  .mc-dot{ position:absolute; bottom:2px; width:3px; height:3px; border-radius:50%; background:var(--blue); }
  .mc-selected .mc-dot{ background:var(--ink); }

  /* TIMELINE PANEL */
  .timeline-panel{ padding:18px 18px 14px; display:flex; flex-direction:column; min-height:0; }
  .cal-head{ display:flex; justify-content:space-between; align-items:baseline; margin-bottom:12px; flex:none; }
  .cal-title{ font-family:'Fraunces', serif; font-size:18px; font-weight:700; }
  .cal-date{ font-size:12px; color:var(--text-dim); }
  .cal-body{ display:grid; grid-template-columns:44px 1fr; flex:1; overflow-y:auto; min-height:0; }
  .cal-hours{ display:flex; flex-direction:column; }
  .cal-hour{ font-family:'IBM Plex Mono', monospace; font-size:11px; color:var(--text-faint); position:relative; top:-6px; }
  .cal-track{ position:relative; border-left:1px solid var(--glass-border); margin-left:4px; }
  .cal-line{ position:absolute; left:0; right:0; height:1px; background:var(--glass-border); }
  .cal-event{ position:absolute; border-radius:9px; padding:5px 11px; font-size:12.5px; font-weight:700; overflow:hidden; cursor:pointer; line-height:1.3; transition:all var(--dur-fast) var(--ease-out); display:flex; align-items:baseline; gap:5px; white-space:nowrap; background:color-mix(in srgb, var(--row-color, var(--green)) 18%, transparent); border:1px solid color-mix(in srgb, var(--row-color, var(--green)) 48%, transparent); color:var(--text); }
  .cal-ev-title{ overflow:hidden; text-overflow:ellipsis; white-space:nowrap; min-width:0; }
  .cal-event:hover{ filter:brightness(1.12); }
  /* A TASK IS NOT A MEETING, AND THE DAY SHOULD SAY SO WITHOUT A LEGEND.
   *
   * Tasks appear on the day grid now (see tasksForDate). They sit at their own
   * time like everything else, but nobody else booked them and nobody else is
   * blocked by them — so: dashed, unfilled, no account colour, no glow. The
   * difference is carried by the border style rather than by hue alone, which
   * is the same rule the goal dots follow and the reason it survives every
   * theme and colour-blindness. */
  .cal-event.cal-task{ background:transparent; border-style:dashed; font-weight:600; }
  :root:not([data-mode="light"]) .cal-event.cal-task{ box-shadow:none; }
  .cal-event.cal-task .cal-ev-time{ opacity:.9; }
  .agenda-item.agenda-task{ border-left-style:dashed; }
  .cal-event.selected{ filter:brightness(1.2) saturate(1.25); box-shadow:0 0 0 2px var(--ring), 0 0 26px var(--ring-soft); transform:scale(1.02); z-index:6; }
  .cal-ev-time{ font-size:9.5px; font-weight:500; opacity:.8; flex:none; }
  @keyframes pulseGlow{ 0%,100%{box-shadow:0 0 14px var(--pulse-lo);} 50%{box-shadow:0 0 24px var(--pulse-hi);} }
  .cal-now-line{ position:absolute; left:0; right:0; height:2px; background:var(--blue); box-shadow:0 0 10px var(--blue-glow); z-index:5; }
  .cal-now-dot{ position:absolute; left:-4px; top:-4px; width:9px; height:9px; border-radius:50%; background:var(--blue); box-shadow:0 0 10px var(--blue-glow); animation:breathe 2s infinite; }

  /* INBOX */
  .inbox-wrap{ display:flex; gap:16px; flex:1; min-height:0; }
  /* THE FOLDER ROW IS A PICKER AND A COMPOSE BUTTON, NOT SIX TABS.
   *
   * Six tabs spent the whole width of the list column on a choice that is
   * "Inbox" ninety-five times out of a hundred, and left no room for the one
   * button a mail app must have. Writing a new email lived in the action
   * panel, which is two clicks and a guess. So: the current folder collapses
   * into one control that says which folder you are in and opens the rest,
   * and the space that buys goes to New. */
  .mail-folders{ display:flex; align-items:center; gap:8px; padding:12px 14px; border-bottom:1px solid var(--glass-border); flex:none; }
  .folder-pick{ position:relative; flex:none; }
  .folder-btn{ display:flex; align-items:center; gap:8px; padding:8px 12px; border-radius:10px; font-size:13px;
    font-weight:600; color:var(--text); cursor:pointer; white-space:nowrap; font-family:inherit;
    background:color-mix(in srgb, var(--accent) 16%, transparent);
    border:0; box-shadow:0 0 0 1px color-mix(in srgb, var(--accent) 30%, transparent); }
  .folder-btn:hover{ background:color-mix(in srgb, var(--accent) 24%, transparent); }
  .folder-btn > svg{ width:14px; height:14px; flex:none; }
  .folder-chev{ transition:transform .16s ease; opacity:.8; }
  .folder-pick.open .folder-chev{ transform:rotate(180deg); }
  .folder-menu{ display:none; position:absolute; top:calc(100% + 6px); left:0; z-index:60; min-width:212px;
    padding:6px; border-radius:12px; border:1px solid var(--glass-border);
    background:var(--bg-deep); box-shadow:0 18px 44px rgba(0,0,0,.42); }
  .folder-pick.open .folder-menu{ display:block; }
  .folder-item{ display:flex; align-items:center; gap:9px; padding:8px 12px; border-radius:9px; font-size:13px;
    font-weight:600; color:var(--text-dim); cursor:pointer; white-space:nowrap; width:100%;
    background:none; border:0; text-align:left; font-family:inherit; }
  .folder-item svg{ width:14px; height:14px; flex:none; }
  .folder-item:hover, .folder-item:focus-visible{ background:rgba(120,120,140,.14); color:var(--text); outline:none; }
  .folder-item.active{ background:color-mix(in srgb, var(--accent) 16%, transparent); color:var(--text); }
  .folder-new{ margin-left:auto; display:flex; align-items:center; gap:7px; padding:8px 13px; border-radius:10px;
    font-size:13px; font-weight:600; cursor:pointer; white-space:nowrap; font-family:inherit;
    color:var(--text); background:rgba(120,120,140,.12); border:1px solid var(--glass-border); }
  .folder-new:hover{ background:rgba(120,120,140,.20); }
  .folder-new svg{ width:14px; height:14px; flex:none; }
  .folder-mobile{ display:none; }

  .mail-list-col{ width:400px; flex:none; display:flex; flex-direction:column; min-height:0; }
  /* Full-width toolbar bar — sits above the whole inbox (list + reading pane)
     instead of being squeezed into the list column. Icon-first and dense —
     tooltips (via the generic [data-tooltip] system) carry the labels instead of
     permanent text, which is what actually makes a toolbar feel tight rather than webby.
     Buttons sized up from the original 30px — the old size read as fiddly next to
     everything else on the page. */
  .mail-toolbar-full{ display:flex; align-items:center; gap:2px; padding:6px 12px; margin-bottom:8px; flex-wrap:wrap; flex:none; box-shadow:var(--glass-shadow); }
  .tb-btn{ background:none; border:none; color:var(--text-dim); font-size:16px; cursor:pointer; width:34px; height:34px; border-radius:9px; line-height:1; display:inline-flex; align-items:center; justify-content:center; font-family:'Public Sans', sans-serif; transition:all var(--dur-fast) var(--ease-out); }
  .tb-btn .tb-icon{ font-size:16px; line-height:1; }
  .tb-icon-svg{ width:18px; height:18px; flex:none; stroke-width:2.2; filter:drop-shadow(0 0 3px var(--accent-glow)); transition:filter .15s, color .15s; }
  .tb-btn:hover:not(:disabled) .tb-icon-svg{ filter:drop-shadow(0 0 7px var(--accent-glow)); }
  .tb-btn:disabled .tb-icon-svg{ filter:none; }
  .tb-btn .tb-label{ display:none; } /* tooltip carries the label now — see [data-tooltip] */
  .tb-btn:hover:not(:disabled){ background:rgba(120,120,140,.14); color:var(--text); }
  .tb-btn:disabled{ opacity:.32; cursor:default; }
  .tb-select{ background:rgba(120,120,140,.10); border:1px solid var(--glass-border); color:var(--text-dim); font-size:11px; border-radius:7px; padding:4px 6px; cursor:pointer; }
  .tb-count{ font-size:10.5px; color:var(--text-faint); font-family:'IBM Plex Mono', monospace; padding:0 6px; }
  .tb-divider{ width:1px; height:18px; background:var(--glass-border); margin:0 4px; }
  .tb-filter-chips{ display:flex; flex-wrap:wrap; gap:5px; margin:0 0 0 6px; }
  .tb-filter-chips .chip{ font-size:11.5px; padding:5px 12px; font-weight:600; }
  :root:not([data-mode="light"]) .tb-filter-chips .chip.active{ box-shadow:0 0 0 1px color-mix(in srgb, var(--accent) 40%, transparent), 0 0 18px 2px var(--accent-glow); }
  @media (max-width: 780px){
    .mail-toolbar-full{ overflow-x:auto; flex-wrap:nowrap; padding:6px 8px; }
    .tb-btn{ width:32px; height:32px; }
  }
  .mail-list{ width:auto; flex:1; overflow-y:auto; padding:10px; }
  .mail-more{ padding:14px 8px 20px; text-align:center; }
  .mail-more.end{ font-size:11.5px; color:var(--text-faint); letter-spacing:0.04em; }
  .mail-more .btn{ font-size:12.5px; padding:8px 18px; }
  .mail-row{ display:flex; gap:11px; padding:13px 12px; border-radius:12px; cursor:pointer; align-items:flex-start; margin-bottom:3px; border-left:3px solid transparent; position:relative; touch-action:pan-y; overflow:hidden; }
  .mail-row:hover{ background:rgba(120,120,140,.08); }
  .mail-row.selected{ background:color-mix(in srgb, var(--row-color, var(--accent)) 16%, transparent); box-shadow:0 0 0 1px color-mix(in srgb, var(--row-color, var(--accent)) 35%, transparent); }
  /* The selected row's wash lifts the ground under the quietest text on it. */
  .mail-row.selected .mail-time, .mail-row.selected .mail-preview{ color:var(--text-dim); }
  /* Anything that drills through to a record says so on hover. Without this an
     avatar is indistinguishable from decoration. */
  .avatar.is-jump{ cursor:pointer; transition:box-shadow var(--dur-fast) var(--ease-out), transform var(--dur-fast) var(--ease-out); }
  .avatar.is-jump:hover{ transform:scale(1.06); box-shadow:0 0 0 2px var(--accent), 0 0 16px var(--accent-glow); }
  .new-badge.is-jump:hover{ text-decoration:underline; }
  .avatar{ width:38px; height:38px; border-radius:50%; flex:none; background:linear-gradient(135deg, color-mix(in srgb, var(--blue) 35%, transparent), color-mix(in srgb, var(--green) 25%, transparent)); border:1px solid var(--glass-border-hi); display:flex; align-items:center; justify-content:center; font-size:13px; font-weight:700; color:var(--text); }
  .mail-row .avatar{ background:linear-gradient(135deg, color-mix(in srgb, var(--row-color, var(--blue)) 45%, transparent), color-mix(in srgb, var(--row-color, var(--blue)) 15%, transparent)); border-color:color-mix(in srgb, var(--row-color, var(--blue)) 40%, var(--glass-border-hi)); }
  /* The initials sit on the account colour at 45%, which on the brighter dark
     themes left them at 4.32:1. Eased to 36% — the account is still legible as
     a colour, and the two letters on top of it are legible as letters. */
  :root:not([data-mode="light"]) .mail-row .avatar{ background:linear-gradient(135deg, color-mix(in srgb, var(--row-color, var(--blue)) 36%, transparent), color-mix(in srgb, var(--row-color, var(--blue)) 12%, transparent)); }
  .avatar.lg{ width:120px; height:120px; font-size:38px; }
  /* On a dark room, mixing an accent toward TRANSPARENT lets the dark show
     through and the disc reads as lit glass. Over cream the same mix lets the
     paper through and two half-strength hues average into a grey-green
     smudge — the avatars were the muddiest thing on the Contacts page.
     Mixing toward white instead keeps the hue and turns the disc into tinted
     paper, which is the light-mode equivalent of the same idea. */
  :root[data-mode="light"] .avatar{
    background:linear-gradient(135deg,
      color-mix(in srgb, var(--blue) 26%, #fff),
      color-mix(in srgb, var(--teal) 20%, #fff));
    border-color:color-mix(in srgb, var(--blue) 24%, var(--glass-border-hi));
    color:var(--text);
  }
  :root[data-mode="light"] .mail-row .avatar{
    background:linear-gradient(135deg,
      color-mix(in srgb, var(--row-color, var(--blue)) 30%, #fff),
      color-mix(in srgb, var(--row-color, var(--blue)) 12%, #fff));
    border-color:color-mix(in srgb, var(--row-color, var(--blue)) 30%, var(--glass-border-hi));
  }
  .mail-body{ flex:1; min-width:0; }
  .mail-top{ display:flex; justify-content:space-between; gap:8px; }
  .mail-from{ font-size:13.5px; font-weight:700; }
  .mail-time{ font-family:'IBM Plex Mono', monospace; font-size:10.5px; color:var(--text-faint); flex:none; }
  .mail-subject{ font-size:12.5px; margin-top:2px; color:var(--text); }
  /* HOW MANY ARE IN THIS CONVERSATION.
     On the subject rather than out at the edge, because it is a fact about the
     subject — "Re: survey dates, nine of them" — and a number floating in the
     right margin reads as an unread count, which is a different thing. Quiet
     on purpose: it is context, not an alert, so it never takes an accent
     colour. */
  .mail-thread-n{ display:inline-block; min-width:16px; padding:0 4px; margin-right:6px;
    border-radius:6px; font-family:'IBM Plex Mono', monospace; font-size:10px; line-height:15px;
    text-align:center; color:var(--text-faint); background:rgba(120,120,140,.16);
    border:1px solid var(--glass-border); vertical-align:1px; }
  .mail-empty{ text-align:center; color:var(--text-faint); font-size:12.5px; padding:50px 20px; }

  /* THE REST OF THE CONVERSATION, above the message you opened — where it is
     in time. Each row is closed until pressed and costs nothing to draw. */
  .rp-earlier{ margin:0 0 14px; border:1px solid var(--glass-border); border-radius:12px;
    overflow:hidden; background:rgba(120,120,140,.05); }
  .rp-earlier-head{ padding:8px 12px; font-size:11px; letter-spacing:.04em; text-transform:uppercase;
    color:var(--text-faint); border-bottom:1px solid var(--glass-border); }
  .rp-earlier-row{ padding:9px 12px; cursor:pointer; border-bottom:1px solid var(--glass-border); }
  .rp-earlier-row:last-child{ border-bottom:none; }
  .rp-earlier-row:hover{ background:rgba(120,120,140,.08); }
  .rp-earlier-top{ display:flex; align-items:baseline; gap:10px; }
  .rp-earlier-who{ font-size:12.5px; font-weight:600; color:var(--text); min-width:0;
    overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
  .rp-earlier-when{ margin-left:auto; flex:none; font-size:11px; color:var(--text-faint); }
  /* One line closed, the whole thing open. Clamping rather than truncating so a
     preview that happens to be short does not leave a ragged half-row. */
  .rp-earlier-preview{ font-size:12px; color:var(--text-dim); margin-top:3px;
    overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
  .rp-earlier-row.open .rp-earlier-preview{ display:none; }
  .rp-earlier-body{ display:none; margin-top:8px; }
  .rp-earlier-row.open .rp-earlier-body{ display:block; }
  .reading-pane{ flex:1; padding:26px 30px; overflow-y:auto; }
  /* Snooze on the left, the dark/original toggle pushed to the right. Was a
     bare block with one child; now it has two and needs to say so. */
  /* Themed system messages. Matt: "everything is so beautiful and then we get
     this plain ugly card." These replace the native toast(, 'ok') for anything that
     isn't a genuine question, and — more importantly — they give the app a way
     to say "that didn't save" at all. Until now a failed write was reported
     only to the browser console, which nobody reads, so the screen kept showing
     the change and the next refresh silently undid it. */
  /* The checkbox that made every bulk action honest.
     toggleMailSelected and toggleSelectAllMail existed with zero callers, and
     no checkbox was ever rendered — so selectedMailIds was permanently empty
     and every toolbar button labelled as a bulk action silently operated on the
     single open email. It appears on hover or when something is selected, so
     the list stays clean until you reach for it. */
  .tb-selectall{ display:flex; align-items:center; gap:5px; font-size:11.5px; color:var(--text-faint); cursor:pointer; margin-right:6px; }
  .tb-selectall input{ width:14px; height:14px; accent-color:var(--accent); cursor:pointer; margin:0; }
  .tb-selectall:hover{ color:var(--text-dim); }
  /* SELECT MODE, rather than a checkbox that lurks on hover.
     Two earlier attempts were both wrong. The first animated a checkbox open on
     row hover, which slid every row sideways as the mouse crossed it — the
     shudder. The second put the tick on the avatar, which stopped the shudder
     but took away clicking an avatar to drill into that person.
     This is what Matt actually asked for, and it is what Mail on a phone does:
     nothing at all until you say "Select", then the whole list steps aside and
     a column of checkboxes appears in the gap. Off by default, so the ordinary
     inbox has no chrome in it whatsoever, and the avatar is only ever an
     avatar. */
  .mail-row{ transition: background var(--dur-fast) var(--ease-out),
             box-shadow var(--dur-base) var(--ease-out),
             border-color var(--dur-fast) var(--ease-out),
             padding-left var(--dur-base) var(--ease-out); }
  .mail-list.select-mode .mail-row{ padding-left:36px; }

  .mail-check{ position:absolute; left:10px; top:0; bottom:0; width:22px;
    display:flex; align-items:center; justify-content:center;
    opacity:0; pointer-events:none; cursor:pointer;
    transition:opacity var(--dur-base) var(--ease-out); }
  .mail-list.select-mode .mail-check{ opacity:1; pointer-events:auto; }

  /* The app's own control. A stock browser checkbox in a glass UI is the same
     complaint as the old alert boxes — beautiful everywhere, then one plain
     grey square. */
  .mail-check input{ appearance:none; -webkit-appearance:none; margin:0; cursor:pointer;
    position:relative; width:20px; height:20px; border-radius:6px;
    border:1.5px solid color-mix(in srgb, var(--text) 34%, transparent);
    background:color-mix(in srgb, var(--bg) 70%, transparent);
    transition:background var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out); }
  .mail-check input:hover{ border-color:var(--accent); }
  .mail-check input:checked{ background:var(--accent); border-color:var(--accent); }
  .mail-check input:checked::after{ content:''; position:absolute; left:6px; top:2.5px;
    width:5px; height:9px; border:solid #fff; border-width:0 2px 2px 0; transform:rotate(45deg); }
  .mail-check input:focus-visible{ outline:2px solid var(--accent); outline-offset:2px; }

  /* "Select" in the toolbar — a button, not a stray checkbox. */
  .tb-selectmode{ display:inline-flex; align-items:center; gap:6px; height:34px; padding:0 12px;
    border-radius:9px; border:1px solid var(--glass-border); background:var(--glass-bg);
    color:var(--text-dim); font-size:12.5px; font-family:'Public Sans', sans-serif; cursor:pointer;
    transition:all var(--dur-fast) var(--ease-out); flex:none; }
  .tb-selectmode:hover{ color:var(--text); border-color:color-mix(in srgb, var(--accent) 45%, transparent); }
  .tb-selectmode.on{ color:var(--text); background:color-mix(in srgb, var(--accent) 18%, transparent);
    border-color:color-mix(in srgb, var(--accent) 55%, transparent); }
  /* Two labelled buttons plus search plus four filter chips is more than fits
     on a narrow window, and adding Tidy is what tipped it over. Below 1200px
     they become icons and the tooltip carries the name — which is the same
     trade the toolbar icons already make. */
  @media(max-width:1200px){
    .tb-selectmode span{ display:none; }
    .tb-selectmode{ padding:0 9px; }
  }

  /* "All" only makes sense once you are selecting. */
  .tb-selectall{ display:none; }
  .mail-toolbar-full.selecting .tb-selectall{ display:flex; }

  /* Tidy */
  .tidy-list{ max-height:52vh; overflow-y:auto; display:flex; flex-direction:column; gap:6px; }
  .tidy-row{ display:flex; align-items:center; gap:12px; padding:10px 12px; border-radius:11px;
    background:var(--glass-bg); border:1px solid var(--glass-border); }
  .tidy-row.is-protected{ opacity:.62; }
  .tidy-pick{ flex:none; width:16px; height:16px; accent-color:var(--blue); cursor:pointer; }
  .tidy-sweepbar, .tidy-running{ display:flex; align-items:center; gap:8px; flex-wrap:wrap;
    margin-bottom:12px; padding:9px 12px; border-radius:11px;
    border:1px solid var(--glass-border-hi); background:var(--glass-bg); font-size:12.5px; }
  .tidy-sweep-n{ font-weight:650; }
  .tidy-btn:disabled{ opacity:.4; cursor:default; }
  .tidy-running{ border-color:color-mix(in srgb, var(--blue) 40%, transparent); }
  .tidy-main{ flex:1; min-width:0; }
  .tidy-name{ font-weight:600; font-size:13.5px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
  .tidy-sub{ font-size:11.5px; color:var(--text-faint); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }

  /* ---- Themed dialog --------------------------------------------------- */
  .dlg-body{ font-size:14px; line-height:1.55; color:var(--text-faint); white-space:pre-wrap;
             margin-top:2px; max-height:46vh; overflow-y:auto; overflow-wrap:anywhere; }
  .dlg-body b{ color:var(--text); font-weight:600; }
  /* THE DIALOG IS ALWAYS ON TOP, AND IT WAS NOT.
   *
   * Every overlay in the app is a .modal-backdrop at z-index 50, so which one
   * paints above which came down to the order they happen to appear in this
   * file — and #dlgBackdrop is declared near the top, which put it UNDER the
   * composer, the vault, the event editor and everything else declared later.
   * A dialog raised from inside one of those was visible and unclickable: the
   * modal underneath swallowed the pointer, so the buttons could not be
   * pressed. It is the one overlay whose whole job is to be answered, so it
   * gets a number of its own rather than a position in a list. */
  #dlgBackdrop{ z-index:120; }
  #dlgCard{ border-top-color:var(--orange, var(--accent)); }
  #dlgCard.danger{ border-top-color:var(--cat-red-2, #ff6b5e); }
  .toast-undo{ margin-left:8px; cursor:pointer; font-weight:600; color:var(--accent);
               text-decoration:underline; text-underline-offset:2px; flex:none; }
  /* Unsubscribe lives on the row, deliberately — the top bar is already full
     and this is a per-sender decision, not a mode. */
  .tidy-btn.unsub{ border-color:rgba(80,200,140,.45); }
  .tidy-btn.unsub:hover{ background:rgba(80,200,140,.14); }
  .tidy-done{ font-size:11.5px; color:var(--text-faint); white-space:nowrap; }
  .tidy-done b{ color:var(--text); font-weight:600; }
  .tidy-bulkline{ font-size:12px; color:var(--text-faint); margin:0 0 10px; line-height:1.45; }
  .tidy-bulkline a{ color:var(--accent); cursor:pointer; text-decoration:underline; }
  .tidy-probe{ font-size:11px; color:var(--text-faint); margin:0 0 10px; }

  /* ---- Signature panel ------------------------------------------------- */
  .sig-tabs{ display:flex; gap:6px; margin:0 0 10px; }
  .sig-tab{ font:inherit; font-size:12px; padding:5px 12px; border-radius:8px; cursor:pointer;
            background:transparent; color:var(--text-faint); border:1px solid var(--line); }
  .sig-tab:hover{ color:var(--text); }
  .sig-tab.active{ background:color-mix(in srgb, var(--accent) 78%, #000); border-color:color-mix(in srgb, var(--accent) 78%, #000); color:var(--on-accent,#fff); }
  .sig-opt{ display:flex; align-items:flex-start; gap:9px; margin:9px 0; cursor:pointer; }
  .sig-opt input{ width:15px; height:15px; margin-top:2px; flex:none; cursor:pointer; accent-color:var(--accent); }
  .sig-opt span{ font-size:12.5px; color:var(--text); line-height:1.35; }
  .sig-opt em{ display:block; font-style:normal; font-size:11.5px; color:var(--text-faint); margin-top:2px; }
  .sig-opt code{ font-family:ui-monospace,SFMono-Regular,Menlo,monospace; font-size:11px; }
  .sig-accounts-label{ font-size:11px; letter-spacing:.06em; text-transform:uppercase;
                       color:var(--text-faint); margin:14px 0 6px; }
  .sig-accounts{ display:flex; flex-direction:column; gap:5px; }
  .sig-accounts label{ display:flex; align-items:center; gap:9px; font-size:12.5px; cursor:pointer; }
  .sig-accounts input{ width:15px; height:15px; flex:none; cursor:pointer; accent-color:var(--accent); }
  .sig-report{ margin-top:10px; display:flex; flex-direction:column; gap:6px; }
  .sig-note{ font-size:12px; line-height:1.4; padding:8px 11px; border-radius:9px;
             border:1px solid var(--line); color:var(--text-faint); }
  .sig-note.good{ border-color:rgba(80,200,140,.35); color:var(--text); }
  .sig-note.warn{ border-color:rgba(230,170,60,.4); color:var(--text); }
  .sig-note.bad{ border-color:rgba(230,90,90,.45); color:var(--text); }
  .sig-note b{ color:var(--text); }
  .sig-preview{ width:100%; min-height:170px; border:1px solid var(--line); border-radius:11px;
                background:#fff; margin-top:6px; }
  /* Under the compose and reply boxes: says what will be attached, so the
     signature being invisible while typing is never a surprise. */
  .sig-hint{ font-size:11.5px; color:var(--text-faint); margin:-4px 0 8px; }
  .tidy-count{ font-family:'JetBrains Mono', monospace; font-size:13px; font-weight:700; flex:none;
    min-width:52px; text-align:right; }
  .tidy-acts{ display:flex; gap:6px; flex:none; }
  .tidy-btn{ font-size:11.5px; padding:5px 10px; border-radius:8px; cursor:pointer;
    background:var(--glass-bg); border:1px solid var(--glass-border); color:var(--text-dim);
    font-family:'Public Sans', sans-serif; transition:all var(--dur-fast) var(--ease-out); }
  .tidy-btn:hover{ color:var(--text); border-color:color-mix(in srgb, var(--accent) 50%, transparent); }
  .tidy-btn.danger:hover{ color:var(--text); border-color:color-mix(in srgb, var(--pink) 60%, transparent);
    background:color-mix(in srgb, var(--pink) 15%, transparent); }
  .tidy-btn:disabled{ opacity:.4; cursor:default; }
  .tidy-tag{ font-size:10.5px; color:var(--green); }

  /* The title, folded into the toolbar it used to sit above. */
  .tb-title{ font-family:'Newsreader', serif; font-size:21px; font-weight:600; letter-spacing:-.01em;
    margin:0 6px 0 2px; flex:none; line-height:1; }

  /* Bulk actions only exist once something is ticked. */
  .tb-bulk{ display:none; align-items:center; gap:2px; }
  .mail-toolbar-full.has-selection .tb-bulk{ display:inline-flex; }

  /* Canopy-style actions, on the message rather than in the chrome.
     Not as heavy as Canopy's pills — the same footprint as the old toolbar
     icons — but each lights up in the colour of the account the message belongs
     to, so the mailbox you are acting in is visible at the moment you act. */
  .rp-actions{ display:flex; align-items:center; gap:6px; flex-wrap:wrap; margin:2px 0 12px; }
  .rp-act{ width:36px; height:32px; border-radius:9px; cursor:pointer; flex:none;
    display:inline-flex; align-items:center; justify-content:center;
    background:var(--glass-bg); border:1px solid var(--glass-border); color:var(--text-dim);
    transition:background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out),
               border-color var(--dur-fast) var(--ease-out), box-shadow var(--dur-base) var(--ease-out); }
  .rp-act svg{ width:17px; height:17px; }
  .rp-act:hover{ color:var(--text);
    border-color:color-mix(in srgb, var(--act, var(--accent)) 55%, transparent);
    background:color-mix(in srgb, var(--act, var(--accent)) 16%, transparent);
    box-shadow:0 0 16px 1px var(--act-glow, var(--accent-glow)); }
  .rp-act.danger:hover{ color:var(--text);
    border-color:color-mix(in srgb, var(--pink) 60%, transparent);
    background:color-mix(in srgb, var(--pink) 16%, transparent);
    box-shadow:0 0 16px 1px var(--pink-glow); }

  /* Admin centre */
  .ai-spend{ display:flex; align-items:baseline; gap:14px; flex-wrap:wrap; padding:14px 16px; border-radius:12px;
    background:var(--glass-bg); border:1px solid var(--glass-border); margin-bottom:4px; }
  .ai-spend-num{ font-size:26px; font-weight:700; letter-spacing:-.02em; }

  /* MEMORY — the list of what Lucid thinks it knows.
     Deliberately plain and complete rather than clever. Every row shows the
     fact, where it came from, and a way to delete it, because a memory he
     cannot find is a memory he cannot correct. */
  .mem-add{ display:flex; gap:8px; align-items:stretch; margin-bottom:12px; }
  .mem-add .field{ flex:1; min-width:0; }
  .mem-row{ display:flex; align-items:flex-start; gap:10px; padding:10px 12px; border-radius:10px;
    border:1px solid var(--glass-border); background:var(--glass-bg); margin-bottom:6px; }
  .mem-row:hover{ border-color:var(--glass-border-hi); }
  .mem-body{ flex:1; min-width:0; }
  .mem-text{ font-size:13.5px; line-height:1.5; word-break:break-word; }
  .mem-meta{ font-size:10.5px; text-transform:uppercase; letter-spacing:.05em; color:var(--text-faint); margin-top:4px; }
  .mem-pin, .mem-del{ flex:none; cursor:pointer; color:var(--text-faint); font-size:14px; line-height:1;
    width:26px; height:26px; border-radius:7px; display:flex; align-items:center; justify-content:center; }
  .mem-pin:hover, .mem-del:hover{ background:color-mix(in srgb, var(--text) 8%, transparent); color:var(--text); }
  .mem-pin.on{ color:var(--orange); }
  .mem-kind{ display:inline-block; font-size:9.5px; font-weight:700; text-transform:uppercase; letter-spacing:.05em;
    padding:2px 6px; border-radius:5px; margin-right:6px;
    background:color-mix(in srgb, var(--blue) 16%, transparent); color:var(--blue-text, var(--blue)); }
  :root{ --blue-text:var(--blue-2); }
  :root[data-mode="light"]{ --blue-text:var(--blue); }
  .mem-kind.k-inferred{ background:color-mix(in srgb, var(--orange) 16%, transparent); color:var(--orange-text, var(--orange)); }
  :root{ --orange-text:var(--orange-2); }
  :root[data-mode="light"]{ --orange-text:var(--orange); }
  .ai-spend-of{ font-size:12.5px; color:var(--text-faint); }
  .ai-bar{ width:100%; height:6px; border-radius:4px; background:color-mix(in srgb, var(--text) 10%, transparent); overflow:hidden; margin-top:4px; }
  .ai-bar span{ display:block; height:100%; border-radius:4px; background:var(--green); transition:width var(--dur-base) var(--ease-out); }
  .ai-bar.warn span{ background:var(--orange); }
  .diag-row{ display:flex; align-items:flex-start; gap:10px; padding:9px 2px; border-bottom:1px solid var(--glass-border); font-size:13px; }
  .diag-row:last-child{ border-bottom:none; }
  .diag-dot{ width:8px; height:8px; border-radius:50%; margin-top:6px; flex:none; }
  .diag-ok   .diag-dot{ background:var(--green);  box-shadow:0 0 8px 1px var(--green-glow); }
  .diag-bad  .diag-dot{ background:var(--cat-red-2, #ff6b5e); box-shadow:0 0 8px 1px rgba(255,107,94,.45); }
  .diag-warn .diag-dot{ background:var(--orange); }
  .diag-name{ font-weight:600; min-width:150px; }
  .diag-detail{ color:var(--text-dim); flex:1; overflow-wrap:anywhere; }
  .diag-counts{ display:flex; gap:16px; flex-wrap:wrap; font-size:12.5px; color:var(--text-dim); padding:8px 2px; }
  .app-row{ display:flex; align-items:center; gap:12px; padding:10px 2px; border-bottom:1px solid var(--glass-border); font-size:13px; }
  .app-row:last-child{ border-bottom:none; }
  .toast-stack{ position:fixed; right:22px; bottom:22px; z-index:9000; display:flex; flex-direction:column; gap:10px;
    align-items:flex-end; pointer-events:none; max-width:min(420px, calc(100vw - 44px)); }
  .toast{ pointer-events:auto; display:flex; align-items:flex-start; gap:10px; padding:12px 14px; border-radius:12px;
    font-size:13px; line-height:1.45; background:var(--glass); border:1px solid var(--glass-border);
    backdrop-filter:blur(18px); -webkit-backdrop-filter:blur(18px); color:var(--text);
    box-shadow:0 10px 34px -10px rgba(0,0,0,.55); max-width:100%;
    animation:toastIn .22s cubic-bezier(.2,.8,.3,1); overflow-wrap:anywhere; }
  .toast.leaving{ animation:toastOut .18s ease-in forwards; }
  .toast-dot{ width:8px; height:8px; border-radius:50%; margin-top:5px; flex:none; }
  .toast.ok    .toast-dot{ background:var(--green);  box-shadow:0 0 10px 1px var(--green-glow); }
  .toast.warn  .toast-dot{ background:var(--orange); box-shadow:0 0 10px 1px var(--orange-glow, rgba(255,150,60,.5)); }
  .toast.error .toast-dot{ background:var(--cat-red-2, #ff6b5e); box-shadow:0 0 10px 1px rgba(255,107,94,.45); }
  .toast-x{ margin-left:6px; cursor:pointer; color:var(--text-faint); flex:none; }
  .toast-x:hover{ color:var(--text); }
  @keyframes toastIn{ from{ opacity:0; transform:translateY(8px) scale(.98); } to{ opacity:1; transform:none; } }
  @keyframes toastOut{ to{ opacity:0; transform:translateY(6px) scale(.98); } }
  @media (prefers-reduced-motion: reduce){ .toast, .toast.leaving{ animation:none; } }
  .rp-toolbar-row{ margin-bottom:14px; display:flex; align-items:center; gap:10px; flex-wrap:wrap; }
  .rp-toolbar-row > :last-child:not(:first-child){ margin-left:auto; }
  .rp-compose-bar{ display:flex; align-items:center; justify-content:space-between; gap:16px; margin-bottom:18px; }
  .rp-compose-field-row{ display:flex; align-items:center; gap:12px; padding:8px 0; border-bottom:1px solid var(--glass-border); }
  /* Recipient chip-input — shared across New Message / Reply / Forward. */
  .chip-input-wrap{ position:relative; flex:1; min-width:0; }
  .chip-input-chips{ display:flex; flex-wrap:wrap; align-items:center; gap:6px; min-height:26px; }
  .chip-token{ display:inline-flex; align-items:center; gap:6px; padding:4px 6px 4px 10px; border-radius:14px; background:color-mix(in srgb, var(--accent) 16%, transparent); border:1px solid color-mix(in srgb, var(--accent) 35%, transparent); font-size:12.5px; font-weight:600; white-space:nowrap; }
  .chip-token-x{ width:16px; height:16px; border-radius:50%; display:inline-flex; align-items:center; justify-content:center; cursor:pointer; font-size:12px; color:var(--text-faint); }
  .chip-token-x:hover{ background:rgba(255,255,255,.12); color:var(--text); }
  .chip-input-field{ flex:1; min-width:120px; background:none; border:none; outline:none; color:var(--text); font-family:'Public Sans'; font-size:13px; padding:4px 2px; }
  .chip-input-suggestions{ position:absolute; top:calc(100% + 4px); left:0; right:0; background:var(--bg-mid); border:1px solid var(--glass-border-hi); border-radius:10px; box-shadow:0 16px 40px rgba(0,0,0,.5); z-index:30; overflow:hidden; display:none; max-height:220px; overflow-y:auto; }
  .chip-input-suggestions.open{ display:block; }
  .chip-suggestion{ display:flex; flex-direction:column; padding:9px 14px; cursor:pointer; border-bottom:1px solid var(--glass-border); }
  .chip-suggestion:last-child{ border-bottom:none; }
  .chip-suggestion:hover, .chip-suggestion.active{ background:rgba(120,120,140,.12); }
  .chip-suggestion-name{ font-size:12.5px; font-weight:700; }
  .chip-suggestion-email{ font-size:11px; color:var(--text-faint); }
  .cc-bcc-toggle{ display:flex; gap:12px; margin:4px 0; }
  .cc-bcc-toggle span{ font-size:11.5px; font-weight:700; color:var(--text-faint); cursor:pointer; text-transform:uppercase; letter-spacing:.03em; }
  .cc-bcc-toggle span:hover{ color:var(--text); }
  .cc-bcc-toggle span.hidden-field{ display:none; }
  .rp-compose-field-row .field{ margin:0; border:none; background:none; padding:0; }
  .rp-compose-label{ font-size:11px; font-weight:700; text-transform:uppercase; letter-spacing:.04em; color:var(--text-faint); width:56px; flex:none; }
  .rp-trail-meta{ font-size:12px; color:var(--text-faint); margin-bottom:10px; padding-top:14px; border-top:1px solid var(--glass-border); }
  .rp-trail-body{ border-radius:12px; overflow:hidden; }
  .rp-header{ display:flex; justify-content:space-between; align-items:flex-start; margin-bottom:16px; }
  .rp-subject{ font-family:'Fraunces', serif; font-size:21px; font-weight:600; margin:0 0 4px; }
  .rp-meta{ font-size:12px; color:var(--text-dim); }
  .rp-meta b{ color:var(--text); }
  /* THE FULL HEADER.
     Matt: "my emails don't show who they are from. need full big boy details
     for emails like any regular client." He was right — the reader printed a
     display name and a relative time, and nothing else. This is the block every
     mail client has: a face, a name with the actual address beside it, the real
     date, and everyone else who was on the message.
     The To/Cc lines arrive with the body rather than with the list, so this
     block is built in two beats and must not jump when the second one lands —
     hence a container that is filled in place rather than inserted. */
  .rp-id{ display:flex; gap:12px; align-items:flex-start; min-width:0; }
  .rp-face{ width:38px; height:38px; border-radius:50%; flex:none; display:flex; align-items:center;
            justify-content:center; font-size:13px; font-weight:700; letter-spacing:.02em;
            background:var(--glass); border:1px solid var(--glass-border); color:var(--text); }
  .rp-who{ min-width:0; }
  .rp-name{ font-size:14px; font-weight:650; color:var(--text); }
  .rp-addr{ font-size:12px; color:var(--text-dim); font-family:ui-monospace,'SFMono-Regular',Menlo,monospace;
            word-break:break-all; }
  .rp-when{ font-size:11.5px; color:var(--text-faint); margin-top:2px; }
  .rp-lines{ margin-top:6px; font-size:12px; color:var(--text-dim); line-height:1.55; }
  .rp-line{ display:flex; gap:6px; }
  .rp-line .rp-key{ color:var(--text-faint); font-weight:700; text-transform:uppercase; font-size:10px;
                    letter-spacing:.05em; padding-top:3px; flex:none; min-width:26px; }
  .rp-line .rp-vals{ min-width:0; word-break:break-word; }
  .rp-person{ cursor:pointer; border-bottom:1px dotted var(--glass-border); }
  .rp-person:hover{ color:var(--text); border-bottom-color:currentColor; }
  .rp-more{ background:none; border:none; padding:0; font:inherit; color:var(--text-faint);
            cursor:pointer; text-decoration:underline; }
  .rp-more:hover{ color:var(--text); }
  .rp-onbehalf{ font-size:11.5px; color:var(--amber, #E0A458); margin-top:3px; }
  .rp-body{ font-size:13.5px; line-height:1.7; color:var(--text); opacity:.88; white-space:pre-line; margin:16px 0 20px; }
  .btn-danger{ color:var(--red, #FF8080); border-color:color-mix(in srgb, var(--red, #FF8080) 35%, transparent); }
  /* The dialog's destructive button. It should read as the dangerous one at a
     glance — the whole point of a confirmation is that the two choices do not
     look interchangeable. */
  .btn.danger{ color:var(--cat-red-2, #ff6b5e);
               border-color:color-mix(in srgb, var(--cat-red-2, #ff6b5e) 45%, transparent);
               background:color-mix(in srgb, var(--cat-red-2, #ff6b5e) 10%, transparent); }
  /* The -2 variants are the PASTEL ones — they exist to sit on a dark ground.
     As text on cream, --cat-red-2 measures 1.26:1: a Delete button with
     nothing legible on it. Light mode takes the base hue instead, which is the
     variant that was tuned to be read rather than to glow. */
  :root[data-mode="light"] .btn.danger{ color:var(--cat-red); }
  :root[data-mode="light"] .btn.danger:hover{ background:color-mix(in srgb, var(--cat-red) 14%, transparent); }
  .btn.danger:hover{ background:color-mix(in srgb, var(--cat-red-2, #ff6b5e) 20%, transparent);
                     border-color:var(--cat-red-2, #ff6b5e); }
  .btn-danger:hover{ background:color-mix(in srgb, var(--red, #FF8080) 14%, transparent);
    border-color:color-mix(in srgb, var(--red, #FF8080) 60%, transparent); }
  .btn{ padding:11px 20px; border-radius:10px; font-size:13.5px; font-weight:600; cursor:pointer; border:1px solid var(--glass-border-hi); background:rgba(120,120,140,.08); color:var(--text); transition:all var(--dur-fast) var(--ease-out); }
  .btn:hover{ background:rgba(120,120,140,.14); }
  .btn.primary{ background:linear-gradient(135deg, var(--accent), var(--accent-2)); border-color:transparent; color:var(--ink); box-shadow:0 4px 20px var(--accent-glow); }
  /* Muted-glow variant — same shape as .btn.primary but tinted/translucent
     instead of a solid saturated fill, matching the "flat, muted, then
     enlightened" language the rest of the redesign uses. Reserved for
     contextual, account-colored buttons (like the in-email "+ Add contact")
     rather than replacing bold CTAs like Compose/New Message, which are
     meant to stay punchy. */
  /* THE LETTERING TAKES THE LIGHT VARIANT ON A DARK THEME.
     --accent is a saturated mid-blue. On a 14% tint over a near-black panel it
     measured 4.04:1 against a 4.5 floor — the one button on the page asking to
     be pressed, and the hardest thing on it to read. --accent-2 is the same
     hue's pastel and is what every other coloured label in the app already
     uses on dark. */
  .btn.primary.tinted{
    background:color-mix(in srgb, var(--accent) 14%, transparent);
    border:1px solid color-mix(in srgb, var(--accent) 42%, transparent);
    color:var(--accent-2, var(--accent)); box-shadow:none;
  }
  /* AND IT CAME BACK, because this button sets its own colours.
     The in-email "+ Add contact" is tinted with the ACCOUNT's colour and sets
     --accent and --accent-2 to the same value inline, which defeats the fix
     above: --accent-2 is only the pastel when nobody has overridden it. So the
     lightening is done here instead of relying on a token, and it holds
     whatever colour the mailbox happens to be. */
  :root:not([data-mode="light"]) .btn.primary.tinted{
    color:color-mix(in srgb, var(--accent-2, var(--accent)) 58%, #fff);
  }
  :root[data-mode="light"] .btn.primary.tinted{
    color:color-mix(in srgb, var(--accent) 78%, #000);
  }
  :root:not([data-mode="light"]) .btn.primary.tinted{ box-shadow:0 0 18px 1px var(--accent-glow); }
  .btn.primary:hover{ filter:brightness(1.08); }
  /* THE PRIMARY BUTTON WAS BUILT FOR A DARK APP.
     `linear-gradient(--accent, --accent-2)` with `color:var(--ink)` means dark
     text on a fill that runs from a solid colour to its PASTEL. On a dark
     theme that is a bright button with near-black lettering: correct. In light
     mode the same recipe is dark-on-mid-blue at one end and dark-on-pale at
     the other — "Send" measured 3.76:1.
     Light mode inverts it: the fill darkens across the gradient instead of
     fading out, and the lettering is the theme's own paper colour. */
  :root[data-mode="light"] .btn.primary{
    background:linear-gradient(135deg, var(--accent), color-mix(in srgb, var(--accent) 82%, var(--neutral-2)));
    color:var(--neutral-ink);
  }
  /* The tinted variant keeps its wash — it is deliberately quiet — but its
     lettering is an ACCOUNT colour chosen by Matt, so it cannot be replaced,
     only deepened. Mixing toward the theme's darkest neutral keeps the hue he
     picked and buys the contrast. */
  :root[data-mode="light"] .btn.primary.tinted{
    color:color-mix(in srgb, var(--accent) 80%, var(--neutral-2));
  }
  .btn-row{ display:flex; gap:8px; margin-top:6px; }
  .compose-inline{ margin-top:16px; padding:14px; }
  .field{ width:100%; background:rgba(120,120,140,.08); border:1px solid var(--glass-border); border-radius:10px; color:var(--text); font-family:'Public Sans'; font-size:14px; padding:11px 14px; margin-bottom:10px; transition:all var(--dur-fast) var(--ease-out); }
  :root:not([data-mode="light"]) .field:focus{ outline:none; border-color:color-mix(in srgb, var(--accent) 45%, var(--glass-border)); box-shadow:0 0 14px 0 var(--accent-glow); }
  .field:focus{ outline:none; border-color:color-mix(in srgb, var(--accent) 50%, transparent); }
  textarea.field{ resize:vertical; min-height:80px; font-family:'Public Sans'; }
  .new-badge{ display:inline-block; font-size:10.5px; text-transform:uppercase; letter-spacing:0.05em; color:var(--blue); border:1px solid color-mix(in srgb, var(--blue) 40%, transparent); padding:2px 8px; border-radius:6px; margin-bottom:10px; }
  /* Solid blue lettering on a near-black row: 3.95:1. The pastel is what that
     variant is for. */
  :root:not([data-mode="light"]) .new-badge{ color:var(--blue-2); border-color:color-mix(in srgb, var(--blue-2) 40%, transparent); }
  .inbox-topbar{ display:flex; justify-content:space-between; align-items:center; margin-bottom:6px; }

  /* CONTACTS */
  .contact-filters{ display:flex; flex-wrap:wrap; gap:6px; margin-bottom:16px; }
  .chip{ font-size:13px; padding:7px 15px; border-radius:20px; border:1px solid var(--glass-border); color:var(--text-dim); cursor:pointer; font-weight:600; transition:all var(--dur-fast) var(--ease-out); }
  .chip.active{ background:rgba(120,120,140,.12); color:var(--text); border-color:var(--glass-border-hi); }
  :root:not([data-mode="light"]) .chip.active{ box-shadow:0 0 14px 1px var(--accent-glow); }
  #contactList, #organizationList{ display:grid; grid-template-columns:repeat(3, 1fr); gap:20px; align-items:start; }
  @media(max-width:1180px){ #contactList, #organizationList{ grid-template-columns:repeat(2, 1fr); } }
  @media(max-width:720px){ #contactList, #organizationList{ grid-template-columns:1fr; } }
  .contact-row{ display:flex; flex-direction:column; gap:12px; padding:20px; cursor:pointer; border-left:none; position:relative; transition:box-shadow var(--dur-base) var(--ease-out), transform var(--dur-base) var(--ease-out); }
  .cc-head{ display:flex; align-items:flex-start; justify-content:space-between; gap:10px; }
  .contact-row .avatar{ width:52px; height:52px; font-size:16px; }
  .contact-row:hover{ border-color:var(--glass-border-hi); }
  .contact-name{ font-size:14px; font-weight:600; }
  .contact-org{ font-size:12px; color:var(--text-dim); }
  .cat-chip{ display:inline-flex; align-items:center; gap:4px; font-size:10.5px; text-transform:uppercase; letter-spacing:0.04em; padding:3px 9px; border-radius:20px; margin-left:auto; flex:none; font-weight:600; }
  .cat-chip svg{ width:10px; height:10px; flex:none; }
  .cat-Client{ background:color-mix(in srgb, var(--green) 16%, transparent); color:var(--green); }
  .cat-Prospect{ background:color-mix(in srgb, var(--blue) 16%, transparent); color:var(--blue); }
  /* A CATEGORY COLOUR IS PAINTED ON THE PANEL, AND THE PANEL CHANGES.
     --cat-blue is the solid variant and --cat-blue-2 the pastel; which one can
     be read depends entirely on what is behind it. "· Prospect" measured
     2.78:1 on midnight because it used the solid one on a near-black row.
     One token per category that already knows which ground it is on — the
     bare --cat-* names stay exactly as they are for fills, borders and glows,
     where the solid colour is still the right answer. */
  :root{
    --cat-green-text:var(--cat-green-2);   --cat-blue-text:var(--cat-blue-2);
    --cat-violet-text:var(--cat-violet-2); --cat-orange-text:var(--cat-orange-2);
    --cat-red-text:var(--cat-red-2);       --cat-teal-text:var(--cat-teal-2);
  }
  :root[data-mode="light"]{
    --cat-green-text:var(--cat-green);     --cat-blue-text:var(--cat-blue);
    --cat-violet-text:var(--cat-violet);   --cat-orange-text:var(--cat-orange);
    --cat-red-text:var(--cat-red);         --cat-teal-text:var(--cat-teal);
  }
  .cat-Colleague{ background:color-mix(in srgb, var(--violet) 16%, transparent); color:var(--violet); }
  .cat-Vendor{ background:color-mix(in srgb, var(--orange) 16%, transparent); color:var(--orange); }
  .cat-Friend{ background:color-mix(in srgb, var(--pink) 16%, transparent); color:var(--pink); }

  /* PROFILE */
  /* THE ONE WAY BACK, AND IT WAS FIFTEEN PIXELS TALL.
     Measured on a phone this was 82 × 15 — the height of its own line of text,
     because a bare flex row has no padding of its own. It is the primary way
     out of a contact, so on a phone it was the hardest thing on the screen to
     hit and the one most needed. The negative left margin keeps the arrow
     optically aligned with the heading below it, so the target grows without
     the layout moving. */
  .profile-back{ display:flex; align-items:center; gap:6px; color:var(--text-dim); font-size:13px;
    cursor:pointer; margin:0 0 10px -10px; padding:10px; border-radius:10px;
    min-height:42px; box-sizing:border-box; width:fit-content;
    -webkit-tap-highlight-color:transparent; }
  .profile-back:hover{ color:var(--text); background:rgba(120,120,140,.10); }
  .profile-back:active{ background:rgba(120,120,140,.18); }
  /* Two things here were wrong and together they pushed the right-hand boxes
     clean off the screen, which is the "can't see the rest of the contact card"
     with a horizontal scrollbar running to infinity.
       · `520px` is a FIXED track. Once the window is narrower than
         (left min-content + 18 + 520) the grid simply overflows its parent —
         it has no permission to shrink. `minmax(0, 520px)` makes 520 a maximum
         rather than a floor, so it gives ground instead of overflowing.
       · `1fr` carries an implicit `min-width:auto`, i.e. a min-content floor.
         One unbreakable run in an email preview — and previews are full of
         non-breaking spaces and long URLs — sets that floor to the width of the
         whole run. `minmax(0, 1fr)` removes the floor.
     Measured: the old rule started overflowing at ~1030px of viewport, while
     the single-column fallback below did not fire until 900px. That ~130px gap
     is exactly where Matt's window was. */
  /* Container-based, NOT viewport-based, and that distinction is the bug.
     A @media query measures the real browser window. This app runs inside a
     `zoom` wrapper for the text-size setting, and CSS zoom does not affect
     media queries — so at 110% text size the window still reads as (say)
     1140px while the layout inside it only has 1035px to work with. The
     breakpoint never fires, the two columns don't fit, and the right-hand
     boxes go off the edge. Raising the breakpoint only moves where that
     happens.
     auto-fit + minmax asks the CONTAINER how much room there actually is, so
     it is correct at every window size and every zoom level, with no
     breakpoint to get wrong. Two columns when two fit, one when they don't. */
  .profile-grid{ display:grid; grid-template-columns:repeat(auto-fit, minmax(min(100%, 400px), 1fr)); gap:18px; align-items:start; }
  .profile-grid > *{ min-width:0; }
  /* auto-fit means the two boxes drop to one column on their own once there
     isn't room, instead of squeezing to unreadable slivers. */
  .box-grid{ display:grid; grid-template-columns:repeat(auto-fit, minmax(210px, 1fr)); gap:16px; }
  .box-grid .box{ margin-bottom:0; min-width:0; }
  .profile-header{ display:flex; gap:20px; align-items:center; padding:24px; margin-bottom:16px; }
  .profile-name{ font-family:'Fraunces', serif; font-size:30px; font-weight:700; }
  .profile-org{ color:var(--text-dim); font-size:13.5px; margin-top:4px; }
  .icon-row{ display:flex; gap:8px; margin-top:12px; }
  .icon-btn{ width:34px; height:34px; border-radius:50%; display:flex; align-items:center; justify-content:center; background:rgba(120,120,140,.08); border:1px solid var(--glass-border-hi); cursor:pointer; color:var(--text-dim); transition:all var(--dur-fast) var(--ease-out); }
  .icon-btn:hover{ color:var(--blue); border-color:color-mix(in srgb, var(--blue) 50%, transparent); box-shadow:0 0 14px color-mix(in srgb, var(--blue) 25%, transparent); }
  .icon-btn svg{ width:14px; height:14px; }
  .edit-btn{ margin-left:auto; align-self:flex-start; }
  .tab-row{ display:flex; gap:4px; padding:6px; margin-bottom:14px; width:fit-content; }
  .tab{ padding:7px 16px; border-radius:9px; font-size:12.5px; font-weight:600; color:var(--text-dim); cursor:pointer; }
  .tab.active{ background:color-mix(in srgb, var(--blue) 18%, transparent); color:var(--text); box-shadow:0 0 0 1px color-mix(in srgb, var(--blue) 35%, transparent); }
  .summary-card{ padding:15px 16px; margin-bottom:18px; }
  .summary-label{ font-size:12px; font-weight:700; display:flex; align-items:center; gap:7px; margin-bottom:5px; }
  .summary-label svg{ width:13px; height:13px; color:var(--blue); }
  .summary-text{ font-size:12.5px; line-height:1.6; color:var(--text-dim); }
  .key-link{ font-size:11.5px; color:var(--blue); cursor:pointer; margin-top:8px; display:inline-block; }
  .timeline-item{ display:flex; gap:16px; padding:18px 20px; margin-bottom:10px; min-width:0; }
  /* PULSE — MEASUREMENT, NOT THREE MORE LISTS.
   *
   * It used to be read receipts, upcoming events and unread mail in three
   * columns, and Matt was right about every one of them: the events are the
   * agenda with a longer horizon, the unread is the inbox with a number on it,
   * and 300 read receipts is a log nobody reads. This is a tile grid instead —
   * a number, how it compares with your own average, and twelve weeks of shape
   * behind it. */
  .pulse-tiles{ display:grid; gap:12px; grid-template-columns:repeat(auto-fit, minmax(272px, 1fr));
    max-width:1240px; margin-bottom:26px; }
  .pulse-tile{ display:flex; flex-direction:column; gap:6px; padding:15px 17px 13px; border-radius:14px;
    border:1px solid var(--glass-border); background:var(--glass-bg); text-align:left;
    font-family:inherit; cursor:pointer; min-width:0; }
  .pulse-tile:hover{ border-color:var(--glass-border-hi); }
  .pulse-tile:focus-visible{ outline:2px solid var(--blue); outline-offset:2px; }
  .pulse-tile.flat{ cursor:default; }
  .pulse-tile.flat:hover{ border-color:var(--glass-border); }
  .pt-label{ font-size:11px; font-weight:700; text-transform:uppercase; letter-spacing:.06em;
    color:var(--text-dim); }
  .pt-figure{ display:flex; align-items:baseline; gap:9px; flex-wrap:wrap; }
  .pt-n{ font-family:'IBM Plex Mono',monospace; font-size:29px; font-weight:500; line-height:1.05;
    font-variant-numeric:tabular-nums; color:var(--text); }
  .pt-unit{ font-size:12.5px; color:var(--text-dim); }
  /* Direction is carried by the arrow and the word, never by colour alone —
   * and "up" is not automatically good here. More mail arriving is not a win. */
  .pt-delta{ font-size:11.5px; font-weight:600; color:var(--text-dim); }
  .pt-spark{ display:block; width:100%; height:34px; margin-top:2px; }
  .pt-foot{ font-size:11.5px; color:var(--text-faint); }

  .pulse-two{ display:grid; gap:22px; grid-template-columns:1fr; max-width:1240px; align-items:start; }
  @media(min-width:1040px){ .pulse-two{ grid-template-columns:minmax(0,1fr) minmax(0,1.15fr); } }

  .pulse-wait{ display:flex; flex-direction:column; gap:8px; }
  /* The row is a link AND a set of controls, so the link is its own button
     inside rather than the whole row being one — a row that is a button cannot
     contain buttons, and nesting them is how a "Snooze" click ends up opening
     the contact instead. */
  .pw-row{ display:flex; align-items:center; gap:6px; padding:5px 8px 5px 5px; border-radius:12px;
    border:1px solid var(--glass-border); background:var(--glass-bg); }
  .pw-row:hover{ border-color:var(--glass-border-hi); }
  .pw-open{ display:flex; align-items:flex-start; gap:11px; padding:6px 5px; border:0; border-radius:9px;
    background:none; cursor:pointer; font-family:inherit; text-align:left; flex:1; min-width:0; }
  .pw-open:focus-visible{ outline:2px solid var(--blue); outline-offset:2px; }
  .pw-acts{ display:flex; align-items:center; gap:4px; flex:none; opacity:0; transition:opacity var(--dur-fast) var(--ease-out); }
  .pw-row:hover .pw-acts, .pw-row:focus-within .pw-acts{ opacity:1; }
  /* Always reachable without a mouse, and always visible on a touch screen —
     an action that only appears on hover is an action a phone cannot use. */
  @media (hover:none){ .pw-acts{ opacity:1; } }
  .pw-act{ font-family:inherit; font-size:11px; font-weight:600; padding:4px 9px; border-radius:999px;
    border:1px solid var(--glass-border); background:rgba(120,120,140,.10); color:var(--text-dim); cursor:pointer; }
  .pw-act:hover{ color:var(--text); background:rgba(120,120,140,.20); }
  .pw-act:focus-visible{ outline:2px solid var(--blue); outline-offset:2px; }
  .pw-act.x{ padding:4px 8px; font-size:13px; line-height:1; }
  .pw-act.x:hover{ color:var(--pink, var(--text)); }
  .pw-av{ flex:none; width:30px; height:30px; border-radius:50%; display:flex; align-items:center;
    justify-content:center; font-size:11px; font-weight:700; background:rgba(120,120,140,.16);
    color:var(--text); }
  .pw-main{ min-width:0; flex:1; display:flex; flex-direction:column; gap:2px; }
  .pw-name{ font-size:13px; font-weight:650; color:var(--text); }
  .pw-sub{ font-size:11.5px; color:var(--text-dim); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
  /* THE LINE THAT SAYS WHERE A FACT CAME FROM.
     Every scan in Lucid ends with one: the sentence a warranty date, a job
     change or a filter size was read out of. It wants to sit BACK from the
     row above it, and the obvious way to do that — style="opacity:.72" on a
     .pw-sub — took an 11.5px line that passes at 4.6:1 down to 3.16:1. The
     readability harness caught it the moment the house page was added to the
     scene list, in eight places at once.
     So the recession is done with a COLOUR that was chosen to be readable,
     not by fading a colour that was. */
  .pw-from{ color:var(--text-faint); }
  .pw-days{ flex:none; font-family:'IBM Plex Mono',monospace; font-size:11px; color:var(--text-dim);
    font-variant-numeric:tabular-nums; }
  .pw-days.late{ color:var(--orange-text, var(--orange)); font-weight:700; }

  /* THE HEAT MAP. One hue, light to dark — never a rainbow, because the thing
   * being shown is a magnitude and a rainbow implies categories. The scale is
   * relative to the busiest hour in the window, which is the only reading that
   * means anything for one person's own mail. */
  .pulse-heat{ display:flex; flex-direction:column; gap:6px; }
  .ph-grid{ display:grid; grid-template-columns:26px repeat(24, minmax(0,1fr)); gap:2px; }
  .ph-dow{ font-family:'IBM Plex Mono',monospace; font-size:9.5px; color:var(--text-faint);
    display:flex; align-items:center; }
  .ph-cell{ aspect-ratio:1; border-radius:2px; background:rgba(120,120,140,.10); min-width:0; }
  .ph-hours{ display:grid; grid-template-columns:26px repeat(24, minmax(0,1fr)); gap:2px;
    font-family:'IBM Plex Mono',monospace; font-size:9px; color:var(--text-faint); }
  .ph-hours span{ text-align:center; }
  .ph-key{ display:flex; align-items:center; gap:7px; font-size:11px; color:var(--text-dim); }
  .ph-key i{ display:block; width:13px; height:13px; border-radius:2px; }

  .rp-receipt{ margin:0 0 14px; padding:12px 14px; border-radius:12px;
    border:1px solid color-mix(in srgb, var(--green) 34%, transparent);
    background:color-mix(in srgb, var(--green) 8%, transparent); }
  .rp-receipt-head{ display:flex; justify-content:space-between; gap:10px; align-items:baseline; }
  .rp-receipt-who{ font-size:13px; font-weight:700; color:var(--text); }
  .rp-receipt-when{ font-family:'IBM Plex Mono',monospace; font-size:10.5px; color:var(--text-dim); flex:none; }
  .rp-receipt-sub{ font-size:12.5px; color:var(--text-dim); margin-top:3px; }

  .pulse-empty{ text-align:center; padding:28px 12px; font-size:15px; font-weight:700; color:var(--text-dim); }
  .timeline-icon{ width:32px; height:32px; border-radius:50%; background:rgba(120,120,140,.10); display:flex; align-items:center; justify-content:center; flex:none; margin-top:1px; }
  .timeline-icon svg{ width:15px; height:15px; color:var(--text-dim); }
  .timeline-date{ font-family:'IBM Plex Mono', monospace; font-size:11.5px; color:var(--text-faint); float:right; padding-top:2px; margin-left:12px; white-space:nowrap; }
  /* `anywhere` rather than `break-word`: break-word will not break a run that
     is longer than the line on its own, which is precisely the case that blows
     a column open. Email previews are full of those — tracking URLs, message
     ids, base64 — so this needs to be the strong version. The 2-line clamp
     keeps a row a row: a preview is a glance, and the full message is one click
     away under "Read here". */
  .timeline-text{ font-size:14.5px; line-height:1.5; overflow-wrap:anywhere; word-break:break-word;
    display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden; }
  .timeline-from{ font-weight:700; }
  .box{ padding:20px; margin-bottom:16px; border-top-width:3px; border-top-style:solid; }
  :root:not([data-mode="light"]) .box{ box-shadow:0 8px 26px -8px rgba(0,0,0,.5), 0 -2px 20px -6px var(--box-accent, transparent); }
  /* --text-dim, not --text-faint. Ten-and-a-half-pixel uppercase measured
     2.84:1 on midnight and 4.2:1 on cream — the same failure the art credit
     had, for the same reason: the faint token is for text that is larger or
     denser than this, and a heading nobody can read is not a heading. */
  .box-title{ font-size:10.5px; text-transform:uppercase; letter-spacing:0.08em; color:var(--text-dim); margin-bottom:11px; }
  .box-title-note{ float:right; text-transform:none; letter-spacing:0; font-weight:700; }
  /* The measured relationship score. Three bars, because a single number with
     no working shown is a number nobody can argue with. */
  .sig-score{ font-size:26px; font-weight:800; line-height:1; margin-bottom:2px; }
  .sig-sub{ font-size:11.5px; color:var(--text-faint); margin-bottom:10px; }
  .sig-part{ display:flex; align-items:center; gap:8px; margin-bottom:5px; font-size:11.5px; color:var(--text-dim); }
  .sig-part-name{ width:74px; flex:none; }
  .sig-bar{ flex:1; height:5px; border-radius:3px; background:var(--glass-border); overflow:hidden; }
  .sig-bar span{ display:block; height:100%; border-radius:3px; background:currentColor; }
  .sig-part-value{ width:30px; flex:none; text-align:right; font-family:'IBM Plex Mono', monospace; font-size:10.5px; }
  .sig-caveat{ margin-top:9px; font-size:11.5px; color:var(--orange); }
  .rel-pill{ display:inline-flex; align-items:center; gap:9px; padding:9px 15px; border-radius:20px; font-size:13.5px; font-weight:700; width:100%; }
  .rel-strong{ background:color-mix(in srgb, var(--cat-green) 16%, transparent); color:var(--cat-green); box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--cat-green) 35%, transparent); }
  .rel-stable{ background:color-mix(in srgb, var(--cat-violet) 16%, transparent); color:var(--cat-violet); box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--cat-violet) 30%, transparent); }
  .rel-cooling, .rel-stale{ background:color-mix(in srgb, var(--cat-blue) 18%, transparent); color:var(--cat-blue); box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--cat-blue) 40%, transparent); }
  .rel-risk{ background:color-mix(in srgb, var(--cat-orange) 18%, transparent); color:var(--cat-orange); box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--cat-orange) 40%, transparent); }
  .rel-inactive{ background:rgba(120,120,140,.14); color:var(--text-faint); box-shadow:inset 0 0 0 1px var(--glass-border-hi); }
  .rel-dot{ width:7px; height:7px; border-radius:50%; background:currentColor; }
  .cd-tabs{ display:flex; gap:10px; border-bottom:1px solid var(--glass-border); margin-bottom:22px; }
  .cd-tab{ padding:13px 20px; font-size:14.5px; font-weight:700; color:var(--text-dim); cursor:pointer; border-bottom:2px solid transparent; margin-bottom:-1px; }
  .cd-tab:hover{ color:var(--text); }
  .cd-tab.active{ color:var(--text); border-bottom-color:var(--blue); }
  .cd-note{ padding:12px 13px; margin-bottom:8px; }
  .cd-note-date{ font-family:'IBM Plex Mono', monospace; font-size:10px; color:var(--text-faint); margin-bottom:4px; }
  .cd-action-item{ display:flex; align-items:flex-start; gap:9px; padding:10px 12px; margin-bottom:6px; }
  .cd-action-item.done{ opacity:.5; text-decoration:line-through; }
  .p-pulse{ display:flex; align-items:flex-end; gap:3px; height:28px; margin:13px 0 4px; }
  .p-pulse i{ width:4px; border-radius:1px; display:block; }
  .info-row{ font-size:12.5px; padding:8px 0; border-bottom:1px solid var(--glass-border); }
  .em-alt-row{ display:flex; align-items:center; gap:10px; padding:9px 0; border-bottom:1px solid var(--glass-border); }
  .em-alt-email{ flex:1; min-width:0; font-size:13px; word-break:break-all; }
  .em-toggle-pill{ flex:none; font-size:11px; font-weight:700; padding:5px 12px; border-radius:20px; cursor:pointer; border:1px solid var(--glass-border-hi); transition:all var(--dur-fast) var(--ease-out); }
  .em-toggle-pill.active{ background:color-mix(in srgb, var(--green) 16%, transparent); color:var(--green); border-color:color-mix(in srgb, var(--green) 40%, transparent); }
  :root:not([data-mode="light"]) .em-toggle-pill.active{ box-shadow:0 0 12px 0 var(--green-glow); }
  .em-toggle-pill.inactive{ background:rgba(120,120,140,.12); color:var(--text-faint); }
  .em-alt-remove{ flex:none; width:22px; height:22px; border-radius:50%; display:flex; align-items:center; justify-content:center; cursor:pointer; color:var(--text-faint); font-size:15px; }
  .em-alt-remove:hover{ background:color-mix(in srgb, var(--pink) 18%, transparent); color:var(--pink); }
  .cd-inactive-toggle{ display:flex; align-items:center; gap:5px; margin-top:6px; cursor:pointer; font-size:11px; font-weight:600; color:var(--text-faint); }
  .cd-inactive-toggle:hover{ color:var(--text-dim); }
  .cd-inactive-chevron{ width:12px; height:12px; transition:transform .15s; }
  .cd-inactive-chevron.open{ transform:rotate(90deg); }
  .cd-inactive-emails{ margin-top:6px; padding-top:6px; border-top:1px dashed var(--glass-border); }
  .cd-inactive-emails .cd-inactive-row{ font-size:12.5px; color:var(--text-faint); padding:2px 0; }
  .info-row:last-child{ border:none; }
  .info-label{ font-size:10px; text-transform:uppercase; color:var(--text-faint); letter-spacing:.05em; margin-bottom:2px; }
  .p-tags{ display:flex; flex-wrap:wrap; gap:6px; }
  .p-tag{ font-size:11px; border:1px solid var(--glass-border-hi); color:var(--text-dim); padding:3px 10px; border-radius:20px; }

  /* PROJECTS */
  .project-grid{ display:grid; grid-template-columns:repeat(auto-fill,minmax(230px,1fr)); gap:14px; }
  .project-card{ padding:22px; cursor:pointer; border-left:3px solid transparent; }
  .project-card:hover{ border-color:var(--glass-border-hi); }
  .project-name{ font-size:15px; font-weight:700; margin-bottom:4px; }
  .project-meta{ font-size:12px; color:var(--text-dim); margin-bottom:12px; }
  .project-progress{ height:5px; border-radius:3px; background:rgba(120,120,140,.14); overflow:hidden; }
  .project-progress-fill{ height:100%; border-radius:3px; }
  .proj-title-row{ display:flex; align-items:center; justify-content:space-between; gap:12px; margin-bottom:16px; flex-wrap:wrap; }
  .proj-title-row .page-title{ margin:0; }
  .view-toggle{ display:flex; gap:5px; padding:5px; border-radius:12px; background:rgba(120,120,140,.08); }
  .vt-btn{ padding:8px 20px; border-radius:9px; font-size:13.5px; font-weight:600; color:var(--text-dim); cursor:pointer; }
  :root:not([data-mode="light"]) .vt-btn.active{ box-shadow:0 0 14px 1px var(--accent-glow); }
  .vt-btn.active{ background:color-mix(in srgb, var(--blue) 20%, transparent); color:var(--text); }
  /* Board columns are sections, so the count is dynamic — auto-fit rather than
     a hardcoded 3, and horizontally scrollable once there are more than fit. */
  #projBoardWrap{ display:flex; gap:14px; align-items:flex-start; overflow-x:auto; padding-bottom:8px; }
  #projBoardWrap .board-col{ flex:0 0 300px; max-width:300px; }
  @media(max-width:900px){ #projBoardWrap{ display:block; } #projBoardWrap .board-col{ max-width:none; margin-bottom:14px; } }
  .board-col-head{ display:flex; align-items:center; gap:8px; }
  .sec-name{ flex:1; min-width:0; cursor:text; }
  .sec-menu{ cursor:pointer; color:var(--text-faint); padding:0 4px; border-radius:6px; }
  .sec-menu:hover{ color:var(--text); background:rgba(120,120,140,.14); }
  /* Drag and drop. The dragged card fades and tilts a touch; the column it is
     over lights up. Without a clear drop target, dragging in a dark UI is
     guesswork. */
  .task-card[draggable="true"], .list-row[draggable="true"]{ cursor:grab; }
  .task-card.dragging, .list-row.dragging{ opacity:.4; transform:rotate(1.5deg); }
  .drop-zone{ min-height:36px; border-radius:10px; transition:background var(--dur-fast) var(--ease-out), box-shadow var(--dur-fast) var(--ease-out); }
  .drop-zone.drag-over{ background:color-mix(in srgb, var(--accent) 12%, transparent); box-shadow:inset 0 0 0 1.5px color-mix(in srgb, var(--accent) 45%, transparent); }
  .proj-status-pill{ font-size:11.5px; font-weight:700; padding:5px 11px; border-radius:999px; cursor:pointer; white-space:nowrap;
    transition:background var(--dur-fast) var(--ease-out), box-shadow var(--dur-fast) var(--ease-out); }
  .proj-status-pill:hover{ box-shadow:0 0 0 1px var(--glass-border-hi); }
  .ps-on_track{ color:#7EC49E; background:rgba(63,155,107,.16); }
  .ps-at_risk{ color:#EAA766; background:rgba(217,125,46,.16); }
  .ps-off_track{ color:#D97066; background:rgba(184,56,46,.16); }
  .ps-on_hold{ color:var(--text-dim); background:rgba(120,120,140,.16); }
  .ps-complete{ color:#6E8CE8; background:rgba(41,84,217,.16); }
  /* Those five are hardcoded hex picked against a near-black panel — pastels,
     which is right there and unreadable on cream. The light themes take the
     solid tokens instead. */
  :root[data-mode="light"] .ps-on_track{ color:var(--cat-green); background:color-mix(in srgb, var(--cat-green) 15%, transparent); }
  :root[data-mode="light"] .ps-at_risk{ color:var(--cat-orange); background:color-mix(in srgb, var(--cat-orange) 15%, transparent); }
  :root[data-mode="light"] .ps-off_track{ color:var(--cat-red); background:color-mix(in srgb, var(--cat-red) 15%, transparent); }
  :root[data-mode="light"] .ps-complete{ color:var(--cat-blue); background:color-mix(in srgb, var(--cat-blue) 15%, transparent); }
  /* The Projects home strip — see renderProjectsHome(). */
  /* Custom-field chips. The colour is the option's own, carried across from
     Asana's palette — but the NAME is always printed, so the chip never relies
     on colour alone to say what it is. */
  .pj-cf{ font-weight:650; }
  .pj-cf-red{    color:var(--red,#E06C6C);    background:color-mix(in srgb, var(--red,#E06C6C) 16%, transparent); }
  .pj-cf-amber{  color:var(--amber,#C08A2E);  background:color-mix(in srgb, var(--amber,#C08A2E) 18%, transparent); }
  .pj-cf-green{  color:var(--green,#3E9E70);  background:color-mix(in srgb, var(--green,#3E9E70) 16%, transparent); }
  .pj-cf-blue{   color:var(--blue,#5B8DEF);   background:color-mix(in srgb, var(--blue,#5B8DEF) 16%, transparent); }
  .pj-cf-teal{   color:var(--teal,#3E9E9E);   background:color-mix(in srgb, var(--teal,#3E9E9E) 16%, transparent); }
  .pj-cf-purple{ color:var(--purple,#8E7BD8); background:color-mix(in srgb, var(--purple,#8E7BD8) 16%, transparent); }
  .pj-cf-pink{   color:var(--pink,#D473A8);   background:color-mix(in srgb, var(--pink,#D473A8) 16%, transparent); }
  .pj-cf-grey{   color:var(--text-dim);       background:rgba(120,120,140,.16); }
  .pj-home-queue{ max-width:1240px; margin-bottom:26px; }
  .pj-home-clear{ max-width:1240px; margin-bottom:26px; font-size:12.5px; color:var(--text-faint);
    padding:14px 16px; border-radius:14px; border:1px dashed var(--glass-border); }
  .proj-empty-sec{ color:var(--text-faint); font-size:12px; padding:10px 4px; }
  .list-sec-head{ display:flex; align-items:center; gap:8px; margin:18px 0 6px; }
  .list-sec-head .section-label{ margin:0; }
  .board-col{ padding:16px; min-height:100px; }
  .board-col-head{ display:flex; justify-content:space-between; font-size:11.5px; font-weight:700; text-transform:uppercase; letter-spacing:.05em; color:var(--text-faint); margin-bottom:10px; }
  /* Inherits --text-faint from the column head, which is the right weight for
     the heading and not enough ink for an 11px numeral on its own tinted pill. */
  /* 4.44–4.61:1 across the warm papers against a 4.5 floor — a count that
     passes on one theme and fails on the next is failing. An 11px numeral on a
     tinted pill has too little ink for --text-dim; it takes full ink, and the
     pill is what keeps it subordinate to the heading beside it. */
  .board-col-count{ background:rgba(120,120,140,.14); border-radius:20px; padding:1px 8px; font-size:11px; color:var(--text); }
  .task-card{ background:rgba(120,120,140,.06); border:1px solid var(--glass-border); border-radius:12px; padding:13px 14px; margin-bottom:10px; cursor:pointer; transition:all var(--dur-fast) var(--ease-out); }
  :root:not([data-mode="light"]) .task-card:hover{ box-shadow:0 0 18px 1px var(--accent-glow); border-color:color-mix(in srgb, var(--accent) 35%, var(--glass-border)); }
  .task-card:hover{ border-color:var(--glass-border-hi); }
  .task-card-title{ font-size:13px; font-weight:600; margin-bottom:7px; }
  .task-card-meta{ display:flex; align-items:center; gap:6px; flex-wrap:wrap; }
  .task-chip{ width:20px; height:20px; border-radius:50%; background:rgba(120,120,140,.18); display:inline-flex; align-items:center; justify-content:center; font-size:9px; font-weight:700; flex:none; }
  .task-due{ font-size:10.5px; color:var(--text-faint); }
  .synced-badge{ font-size:10px; color:var(--blue); font-weight:600; }
  .list-row{ display:flex; align-items:center; gap:12px; padding:13px 15px; margin-bottom:8px; cursor:pointer; border-radius:11px; }
  .list-row:hover{ background:rgba(120,120,140,.06); }
  .list-check{ width:15px; height:15px; border-radius:50%; border:2px solid var(--glass-border-hi); flex:none; }
  .list-check.done{ background:var(--green); border-color:var(--green); }
  .list-title{ flex:1; font-size:13px; font-weight:500; }
  .status-pills{ display:flex; gap:5px; }
  .status-pill{ font-size:12.5px; padding:6px 14px; border-radius:20px; border:1px solid var(--glass-border-hi); cursor:pointer; color:var(--text-dim); font-weight:600; transition:all var(--dur-fast) var(--ease-out); }
  .status-pill.active{ background:color-mix(in srgb, var(--blue) 20%, transparent); color:var(--text); border-color:transparent; font-weight:700; }
  :root:not([data-mode="light"]) .status-pill.active{ box-shadow:0 0 14px 1px var(--blue-glow); }
  .subtask-row{ display:flex; align-items:center; gap:9px; padding:6px 2px; font-size:12.5px; cursor:pointer; }
  .subtask-add-row{ margin-top:6px; }
  .subtask-badge{ font-size:10.5px; color:var(--text-faint); }
  .pc-head{ display:flex; align-items:center; gap:8px; margin-bottom:12px; }
  .pc-weekdays{ display:grid; grid-template-columns:repeat(7,1fr); font-size:10px; color:var(--text-faint); text-align:center; margin-bottom:4px; }
  .pc-grid{ display:grid; grid-template-columns:repeat(7,1fr); gap:6px; }
  .pc-cell{ min-height:74px; border-radius:10px; background:rgba(120,120,140,.05); border:1px solid var(--glass-border); padding:6px; }
  .pc-cell.pc-pad{ background:none; border:none; }
  .pc-daynum{ font-size:11px; color:var(--text-faint); margin-bottom:4px; }
  .pc-chip{ font-size:10px; background:rgba(120,120,140,.14); border-radius:6px; padding:2px 6px; margin-bottom:3px; cursor:pointer; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
  .pc-chip:hover{ background:rgba(120,120,140,.22); }
  /* ==========================================================
     HOME — the front door. SECOND PASS: MONOCHROME WITH GLOW.
     ----------------------------------------------------------
     The first version shipped correct and looked like a control panel: eight
     equal rounded rectangles in a four-column grid, each carrying its own
     coloured chip. The verdict on it was four things at once — too busy, too
     many colours; the grid of equal boxes feels cheap; the typography looks
     like a web app; and, pulling the other way, it is not rich enough.

     Those are not four faults, they are one fault seen from two sides.
     "Too busy" and "not rich enough" only contradict each other if richness is
     assumed to come from HUE. It does not. The resolution is to spend
     restraint on hue and richness on LIGHT:

       · ONE accent on the whole page, not six. Nothing here uses colour to say
         what KIND of thing it is. Type weight, size and position do that job,
         and they do it without turning the page into a fruit salad.

       · DEPTH COMES FROM GLOW AND AIR, NOT FROM BORDERS. The tiles lose their
         frames completely. What separates one from the next is thirty pixels
         of nothing and a hairline that runs off the end of its label. A framed
         box says "widget"; an unframed column of type says "page".

       · THE PAGE HAS A LEAD. A full-bleed band across the top — off the edges
         of the page rather than inside a card — carries the greeting, the one
         thing most worth doing, and the count of what is outstanding set in
         the serif. Everything below it is subordinate. A grid with no lead is
         what made the first version read as a dashboard.

       · SPANS ARE TWELFTHS, NOT QUARTERS, so rows run 7/5 and 5/7 rather than
         a chessboard. The eye follows a line down the page instead of scanning
         a matrix. This is the single change that kills "grid of equal boxes".

       · URGENCY IS LUMINOSITY. A card that matters glows a little at its left
         edge. It does not turn red. In light mode a glow is invisible, so the
         same signal is carried by ink weight instead — see the guard below.

     Layout is still an ORDERED LIST with per-tile spans, not absolute x/y.
     A real 2D packing model demos better and falls apart on a phone; an
     ordered flow collapses to one column for free and can never leave a hole
     in the middle of the page.
     ========================================================== */

  /* ---- the band: the only full-bleed element in the app ---------------- */
  /* Negative margins cancel .main's padding exactly. When that padding changes
     for small screens, so must this — the override is directly below. */
  .home-band{ position:relative; margin:-4px -30px 30px; padding:38px 30px 32px; overflow:hidden; }

  /* THE ONE LESSON FROM THE LIGHT-MODE READABILITY BUG, APPLIED IN ADVANCE.
     An absolutely positioned ::before with no z-index paints ABOVE every
     non-positioned child — that is exactly what made .glass unreadable. So the
     glow is pinned to z-index 0 and every child of the band is lifted to 1.
     Do not remove either half. */
  .home-band::before{
    /* The pools sit INSIDE the band. The first attempt hung them 80% above it
       with the band clipping at overflow:hidden, so the bright centre of every
       one of them was cropped off and all that showed was the faint tail. */
    content:''; position:absolute; left:-18%; right:-10%; top:-55%; height:205%;
    background:
      radial-gradient(38% 52% at 24% 52%, color-mix(in srgb, var(--head-glow, var(--accent-glow)) 88%, transparent), transparent 82%),
      radial-gradient(30% 42% at 58% 76%, color-mix(in srgb, var(--head-glow, var(--accent-glow)) 46%, transparent), transparent 80%),
      radial-gradient(52% 64% at 90% 14%, color-mix(in srgb, var(--head-tint, var(--accent-2, var(--accent))) 20%, transparent), transparent 80%);
    /* Masked out before the clip. Without this the pools meet overflow:hidden
       at full strength and the band ends on a ruled edge of light, which reads
       as a rectangle drawn in blue rather than as a light source. */
    -webkit-mask-image:linear-gradient(180deg, #000 0, #000 54%, transparent 92%);
    mask-image:linear-gradient(180deg, #000 0, #000 54%, transparent 92%);
    pointer-events:none; z-index:0;
  }
  /* On paper, a coloured wash is a stain rather than a light source, so the
     same three pools are dropped to a hint. Light mode gets its depth from the
     hairline and the air instead — see the heat rule further down. */
  :root[data-mode="light"] .home-band::before{ opacity:.30; }
  /* Lite mode exists to stop paying for blur and for animation loops. A static
     radial gradient is neither — it is painted once and costs nothing to keep,
     and hiding it was removing the only thing that makes the front door look
     like anything. It stays, at half strength. */
  :root[data-fx="lite"] .home-band::before{ opacity:.55; }
  .home-band > *{ position:relative; z-index:1; }
  .home-band::after{
    content:''; position:absolute; left:30px; right:30px; bottom:0; height:1px; z-index:1;
    background:linear-gradient(90deg, var(--glass-border-hi), transparent 72%);
  }

  /* ==========================================================
     C14 — THE LIVING SKY
     ==========================================================
     Matt, on Home: "All of these sound good. Motion would be great. (love to
     see the weather idea) Bigger, bolder, colorful, anything to lift this from
     a board to an experience."

     WHAT IT IS. A weather-and-time layer inside the masthead, painted from the
     real reading that the Weather card and the Today page already use — see
     paintHomeSky(). Two attributes drive everything below:

       data-sky = dawn | day | dusk | night     — from the clock and sunset
       data-wx  = sunny | partly | cloudy | rain | storm   — from the reading

     WHY IT IS NOT DECORATION. Motion for its own sake is a screensaver and gets
     old in a week. This moves because the weather moves: clouds actually cross
     when it is cloudy, rain actually falls when it is raining, and the colour
     of the band at 6am is not the colour of the band at 9pm. It tells you
     something before you have read a single word, which is what separates an
     experience from an animated board.

     WHAT IT COSTS. Nothing per view and nothing per month. Every layer is a
     gradient or a blurred ellipse moved by a transform, so it is all on the
     compositor — no canvas, no timer, no request. The weather it reads was
     already being fetched.

     AND WHEN IT MUST STOP. Two switches turn all of it off, and both are
     honoured rather than dimmed: the operating system's reduced-motion
     setting, and Lucid's own Lite effects mode, which exists precisely so a
     tired machine can stop paying for animation. In both cases the colour
     stays and only the movement goes — the information is in the colour. */
  .home-sky{
    position:absolute; inset:0; overflow:hidden; pointer-events:none;
    /* Above the accent pools, below every child of the band. .home-band > *
       sets z-index 1 on direct children, and this is a direct child, so it
       needs a rule of its own to sit underneath the type. */
    z-index:0;
    /* Fades out downwards so the band still ends on air rather than on a line
       of weather — the same reasoning as the mask on .home-band::before. */
    -webkit-mask-image:linear-gradient(180deg, #000 0, #000 58%, transparent 97%);
    mask-image:linear-gradient(180deg, #000 0, #000 58%, transparent 97%);
  }
  /* THE WASH. Four times of day, and they are genuinely different colours
     rather than four opacities of one. Painted on the layer itself so the
     clouds and the rain sit in front of it. */
  .home-sky::before{
    content:''; position:absolute; inset:0;
    background:linear-gradient(160deg,
      color-mix(in srgb, var(--sky-a, var(--accent)) 34%, transparent) 0%,
      color-mix(in srgb, var(--sky-b, var(--accent-2, var(--accent))) 20%, transparent) 46%,
      transparent 88%);
    transition:background 1.2s var(--ease-out);
  }
  .home-band[data-sky="dawn"]  .home-sky{ --sky-a:#ff9e6d; --sky-b:#7f6bd0; }
  .home-band[data-sky="day"]   .home-sky{ --sky-a:#57b6f5; --sky-b:#8fd8e8; }
  .home-band[data-sky="dusk"]  .home-sky{ --sky-a:#ff7b54; --sky-b:#5a4a9e; }
  .home-band[data-sky="night"] .home-sky{ --sky-a:#2b3a7a; --sky-b:#101736; }
  /* On paper a full-strength coloured sky is a stain rather than a light
     source — the same call already made for the accent pools above at .30 and
     for Lite mode at .55, and made here for the same reason rather than for a
     contrast one. The readability problem the sky caused was real and is fixed
     where it belongs: see the ground under .home-boards. */
  :root[data-mode="light"] .home-sky{ opacity:.55; }
  :root[data-mode="light"] .home-sky::before{ opacity:.34; }

  /* THE SUN, OR THE MOON. One disc; which one it is, is a colour and a
     position. It breathes rather than pulses — 14 seconds, and the amplitude is
     small enough that you notice it only if you look. */
  .sky-orb{
    position:absolute; right:9%; top:-14%; width:190px; height:190px; border-radius:50%;
    background:radial-gradient(circle at 50% 50%,
      color-mix(in srgb, var(--sky-orb, #ffd28a) 82%, transparent) 0%,
      color-mix(in srgb, var(--sky-orb, #ffd28a) 30%, transparent) 42%,
      transparent 70%);
    filter:blur(2px); opacity:.85;
    animation:skyBreathe 14s ease-in-out infinite;
  }
  .home-band[data-sky="dawn"]  .home-sky{ --sky-orb:#ffcf9a; }
  .home-band[data-sky="day"]   .home-sky{ --sky-orb:#ffe6a3; }
  .home-band[data-sky="dusk"]  .home-sky{ --sky-orb:#ff9a6a; }
  .home-band[data-sky="night"] .home-sky{ --sky-orb:#cfd8ff; }
  /* Overcast and worse: the sun is behind it, so it is a glow rather than a
     disc. Not hidden — a sky with no light source in it reads as a mistake. */
  .home-band[data-wx="cloudy"] .sky-orb,
  .home-band[data-wx="rain"] .sky-orb,
  .home-band[data-wx="storm"] .sky-orb{ opacity:.30; filter:blur(16px); }
  /* TRANSFORM ONLY, AND THAT IS NOT A STYLE PREFERENCE.
     This used to breathe opacity from .85 to 1 as well, and an animated
     property beats every static rule for that property no matter how specific
     — so the overcast rule above, which drops the sun to .30 behind the cloud,
     had no effect whatsoever: the keyframes simply overwrote it sixty times a
     second. The sun stayed exactly as bright in a thunderstorm as in clear sky.
     Caught by measuring the computed opacity per condition rather than reading
     the rule and assuming. Scale is not a property anything else here sets, so
     it can be animated safely. */
  @keyframes skyBreathe{
    0%,100%{ transform:scale(1); }
    50%{ transform:scale(1.06); }
  }

  /* THE CLOUDS. Three, at three sizes, three speeds and three heights, because
     three identical shapes moving together is a conveyor belt rather than a
     sky. They cross the full width and are placed off-screen at both ends, so
     there is no moment where one appears out of nothing. */
  .sky-cloud{
    position:absolute; border-radius:50%; pointer-events:none; opacity:0;
    background:radial-gradient(ellipse at 50% 55%,
      color-mix(in srgb, var(--sky-cloud, #ffffff) 40%, transparent) 0%,
      color-mix(in srgb, var(--sky-cloud, #ffffff) 14%, transparent) 52%,
      transparent 76%);
    filter:blur(10px);
    transition:opacity 1.2s var(--ease-out);
  }
  .home-band[data-sky="night"] .home-sky{ --sky-cloud:#8ea0d8; }
  .sky-cloud.c1{ width:420px; height:150px; top:6%;  left:-30%; animation:skyDrift 82s linear infinite; }
  .sky-cloud.c2{ width:300px; height:110px; top:34%; left:-30%; animation:skyDrift 128s linear infinite -34s; }
  .sky-cloud.c3{ width:560px; height:180px; top:-8%; left:-30%; animation:skyDrift 168s linear infinite -96s; }
  /* How many are visible IS the forecast. Clear sky, no clouds; partly, one;
     overcast, all three at full weight. */
  .home-band[data-wx="partly"] .sky-cloud.c1{ opacity:.55; }
  .home-band[data-wx="partly"] .sky-cloud.c2{ opacity:.30; }
  .home-band[data-wx="cloudy"] .sky-cloud,
  .home-band[data-wx="rain"] .sky-cloud,
  .home-band[data-wx="storm"] .sky-cloud{ opacity:.72; }
  @keyframes skyDrift{ from{ transform:translateX(0); } to{ transform:translateX(190vw); } }

  /* WHAT IS FALLING. One repeating gradient panel, translated diagonally and
     tiled — a hundred individual raindrop elements would be a hundred layers
     for a texture that reads as one. Hidden unless it is actually raining. */
  .sky-fall{
    position:absolute; inset:-30% -10% -10%; opacity:0; pointer-events:none;
    background-image:repeating-linear-gradient(74deg,
      color-mix(in srgb, var(--sky-rain, #cfe4ff) 40%, transparent) 0 1px,
      transparent 1px 9px);
    background-size:auto 120px;
    animation:skyFall 0.85s linear infinite;
    transition:opacity 1.2s var(--ease-out);
  }
  .home-band[data-wx="rain"] .sky-fall{ opacity:.22; }
  .home-band[data-wx="storm"] .sky-fall{ opacity:.34; animation-duration:.6s; }
  @keyframes skyFall{ from{ background-position:0 0; } to{ background-position:-34px 120px; } }

  /* A STORM IS A THING THAT HAPPENS OCCASIONALLY, NOT A STROBE.
     Nine seconds apart, two frames long, and never at full white — the point
     is that you catch it out of the corner of your eye once in a while. */
  .sky-flash{ position:absolute; inset:0; opacity:0; background:rgba(226,236,255,.5); pointer-events:none; }
  .home-band[data-wx="storm"] .sky-flash{ animation:skyFlash 9s steps(1,end) infinite; }
  @keyframes skyFlash{
    0%, 96%, 100%{ opacity:0; }
    96.6%{ opacity:.30; }
    97.2%{ opacity:0; }
    97.8%{ opacity:.18; }
    98.4%{ opacity:0; }
  }

  /* THE TWO SWITCHES. Colour stays, movement goes — in both cases the sky is
     still telling the truth about the weather, it has just stopped moving. */
  @media (prefers-reduced-motion: reduce){
    .sky-orb, .sky-cloud, .sky-fall, .sky-flash{ animation:none !important; }
  }
  :root[data-fx="lite"] .sky-orb,
  :root[data-fx="lite"] .sky-cloud,
  :root[data-fx="lite"] .sky-fall,
  :root[data-fx="lite"] .sky-flash{ animation:none !important; }
  :root[data-fx="lite"] .sky-cloud{ filter:blur(6px); }

  /* THE READING, BIG. "Bigger, bolder, colorful" — this is the number, at a
     size that makes the top of the page about the day rather than about the
     app. It sits in the masthead beside the lead, so it costs no tile. */
  .home-wx{
    display:flex; align-items:center; gap:14px; cursor:pointer; flex:none;
    padding:10px 16px 10px 12px; border-radius:16px;
    border:1px solid transparent; transition:border-color .18s, background .18s, transform .18s;
  }
  .home-wx:hover{ border-color:var(--glass-border-hi); background:rgba(120,120,140,.10); transform:translateY(-1px); }
  .home-wx:focus-visible{ outline:2px solid var(--accent); outline-offset:2px; }
  .home-wx-ico{ width:44px; height:44px; flex:none; color:var(--sky-ink, var(--accent)); }
  /* The three lines are spans so the markup stays one flat string, so they are
     told to stack here rather than relying on the tag name. */
  .home-wx-temp{ display:block; font-family:'Fraunces', serif; font-size:clamp(34px, 3.4vw, 48px);
    font-weight:600; line-height:1; letter-spacing:-.03em; font-variation-settings:'opsz' 96; }
  .home-wx-cond{ display:block; font-size:13px; font-weight:600; color:var(--text-dim); margin-top:3px; }
  .home-wx-hilo{ display:block; font-family:'IBM Plex Mono', monospace; font-size:11.5px;
    color:var(--text-faint); margin-top:2px; }
  @media(max-width:760px){
    .home-wx{ padding-left:0; }
    .home-wx-ico{ width:34px; height:34px; }
  }

  .home-band-top{ display:flex; align-items:flex-start; justify-content:space-between; gap:18px; flex-wrap:wrap; }
  .home-band-main{ display:flex; align-items:flex-start; justify-content:space-between;
    gap:30px 64px; flex-wrap:wrap; }
  .home-head-acts{ display:flex; align-items:center; gap:8px; flex-wrap:wrap; }

  /* Typography is the whole design here. Fraunces at a real display size, one
     step of tracking tightened, and a line height that lets it breathe — not
     a 27px bold heading that could belong to any admin panel. */
  .home-greet{ font-family:'Fraunces', serif; font-size:clamp(30px, 3.6vw, 46px);
    font-weight:500; letter-spacing:-.022em; line-height:1.04; margin:0 0 8px;
    font-variation-settings:'opsz' 96; }
  /* TEN-PIXEL UPPERCASE IS NOT ENOUGH INK FOR --text-faint. Measured, not
     judged: the date line came back at 4.35:1 against a 4.5 floor on dark, and
     the kicker at 4.28:1. Tracked-out capitals put less ink on the page than
     their size suggests, so every label at this size on Home takes --text-dim.
     They are still plainly recessive against --text; they are just legible. */
  .home-date{ font-size:10.5px; font-weight:600; letter-spacing:.17em; text-transform:uppercase;
    color:var(--text-dim); margin:0; }

  /* ---- the lead: one thing, said properly ------------------------------ */
  .home-lead{ display:flex; align-items:stretch; gap:16px; margin-top:28px; }
  #homeLead{ flex:1 1 420px; min-width:0; }
  .home-lead-mark{ width:2px; border-radius:2px; background:var(--accent); flex:none; }
  :root:not([data-mode="light"]) .home-lead-mark{ box-shadow:0 0 16px 2px var(--accent-glow); }
  .home-lead-body{ min-width:0; }
  .home-lead-kicker{ font-size:10px; font-weight:700; letter-spacing:.19em; text-transform:uppercase;
    color:var(--text-dim); margin-bottom:7px; }
  .home-lead-title{ font-family:'Fraunces', serif; font-size:clamp(19px, 2.1vw, 27px);
    font-weight:600; line-height:1.24; letter-spacing:-.012em; cursor:pointer; }
  .home-lead-why{ font-size:12.5px; color:var(--text-dim); margin-top:7px; line-height:1.5; }
  .home-lead-acts{ display:flex; gap:8px; margin-top:14px; flex-wrap:wrap; }

  /* ---- the count, set in the serif ------------------------------------- */
  /* Two by two, not four across. A single row of four numbers reads as a
     scoreboard; a block of four reads as a masthead, and it holds the right
     side of the band without needing anything invented to fill it. */
  .home-stats{ display:grid; grid-template-columns:repeat(2, minmax(120px, auto));
    gap:24px 46px; margin-top:34px; flex:none; }
  .home-stat{ cursor:pointer; }
  .home-stat-n{ font-family:'Fraunces', serif; font-size:34px; font-weight:600;
    line-height:1; letter-spacing:-.03em; }
  .home-stat-l{ font-size:10px; font-weight:600; letter-spacing:.15em; text-transform:uppercase;
    color:var(--text-dim); margin-top:8px; }
  .home-stat:hover .home-stat-n{ color:var(--accent-2, var(--accent)); }
  :root[data-mode="light"] .home-stat:hover .home-stat-n{ color:var(--accent); }

  /* ---- boards: Go and Calm are just the first two ---------------------- */
  /* A CONTROL OVER A MOVING BACKGROUND NEEDS A GROUND OF ITS OWN.
     These tabs are 12px at --text-dim with nothing behind them, which was
     marginal against the flat band and became unreadable once the weather sky
     went in underneath: test-readability.js measured "Calm" at 2.74:1 against a
     floor of 4.5 in dark, and at 3.62:1 in serene:mist:cool. Every theme, same
     cause.
     Dimming the sky was the first attempt and it is the wrong fix — it makes
     the number pass by making the feature weaker, and it has to be re-tuned for
     every theme separately. A ground fixes it once: at 86% of the page's own
     background the sky contributes about a seventh of what is behind the
     letters, in any theme, whatever the weather is doing. It also reads as
     what it is, which is a segmented control. */
  .home-boards{ display:flex; align-items:center; gap:2px; flex-wrap:wrap;
    background:color-mix(in srgb, var(--bg-base) 86%, transparent);
    border:1px solid var(--glass-border); border-radius:22px; padding:3px; }
  .hb-tab{ font-size:12px; font-weight:600; padding:6px 13px; border-radius:20px; cursor:pointer;
    color:var(--text-dim); white-space:nowrap; display:inline-flex; align-items:center;
    transition:color var(--dur-fast) var(--ease-out), background var(--dur-fast) var(--ease-out); }
  .hb-tab:hover{ color:var(--text); background:color-mix(in srgb, var(--text) 8%, transparent); }
  .hb-tab.active{ color:var(--text); background:color-mix(in srgb, var(--text) 11%, transparent); }
  :root:not([data-mode="light"]) .hb-tab.active{
    box-shadow:0 0 0 1px color-mix(in srgb, var(--accent) 38%, transparent), 0 0 18px -3px var(--accent-glow); }
  .hb-del{ margin-left:7px; margin-right:-3px; font-size:13px; line-height:1; color:var(--text-faint); }
  .hb-del:hover{ color:var(--cat-red-text); }

  /* ---- the grid -------------------------------------------------------- */
  .home-grid{ display:grid; grid-template-columns:repeat(12, minmax(0,1fr));
    gap:32px 34px; align-items:start; }
  @media(max-width:1180px){ .home-grid{ gap:28px; } .home-grid .ht{ grid-column:span 6 !important; } }
  @media(max-width:760px){ .home-grid .ht{ grid-column:span 12 !important; } }

  /* A TILE IS NOT A BOX ANY MORE. No background, no border, no padding —
     it is a column of type with a label over it. Everything that used to be
     carried by the frame is carried by the space around it. */
  .ht{ display:flex; flex-direction:column; min-width:0; position:relative;
    min-height:calc(var(--ht-rows, 2) * 72px); }
  .ht-head{ display:flex; align-items:center; gap:11px; margin-bottom:13px; flex:none; }
  .ht-title{ font-size:10.5px; font-weight:700; text-transform:uppercase; letter-spacing:.16em;
    color:var(--text-dim); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; flex:none; max-width:70%; }
  /* The rule runs off the end of the label and fades. A full-width rule boxes
     the tile back in, which is the thing being undone here. */
  .ht-rule{ flex:1; height:1px; min-width:14px;
    background:linear-gradient(90deg, var(--glass-border-hi), transparent 88%); }
  .ht-body{ flex:1; min-height:0; overflow-y:auto; margin:0 -4px; padding:0 4px; scrollbar-width:thin; }
  /* A tile whose content fills its own area to the edges. The header stays on
     the page background above it, deliberately: a label sitting on top of a
     photograph is legible against this week's picture and invisible against
     next week's. */
  .ht-bleed .ht-body{ position:relative; overflow:hidden; margin:0; padding:0;
    border-radius:16px; min-height:190px; }
  .ht-ctl{ cursor:pointer; color:var(--text-faint); width:22px; height:22px; border-radius:6px;
    display:flex; align-items:center; justify-content:center; font-size:13px; flex:none; }
  .ht-ctl:hover{ color:var(--text); background:color-mix(in srgb, var(--text) 9%, transparent); }
  .ht-ctl.danger:hover{ color:var(--cat-red-text); background:color-mix(in srgb, var(--cat-red) 14%, transparent); }
  /* Unframed tiles cannot be dragged if you cannot see where they start, so
     the frame comes back — dashed, outside the content — only while arranging. */
  /* Tiles that draw a shape rather than a list fill their row. A chart that
     stops halfway down its row leaves a hole beside whatever is taller; a list
     stretched to match one would just be a list with padding under it, which is
     why this is opt-in rather than align-items:stretch on the grid. */
  .ht-fill{ align-self:stretch; }
  .home-grid.editing .ht{ cursor:grab; border-radius:14px; animation:none;
    outline:1px dashed var(--glass-border-hi); outline-offset:11px; }
  .home-grid.editing .ht.dragging{ opacity:.4; cursor:grabbing; }
  /* THE WHOLE TILE IS THE HANDLE.
     A picture inside a tile is a drag source in its own right, so grabbing the
     album art or a face dragged the image instead of the tile — which is most
     of the surface of the tiles worth moving. While arranging, nothing in the
     body takes the pointer, so every part of a tile picks the tile up. */
  .home-grid.editing .ht-body{ pointer-events:none; user-select:none; }
  .home-grid.editing .ht-body *{ -webkit-user-drag:none; }
  /* Where it will land. */
  .home-grid.editing .ht.drop-here{ outline-style:solid;
    outline-color:color-mix(in srgb, var(--accent) 70%, transparent); }
  :root:not([data-mode="light"]) .home-grid.editing .ht.drop-here{ box-shadow:0 0 22px 0 var(--accent-glow); }

  /* Staggered arrival. Fifty milliseconds apart is under the threshold where
     it reads as a queue and over the one where it reads as a jump-cut. */
  @keyframes htRise{ from{ opacity:0; transform:translateY(11px); } to{ opacity:1; transform:none; } }
  .home-grid .ht{ animation:htRise .5s var(--ease-out) both; animation-delay:calc(var(--i, 0) * 50ms); }
  :root[data-fx="lite"] .home-grid .ht{ animation:none; }
  @media (prefers-reduced-motion: reduce){ .home-grid .ht{ animation:none; } }

  /* A CARD IS A CLAIM, A REASON, AND SOMETHING TO DO ABOUT IT.
     The reason line is not decoration — a card that asserts something about
     his life without saying how it knows reads as surveillance rather than as
     help, and he stops trusting the page. */
  .ht-card{ display:flex; align-items:flex-start; gap:12px; padding:12px 0 12px 15px; position:relative; }
  .ht-card + .ht-card{ border-top:1px solid var(--glass-border); }
  /* The heat mark. Same geometry for every card so the column of them reads as
     a margin rule; only its brightness changes. z-index 0 + positioned
     children, for the reason spelled out at the top of this block. */
  .ht-card::before{ content:''; position:absolute; left:0; top:15px; bottom:15px; width:2px;
    border-radius:2px; z-index:0; background:color-mix(in srgb, var(--text) 13%, transparent); }
  .ht-card[data-heat="1"]::before{ background:var(--accent); }
  :root:not([data-mode="light"]) .ht-card[data-heat="1"]::before{ box-shadow:0 0 12px 1px var(--accent-glow); }
  /* Light mode gets no glow, so heat is carried by ink instead. Weight, not
     hue — the page still has one accent on it. */
  :root[data-mode="light"] .ht-card[data-heat="1"] .ht-card-title{ font-weight:700; }
  .ht-card-main{ flex:1; min-width:0; cursor:pointer; position:relative; z-index:1; }
  .ht-card-title{ font-size:14px; font-weight:600; line-height:1.35; letter-spacing:-.004em;
    overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
  .ht-why{ font-size:11.5px; color:var(--text-faint); margin-top:4px; line-height:1.5; }
  /* Conversation continuity: the sentence the other person actually wrote.
     Set as a quote and given the dim token rather than the faint one — it is
     the most valuable line on the card, and it is somebody's own words. */
  .ht-quote{ display:block; margin-top:3px; color:var(--text-dim); font-style:italic;
             overflow:hidden; text-overflow:ellipsis; }
  /* The keeping-up record: one mark per period, oldest first. Shape carries the
     meaning — filled, hollow, faded — so it reads the same to anybody who
     cannot tell the accent from the background. */
  .goal-dots{ display:inline-flex; gap:3px; margin-left:7px; vertical-align:middle; }
  .goal-dot{ width:7px; height:7px; border-radius:50%; border:1.5px solid var(--text-faint);
             display:inline-block; }
  .goal-dot.on{ background:var(--accent); border-color:var(--accent); }
  .goal-dot.before{ opacity:.35; border-style:dotted; }
  .ht-card-acts{ display:flex; align-items:center; gap:5px; flex:none; position:relative; z-index:1; }

  /* Ghost verbs. The old ones were solid blue text on every single card, which
     is six accents per tile pretending to be one. */
  .ht-verb{ font-size:11px; font-weight:600; color:var(--text-dim); cursor:pointer;
    padding:4px 11px; border-radius:20px; white-space:nowrap; border:1px solid var(--glass-border);
    transition:color var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out),
               background var(--dur-fast) var(--ease-out); }
  .ht-verb:hover{ color:var(--text); border-color:var(--glass-border-hi);
    background:color-mix(in srgb, var(--text) 8%, transparent); }
  .ht-verb.primary{ color:var(--text); border-color:color-mix(in srgb, var(--accent) 50%, transparent); }
  :root:not([data-mode="light"]) .ht-verb.primary:hover{ box-shadow:0 0 16px -3px var(--accent-glow); }
  .ht-x{ cursor:pointer; color:var(--text-faint); font-size:15px; line-height:1;
    padding:3px 6px; border-radius:20px; }
  .ht-x:hover{ color:var(--cat-red-text); background:color-mix(in srgb, var(--cat-red) 12%, transparent); }

  /* A face is worth more than a chip for telling one person from another, and
     it costs no colour — this variant is deliberately ink, not the rainbow
     gradient the mail rows use. */
  .ht-av{ width:30px; height:30px; border-radius:50%; flex:none; margin-top:1px;
    display:flex; align-items:center; justify-content:center;
    font-size:11px; font-weight:700; color:var(--text);
    background:color-mix(in srgb, var(--text) 8%, transparent);
    border:1px solid var(--glass-border); position:relative; z-index:1; }

  /* 4.50:1 against a 4.5 floor is not a pass, it is a coin toss. Empty states
     are the copy a person reads most carefully — it is the only thing in the
     tile — so this one takes --text-dim too. */
  .ht-empty{ font-size:12.5px; color:var(--text-dim); line-height:1.6; padding:6px 0; }
  .ht-line{ display:flex; align-items:baseline; gap:12px; padding:9px 0; font-size:13.5px; cursor:pointer; }
  .ht-line + .ht-line{ border-top:1px solid var(--glass-border); }
  .ht-line-t{ flex:1; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
  /* Wide enough for "11:00 AM" on one line. At 54px it wrapped, and a wrapped
     time is what pushed the first event row to double height. */
  .ht-time{ font-size:10.5px; color:var(--text-faint); flex:none; width:68px;
    letter-spacing:.02em; white-space:nowrap; }
  /* Numerals belong to the serif everywhere on this page. That single rule is
     most of what stops it looking like a web app. */
  .ht-num, .ht-time{ font-family:'IBM Plex Mono', monospace; }
  .ht-big{ font-family:'Fraunces', serif; font-size:42px; font-weight:500; letter-spacing:-.03em;
    line-height:1; cursor:pointer; display:flex; align-items:baseline; gap:10px; }
  .ht-big-sub{ font-size:16px; font-weight:500; color:var(--text-faint); letter-spacing:-.01em; }
  .ht-quiet-line{ font-family:'Fraunces', serif; font-size:22px; font-weight:500;
    line-height:1.28; letter-spacing:-.015em; }

  /* ---- the shapes: the tiles that are not lists ------------------------ */

  /* A year of correspondence. One series, so no legend — the tile title names
     it. Thin marks, a soft accent wash under the line, one dot on the last
     point, and no gridlines at all: at 52 points a grid is noise, and the
     question this answers is "what shape is my year", not "what was week 31". */
  .ht-pulse{ display:flex; flex-direction:column; gap:14px; height:100%; }
  .ht-pulse-head{ display:flex; align-items:flex-start; justify-content:space-between; gap:16px; flex-wrap:wrap; }
  .ht-pulse-right{ text-align:right; }
  .ht-pulse-delta{ font-size:12.5px; font-weight:600; color:var(--text-dim); }
  /* A non-breaking space, because a trailing space in `content` is collapsed
     and the arrow ends up welded to the number. */
  .ht-dir-up::before{ content:'\2191\00a0'; }
  .ht-dir-down::before{ content:'\2193\00a0'; }
  .ht-spark{ width:100%; flex:1; min-height:96px; display:block; overflow:visible; }
  :root:not([data-mode="light"]) .ht-spark{ filter:drop-shadow(0 0 12px var(--accent-glow)); }
  .ht-spark-axis{ display:flex; justify-content:space-between; font-size:10px; font-weight:600;
    letter-spacing:.14em; text-transform:uppercase; color:var(--text-dim); }

  /* The people actually in his week. Sized by volume, monochrome, no chips. */
  /* A GRID, NOT A WRAPPING FLEX ROW. Twelve faces at their natural width came
     out as eleven and an orphan on a 1600px screen — which reads as a mistake,
     because it is one. Six columns is two clean rows at any width. */
  .ht-circle{ display:grid; grid-template-columns:repeat(6, minmax(0, 1fr));
    gap:18px 10px; align-items:end; padding-top:4px; }
  @media(max-width:1180px){ .ht-circle{ grid-template-columns:repeat(4, minmax(0,1fr)); } }
  @media(max-width:560px){ .ht-circle{ grid-template-columns:repeat(3, minmax(0,1fr)); } }
  .ht-face{ display:flex; flex-direction:column; align-items:center; gap:6px; cursor:pointer; min-width:0; }
  .ht-face-d{ border-radius:50%; display:flex; align-items:center; justify-content:center;
    font-weight:700; color:var(--text); background:color-mix(in srgb, var(--text) 9%, transparent);
    border:1px solid var(--glass-border); flex:none;
    transition:border-color var(--dur-fast) var(--ease-out), box-shadow var(--dur-fast) var(--ease-out); }
  .ht-face.lead .ht-face-d{ border-color:color-mix(in srgb, var(--accent) 55%, transparent); }
  :root:not([data-mode="light"]) .ht-face.lead .ht-face-d{ box-shadow:0 0 20px -2px var(--accent-glow); }
  .ht-face:hover .ht-face-d{ border-color:var(--glass-border-hi); }
  :root:not([data-mode="light"]) .ht-face:hover .ht-face-d{ box-shadow:0 0 18px -3px var(--accent-glow); }
  .ht-face-n{ font-size:11.5px; font-weight:600; max-width:100%; overflow:hidden;
    text-overflow:ellipsis; white-space:nowrap; text-align:center; }
  .ht-face-c{ font-family:'IBM Plex Mono', monospace; font-size:10px; color:var(--text-dim); }

  /* Weather as a small chart rather than a number. One shared scale across the
     five days: five independently-scaled bars are five separate charts. */
  .ht-wx{ display:flex; flex-direction:column; gap:16px; height:100%; cursor:pointer; }
  .ht-wx-now{ display:flex; align-items:center; gap:14px; }
  .ht-wx-meta{ min-width:0; }
  .ht-wx-cond{ font-size:13.5px; font-weight:600; }
  .ht-wx-days{ display:grid; grid-template-columns:repeat(5, 1fr); gap:8px; align-items:end; }
  .ht-wx-day{ display:flex; flex-direction:column; align-items:center; gap:5px; }
  .ht-wx-dn{ font-size:10px; font-weight:700; letter-spacing:.1em; text-transform:uppercase; color:var(--text-dim); }
  .ht-wx-ico{ width:17px; height:17px; color:var(--text-dim); }
  .ht-wx-ico svg{ width:100%; height:100%; }
  .ht-wx-track{ position:relative; width:4px; height:52px; border-radius:4px;
    background:color-mix(in srgb, var(--text) 8%, transparent); }
  .ht-wx-track span{ position:absolute; left:0; right:0; border-radius:4px; background:var(--accent); }
  :root:not([data-mode="light"]) .ht-wx-track span{ box-shadow:0 0 10px 0 var(--accent-glow); }
  .ht-wx-hi{ font-family:'IBM Plex Mono', monospace; font-size:11.5px; font-weight:500; }
  .ht-wx-lo{ font-family:'IBM Plex Mono', monospace; font-size:10.5px; color:var(--text-dim); }

  /* One countdown, set big, with the runners-up underneath in a whisper. */
  .ht-count{ cursor:pointer; }
  .ht-count-n{ font-family:'Fraunces', serif; font-size:52px; font-weight:500;
    line-height:.95; letter-spacing:-.035em; }
  .ht-count-u{ font-size:10px; font-weight:600; letter-spacing:.15em; text-transform:uppercase;
    color:var(--text-dim); margin-top:6px; }
  .ht-count-t{ font-size:14.5px; font-weight:600; margin-top:10px; line-height:1.3; }
  .ht-count-rest{ margin-top:14px; padding-top:6px; border-top:1px solid var(--glass-border); }

  .ht-sun{ display:flex; flex-direction:column; gap:12px; }
  .ht-sun-pair{ display:flex; gap:30px; }
  .ht-sun-l{ font-size:10px; font-weight:600; letter-spacing:.15em; text-transform:uppercase; color:var(--text-dim); }
  .ht-sun-v{ font-family:'Fraunces', serif; font-size:22px; font-weight:500; letter-spacing:-.02em; margin-top:4px; display:block; }
  .ht-moon{ display:flex; align-items:center; gap:12px; padding-top:12px; border-top:1px solid var(--glass-border); }
  .ht-moon-g{ font-size:26px; line-height:1; color:var(--text-dim); }
  :root:not([data-mode="light"]) .ht-moon-g{ text-shadow:0 0 16px var(--accent-glow); }

  /* A note and a journal are the same control wearing different labels. Borders
     only on focus: a permanently outlined box on an unframed page is the one
     thing that would put the widget back. */
  .ht-scratch{ width:100%; flex:1; min-height:96px; resize:none; border:none; outline:none;
    background:transparent; color:var(--text); font-family:'Public Sans', sans-serif;
    font-size:13.5px; line-height:1.6; padding:0; }
  .ht-scratch::placeholder{ color:var(--text-faint); }
  .ht-scratch:focus{ box-shadow:none; }
  .ht-journal{ display:flex; flex-direction:column; gap:10px; height:100%; }

  /* CURATED FOR TODAY.
     Picture beside prose above 620px, stacked below it. The picture is given a
     fixed share of the width rather than a fixed size, because the works are
     every shape from a miniature to a five-metre altarpiece and a fixed box
     would letterbox half of them. */
  /* THE FRAME.
     Full bleed to the tile's own edges — a photograph inset inside a card with
     a heading above it is a thumbnail, and a thumbnail is not the thing he
     asked for. The crossfade is two stacked images rather than one whose src
     changes, because swapping a src blanks the element for a frame and the
     flicker is the whole difference between "a frame" and "a slideshow". */
  .ht-frame{ position:absolute; inset:0; border-radius:inherit; overflow:hidden;
    background:color-mix(in srgb, var(--text) 5%, transparent); }
  .ht-frame-img{ position:absolute; inset:0; width:100%; height:100%; object-fit:cover;
    opacity:0; transition:opacity 1400ms var(--ease-out); }
  .ht-frame-img.on{ opacity:1; }
  /* A caption over a photograph is unreadable against a bright sky and
     invisible against a dark one, so it gets its own gradient rather than a
     colour that happens to work on this week's picture. */
  .ht-frame-cap{ position:absolute; left:0; right:0; bottom:0; padding:34px 18px 14px;
    background:linear-gradient(to top, rgba(0,0,0,.72), transparent);
    color:#fff; font-size:12.5px; font-weight:500; pointer-events:none; }
  .ht-frame-when{ opacity:.8; font-size:11px; }
  .ht-frame-say{ margin:0; color:var(--text); max-width:34ch; }
  .ht-frame-empty{ position:absolute; inset:0; display:flex; flex-direction:column;
    align-items:center; justify-content:center; gap:10px; text-align:center; padding:20px; }
  .ht-frame-dots{ position:absolute; top:12px; right:14px; display:flex; gap:5px; }
  .ht-frame-dot{ width:5px; height:5px; border-radius:50%; background:rgba(255,255,255,.45); }
  .ht-frame-dot.on{ background:#fff; }
  .ht-art{ margin:0; display:flex; flex-direction:column; gap:12px; height:100%; }
  .ht-art-frame{ display:flex; gap:18px; align-items:flex-start; flex:1; min-height:0; }
  .ht-art-img{ width:42%; max-width:300px; flex:none; border-radius:12px; object-fit:cover;
    max-height:260px; cursor:pointer;
    background:color-mix(in srgb, var(--text) 6%, transparent); }
  :root:not([data-mode="light"]) .ht-art-img{ box-shadow:0 18px 44px -18px rgba(0,0,0,.75); }
  .ht-art-text{ min-width:0; flex:1; }
  .ht-art-title{ font-family:'Fraunces', serif; font-size:17px; font-weight:600;
    line-height:1.25; letter-spacing:-.01em; }
  /* The museum's own catalogue line — artist, date, medium — never anything
     that was written for it. */
  .ht-art-facts{ font-size:11.5px; color:var(--text-dim); line-height:1.5; margin-top:5px; }
  /* And the paragraph that WAS written for it, set apart so the two are never
     mistaken for each other. */
  .ht-art-note{ font-size:13px; line-height:1.6; margin:11px 0 0; max-width:56ch;
    padding-left:11px; border-left:2px solid var(--glass-border); color:var(--text); }
  /* --text-dim, not --text-faint. Ten-pixel uppercase has failed the 4.5:1
     floor every previous time it was set in the fainter token. */
  .ht-art-credit{ font-size:10px; font-weight:600; letter-spacing:.09em; text-transform:uppercase;
    color:var(--text-dim); margin-top:11px; line-height:1.5; }
  .ht-art-bar{ display:flex; align-items:center; gap:10px; flex:none; }
  .ht-art-n{ font-family:'IBM Plex Mono', monospace; font-size:10.5px; font-weight:600;
    letter-spacing:.08em; color:var(--text-dim); font-variant-numeric:tabular-nums; }
  .ht-art-rule{ flex:1; height:1px; background:var(--glass-border); }
  .ht-art-btn{ width:26px; height:26px; border-radius:50%; border:1px solid var(--glass-border);
    background:none; color:var(--text-dim); cursor:pointer; font-size:15px; line-height:1;
    display:grid; place-items:center; padding:0;
    transition:color var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out); }
  .ht-art-btn:hover{ color:var(--text);
    border-color:color-mix(in srgb, var(--accent) 50%, var(--glass-border)); }
  @media(max-width:620px){
    .ht-art-frame{ flex-direction:column; }
    .ht-art-img{ width:100%; max-width:none; max-height:200px; }
  }

  .ht-breathe{ display:flex; flex-direction:column; align-items:center; justify-content:center;
    gap:14px; height:100%; min-height:120px; cursor:pointer; }
  .ht-breathe-dot{ width:34px; height:34px; border-radius:50%;
    background:color-mix(in srgb, var(--teal) 55%, transparent); }
  :root:not([data-mode="light"]) .ht-breathe-dot{ box-shadow:0 0 30px 6px color-mix(in srgb, var(--teal) 30%, transparent); }
  .ht-breathe.running .ht-breathe-dot{ animation:htBreathe 11s ease-in-out infinite; }
  .ht-breathe-label{ font-size:10.5px; color:var(--text-dim); letter-spacing:.15em; text-transform:uppercase; font-weight:600; }
  @keyframes htBreathe{ 0%,100%{ transform:scale(1); } 36%{ transform:scale(1.9); } 64%{ transform:scale(1.9); } }
  /* Cheap mode turns off everything decorative that loops for ever, and a
     breathing circle is the most literally decorative loop in the app. */
  :root[data-fx="lite"] .ht-breathe.running .ht-breathe-dot{ animation:none; }

  /* Calm is a mode, not a palette. The palette comes with it; so does this. */
  :root[data-calm] .ht-verb{ color:var(--text-dim); }
  :root[data-calm] .home-band::before{ opacity:.28; }
  /* Calm has no lead and no counts, so the band would otherwise hold a hundred
     pixels of nothing between the date and the rule. It closes up instead. */
  :root[data-calm] .home-band{ padding-bottom:20px; }
  :root[data-calm] .home-band-main:empty, :root[data-calm] #homeLead:empty{ display:none; }
  :root[data-calm] .ht-card::before{ display:none; }
  :root[data-calm] .ht-card{ padding-left:0; }
  :root[data-calm] .pulse-ring, :root[data-calm] .spine-badge{ display:none; }

  @media(max-width:860px){
    .home-band{ margin:-16px -14px 22px; padding:24px 14px 22px; }
    .home-band::after{ left:14px; right:14px; }
    .home-stats{ gap:18px 30px; margin-top:24px; }
    .home-stat-n{ font-size:25px; }
  }
  /* One box, everything in it. A dialog rather than a page: the answer to
     "where did I put that" should arrive over whatever you were doing and then
     get out of the way, not be somewhere you navigate to. */
  .omni-card{ max-width:640px; width:100%; padding:0; overflow:hidden; }
  .omni-input{ border:none; border-radius:0; border-bottom:1px solid var(--glass-border);
    font-size:17px; padding:20px 22px; background:transparent; }
  .omni-input:focus{ box-shadow:none; outline:none; }
  .omni-results{ max-height:min(60vh, 520px); overflow-y:auto; padding:6px 22px 20px; scrollbar-width:thin; }
  .omni-results .section-label{ margin-top:16px; }
  .omni-row{ display:flex; align-items:baseline; gap:14px; padding:9px 0; cursor:pointer;
    border-bottom:1px solid var(--glass-border); }
  .omni-row:last-child{ border-bottom:none; }
  .omni-row:hover .omni-t{ color:var(--accent-2, var(--accent)); }
  :root[data-mode="light"] .omni-row:hover .omni-t{ color:var(--accent); }
  .omni-t{ flex:1; min-width:0; font-size:13.5px; font-weight:600; overflow:hidden;
    text-overflow:ellipsis; white-space:nowrap; }
  .omni-s{ font-size:11.5px; color:var(--text-dim); flex:none; max-width:44%; overflow:hidden;
    text-overflow:ellipsis; white-space:nowrap; }

  /* The brief is prose, so it is set as prose — wider measure, looser line, and
     the serif, because it is the only thing on the page written in sentences. */
  .ht-brief{ font-family:'Fraunces', serif; font-size:16px; font-weight:400; line-height:1.6;
    letter-spacing:-.005em; max-width:60ch; }

  /* The digest panel. Tabs across the top because there are up to three of
     them a day and they are different briefs, not a list. */
  .dg-tabs{ display:flex; gap:6px; margin-bottom:10px; flex-wrap:wrap; }
  .dg-tab{ font-family:inherit; font-size:10.5px; font-weight:700; text-transform:uppercase;
    letter-spacing:.08em; padding:5px 11px; border-radius:999px; cursor:pointer;
    border:1px solid var(--glass-border); background:rgba(120,120,140,.10); color:var(--text-dim); }
  .dg-tab:hover{ color:var(--text); }
  .dg-tab.on{ color:var(--text); border-color:var(--glass-border-hi); background:rgba(120,120,140,.20); }
  .dg-tab:focus-visible{ outline:2px solid var(--blue); outline-offset:2px; }
  .dg-lines{ display:flex; flex-direction:column; gap:2px; }
  .dg-line{ font-size:13px; line-height:1.55; color:var(--text); padding:6px 8px; border-radius:9px; }
  .dg-line.is-go{ cursor:pointer; }
  .dg-line.is-go:hover{ background:rgba(120,120,140,.12); }

  /* ==========================================================
     THE WALL — one column, whatever it came from.
     ----------------------------------------------------------
     A YouTube video, a Reddit thread, a Bluesky post and a newspaper article
     are five fields each — who, what, when, a link, maybe a picture — so they
     get one card, not five. Anything else would be a design that has to be
     redrawn every time a network is added.
     ========================================================== */
  /* THE RIVER.
     It used to be an 820px column of rows with a 104x72 thumbnail — a list of
     links on a page twice as wide as itself, which is what "I don't like the
     layout" was about. Now: cards in a responsive grid, media at 16:9 and big
     enough to be worth looking at, and the whole thing uses the width it has.

     Masonry via CSS columns rather than grid, because feed items are wildly
     different heights — a Bluesky post is two lines and a YouTube video is a
     picture plus a paragraph — and a grid row is as tall as its tallest cell,
     which leaves holes under everything else. */
  /* ---- MEDIA: WATCH · LISTEN · READ -------------------------------- */
  .media-modes{ display:flex; gap:6px; margin-bottom:16px; flex-wrap:wrap; }
  .media-mode{ padding:7px 15px; border-radius:999px; font-family:inherit; font-size:12.5px;
    font-weight:600; cursor:pointer; color:var(--text-dim); background:rgba(120,120,140,.10);
    border:1px solid transparent; white-space:nowrap; }
  .media-mode:hover{ color:var(--text); background:rgba(120,120,140,.18); }
  .media-mode:focus-visible{ outline:2px solid var(--blue); outline-offset:2px; }
  .media-mode.active{ color:var(--text); background:color-mix(in srgb, var(--accent) 18%, transparent);
    border-color:color-mix(in srgb, var(--accent) 34%, transparent); }

  .media-listen{ max-width:1240px; margin-bottom:26px; }
  .pod-shelf{ display:grid; gap:14px; grid-template-columns:repeat(auto-fill, minmax(148px, 1fr)); }
  .pod-card{ display:flex; flex-direction:column; gap:7px; padding:0; border:0; background:none;
    font-family:inherit; cursor:pointer; text-align:left; min-width:0; }
  .pod-card:focus-visible{ outline:2px solid var(--blue); outline-offset:3px; border-radius:12px; }
  .pod-art{ width:100%; aspect-ratio:1; border-radius:12px; object-fit:cover; display:block;
    background:rgba(120,120,140,.14); border:1px solid var(--glass-border); }
  .pod-card:hover .pod-art{ border-color:var(--glass-border-hi); }
  .pod-name{ font-size:12.5px; font-weight:650; color:var(--text); line-height:1.35;
    display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden; }
  .pod-by{ font-size:11px; color:var(--text-dim); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }

  .pod-head{ display:flex; gap:16px; align-items:flex-start; margin-bottom:18px; }
  .pod-head .pod-art{ width:132px; flex:none; }
  .pod-head-main{ min-width:0; flex:1; display:flex; flex-direction:column; gap:6px; }
  .pod-title{ font-size:21px; font-weight:700; color:var(--text); line-height:1.25; }
  .pod-desc{ font-size:12.5px; color:var(--text-dim); line-height:1.55; max-width:70ch;
    display:-webkit-box; -webkit-line-clamp:4; -webkit-box-orient:vertical; overflow:hidden; }

  .ep-row{ display:flex; gap:12px; align-items:center; padding:11px 13px; border-radius:12px;
    border:1px solid var(--glass-border); background:var(--glass-bg); margin-bottom:8px;
    font-family:inherit; text-align:left; width:100%; cursor:pointer; }
  .ep-row:hover{ border-color:var(--glass-border-hi); }
  .ep-row:focus-visible{ outline:2px solid var(--blue); outline-offset:2px; }
  .ep-row.playing{ border-color:color-mix(in srgb, var(--green) 46%, transparent); }
  .ep-play{ flex:none; width:32px; height:32px; border-radius:50%; display:flex; align-items:center;
    justify-content:center; background:rgba(120,120,140,.16); color:var(--text); }
  .ep-play svg{ width:13px; height:13px; }
  .ep-main{ min-width:0; flex:1; display:flex; flex-direction:column; gap:3px; }
  .ep-title{ font-size:13px; font-weight:650; color:var(--text); }
  .ep-sub{ font-size:11.5px; color:var(--text-dim); }
  .ep-len{ flex:none; font-family:'IBM Plex Mono',monospace; font-size:11px; color:var(--text-dim);
    font-variant-numeric:tabular-nums; }
  /* How far in you already are. A number would be noise; the bar is the whole
     message, and it is only drawn once there is something to say. */
  .ep-progress{ height:3px; border-radius:2px; background:rgba(120,120,140,.20); margin-top:5px; }
  .ep-progress i{ display:block; height:100%; border-radius:2px; background:var(--green); }
  .ep-done{ color:var(--green); font-weight:700; }

  /* ---- THE PLAYER --------------------------------------------------------
     Fixed to the bottom of the app rather than living on the Media page,
     because the whole point is that it survives walking to another page. An
     episode that stops when you open your mail is not a player, it is a
     preview. */
  .player-bar{ position:fixed; left:0; right:0; bottom:0; z-index:70; display:none;
    align-items:center; gap:14px; padding:10px 16px calc(10px + env(safe-area-inset-bottom));
    background:color-mix(in srgb, var(--bg-deep) 92%, transparent);
    backdrop-filter:blur(18px); -webkit-backdrop-filter:blur(18px);
    border-top:1px solid var(--glass-border-hi); }
  .player-bar.open{ display:flex; }
  .pl-art{ width:44px; height:44px; border-radius:9px; flex:none; object-fit:cover;
    background:rgba(120,120,140,.16); }
  .pl-main{ min-width:0; flex:1; display:flex; flex-direction:column; gap:3px; }
  .pl-title{ font-size:12.5px; font-weight:650; color:var(--text); white-space:nowrap;
    overflow:hidden; text-overflow:ellipsis; }
  .pl-show{ font-size:11px; color:var(--text-dim); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
  .pl-seek{ display:flex; align-items:center; gap:9px; }
  .pl-time{ font-family:'IBM Plex Mono',monospace; font-size:10px; color:var(--text-dim);
    font-variant-numeric:tabular-nums; flex:none; }
  .pl-range{ flex:1; min-width:80px; height:4px; accent-color:var(--green); cursor:pointer; }
  .pl-btns{ display:flex; align-items:center; gap:6px; flex:none; }
  .pl-btn{ width:34px; height:34px; border-radius:50%; border:1px solid var(--glass-border);
    background:rgba(120,120,140,.10); color:var(--text); cursor:pointer; display:flex;
    align-items:center; justify-content:center; font-family:inherit; font-size:10.5px; font-weight:700; }
  .pl-btn:hover{ background:rgba(120,120,140,.20); }
  .pl-btn:focus-visible{ outline:2px solid var(--blue); outline-offset:2px; }
  .pl-btn.main{ width:40px; height:40px; background:var(--green); border-color:transparent; color:var(--ink, #06101F); }
  .pl-btn svg{ width:14px; height:14px; }
  .pl-rate{ min-width:42px; border-radius:999px; width:auto; padding:0 10px; }
  /* The bar covers the bottom nav on a phone, so the nav moves up out of it. */
  body.playing .bottom-nav{ bottom:66px; }
  /* The bar is fixed, so the page has to make room or its last row hides
     behind it — which on the Media page is an episode you cannot press. */
  body.playing .main{ padding-bottom:96px; }
  @media(max-width:780px){
    .player-bar{ bottom:0; padding-bottom:calc(10px + env(safe-area-inset-bottom)); }
    .pl-seek{ display:none; }
  }

  .feed-list{ column-width:330px; column-gap:16px; max-width:1180px; }
  /* A sentence is not a card. Without this the empty-state paragraph is poured
   * into the masonry like everything else and reads down one column and up the
   * next — "Paste a YouTube channel, a subreddit… / so or not." Seen in a
   * screenshot, not caught by an assertion. */
  .feed-list > .ht-empty{ column-span:all; }
  .feed-item{ break-inside:avoid; display:block; margin:0 0 16px; padding:0;
    cursor:pointer; border:1px solid var(--glass-border); border-radius:14px;
    overflow:hidden; background:var(--glass-bg); position:relative;
    transition:transform var(--dur-base) var(--ease-out), box-shadow var(--dur-base) var(--ease-out); }
  .feed-item:hover{ transform:translateY(-2px); }
  :root:not([data-mode="light"]) .feed-item:hover{
    box-shadow:0 0 0 1px var(--glass-border-hi), 0 16px 40px -16px rgba(0,0,0,.6); }
  :root[data-mode="light"] .feed-item:hover{ box-shadow:0 6px 22px -10px rgba(20,20,40,.24); }
  .feed-img{ display:block; width:100%; aspect-ratio:16/9; object-fit:cover;
    background:color-mix(in srgb, var(--text) 6%, transparent); }
  /* The play badge only appears on something that will actually play here.
     A triangle over a blog post is a promise the card cannot keep. */
  .feed-play{ position:absolute; top:calc(28.125% - 17px); left:50%; transform:translateX(-50%);
    width:46px; height:46px; border-radius:50%; display:flex; align-items:center;
    justify-content:center; background:rgba(0,0,0,.55); color:#fff; pointer-events:none; }
  .feed-play svg{ width:18px; height:18px; margin-left:2px; }
  .feed-body{ min-width:0; padding:13px 15px 15px; }
  .feed-src{ font-size:10.5px; font-weight:700; letter-spacing:.1em; text-transform:uppercase;
    color:var(--text-dim); display:flex; gap:9px; align-items:baseline; flex-wrap:wrap; }
  .feed-kind, .feed-when{ font-weight:600; letter-spacing:.06em; color:var(--text-faint); }
  .feed-lead{ font-size:14.5px; font-weight:600; line-height:1.38; margin-top:6px; }
  .feed-item:hover .feed-lead{ color:var(--accent-2, var(--accent)); }
  :root[data-mode="light"] .feed-item:hover .feed-lead{ color:var(--accent); }
  /* Unfollow, one press, from the thing you are tired of — rather than from a
     dialog three clicks away that lists twenty sources by handle. */
  .feed-drop{ position:absolute; top:9px; right:9px; width:24px; height:24px; border-radius:50%;
    display:flex; align-items:center; justify-content:center; font-size:15px; line-height:1;
    background:rgba(0,0,0,.5); color:#fff; opacity:0; cursor:pointer;
    transition:opacity var(--dur-fast) var(--ease-out); }
  .feed-item:hover .feed-drop{ opacity:1; }
  .feed-drop:hover{ background:rgba(0,0,0,.75); }
  :root[data-mode="light"] .feed-drop{ background:rgba(255,255,255,.86); color:var(--text); }
  .feed-warn{ color:var(--orange-text, var(--orange)); font-weight:700; }
  @media(max-width:560px){ .feed-list{ column-width:auto; columns:1; } }

  /* An embed is somebody else's page running inside this one, so it is given a
     frame of its own rather than dropped into the column of cards. The border
     and the label are there to make that obvious: what is inside came from X or
     Instagram and is styled by them, and it should not be mistaken for Lucid. */
  /* One line until there is more than one line in it. */
  textarea.feed-add{ flex:1; min-width:0; min-height:0; height:43px; max-height:220px;
    resize:none; overflow-y:auto; line-height:1.4; margin-bottom:0;
    font-family:'Public Sans'; white-space:pre-wrap; }

  /* People, not posts. These cards are wider and quieter than the feed rows
     below them because they are about somebody rather than about something to
     read — and the ones carrying a fact Lucid can keep are marked. */
  /* The story box. A quote from the first email they ever sent is the line
     that makes somebody remember the moment, so it is set as a quote rather
     than as another row of small grey text. */
  .know-line{ font-size:12.5px; line-height:1.55; margin:0 0 7px; color:var(--text); }
  .know-line:last-child{ margin-bottom:0; }
  .know-dim{ color:var(--text-dim); }
  .know-quote{ font-size:12px; line-height:1.5; margin:8px 0; padding-left:10px;
    border-left:2px solid var(--glass-border-hi); color:var(--text-dim); font-style:italic; }
  .know-owed{ font-weight:650; }
  .sig-wrap{ max-width:1180px; margin-bottom:26px; }
  .sig-grid{ display:grid; gap:8px; grid-template-columns:repeat(auto-fill, minmax(248px, 1fr)); }
  .sig{ display:flex; gap:11px; align-items:flex-start; padding:11px 13px; border-radius:12px;
    border:1px solid var(--glass-border); background:var(--glass-bg); font-size:13px; }
  .sig.learn{ border-color:color-mix(in srgb, var(--green) 44%, transparent); }
  .sig-more{ display:flex; align-items:center; gap:6px; margin-top:8px; padding:6px 12px;
    border-radius:999px; border:1px solid var(--glass-border); background:var(--glass-bg);
    font-family:inherit; font-size:12px; font-weight:600; color:var(--text-dim); cursor:pointer; }
  .sig-more:hover{ color:var(--text); border-color:var(--glass-border-hi); }
  .sig-more[aria-expanded="true"] .sig-more-chev{ transform:rotate(180deg); }
  .sig-more-chev{ transition:transform .16s ease; }
  .sig-av{ flex:none; width:30px; height:30px; border-radius:50%; display:flex; align-items:center;
    justify-content:center; font-size:11px; font-weight:700; letter-spacing:.02em;
    background:color-mix(in srgb, var(--text) 10%, transparent); color:var(--text); }
  .sig-main{ flex:1; min-width:0; }
  .sig-head{ font-weight:600; line-height:1.35; }
  .sig-why{ font-size:11px; color:var(--text-dim); margin-top:2px; }
  /* Not the green accent. At 11px bold it measured 4.45:1 on cream paper —
     under the floor. The emphasis comes from the ink being full strength next
     to a dimmed label, which is a stronger signal than a colour anyway. */
  .sig-count{ color:var(--text); }
  .sig-act{ font-size:11.5px; font-weight:650; color:var(--green-text, var(--green)); cursor:pointer;
    display:inline-block; margin-top:5px; }
  .sig-act:hover{ text-decoration:underline; }
  .sig-x{ flex:none; cursor:pointer; color:var(--text-faint); font-size:14px; line-height:1; }
  .sig-x:hover{ color:var(--text); }
  /* NARROW FIRST. One column, embeds above the river — which is the right
     answer when there is only one column to put things in. The rail below is
     the wide-screen case, not the default. */
  .feed-two{ display:block; max-width:1180px; }
  .feed-embeds{ display:flex; flex-direction:column; gap:22px; margin-bottom:26px; }
  .feed-embeds:empty{ display:none; margin:0; }

  /* THE RAIL. Past 1100px there is room for the embeds to sit beside the feed
     instead of on top of it, which is the whole fix: following one Facebook
     page used to push every card 640 pixels down the screen and leave the
     right-hand half of the page empty next to it.

     360px is not arbitrary — it is a hair over Facebook's own minimum for the
     page plugin (180–500), wide enough that a post's picture is worth looking
     at, and narrow enough to leave two full masonry columns beside it. */
  @media (min-width: 1100px){
    .feed-two{ display:flex; align-items:flex-start; gap:20px; max-width:1560px; }
    /* Not sticky. Two embeds are already taller than the window, so pinning
       the rail would pin something that cannot fit and the bottom of it would
       become unreachable. */
    .feed-two > .feed-embeds{ flex:none; width:360px; margin-bottom:0; }
    .feed-two > .feed-list{ flex:1; min-width:0; max-width:none; }
    /* An empty rail must not leave a 380px hole where it would have been. */
    .feed-two > .feed-embeds:empty + .feed-list{ max-width:1180px; }
  }

  .feed-embed{ border:1px solid var(--glass-border); border-radius:14px; overflow:hidden;
    background:var(--glass-bg, transparent); }
  .feed-embed .feed-src{ padding:12px 14px; border-bottom:1px solid var(--glass-border); }
  .feed-embed .x{ margin-left:auto; cursor:pointer; color:var(--text-faint); font-size:15px;
    line-height:1; padding:0 2px; flex:none; }
  .feed-embed .x:hover{ color:var(--text); }
  /* Starts at a readable height and is resized to fit by the frame itself — see
     the note by renderEmbeds(). If the frame never reports back, this height is
     what he is left with, so it is a usable one rather than a placeholder. */
  .feed-embed-frame{ display:block; width:100%; height:520px; border:0; }

  /* Sports. A win and a loss have to be tellable apart at a glance and without
     colour alone, so the letter carries it and the tint only reinforces. */
  .sport-row{ gap:10px; }
  .sport-badge{ width:18px; flex:none; font-family:'IBM Plex Mono', monospace; font-size:11px;
    font-weight:700; color:var(--text-dim); }
  .sport-badge.won{ color:var(--green-text, var(--green)); }
  .sport-badge.lost{ color:var(--text-faint); }
  :root{ --green-text:var(--green-2, var(--green)); }
  :root[data-mode="light"]{ --green-text:var(--green); }

  .ht-track{ display:flex; gap:14px; align-items:center; cursor:pointer; }
  .ht-track-art{ width:62px; height:62px; border-radius:9px; object-fit:cover; flex:none; }
  :root:not([data-mode="light"]) .ht-track-art{ box-shadow:0 8px 26px -10px rgba(0,0,0,.8); }
  .ht-track-n{ font-size:14.5px; font-weight:600; line-height:1.3; }
  .ht-track-s{ font-size:10px; font-weight:600; letter-spacing:.14em; text-transform:uppercase;
    color:var(--text-dim); margin-top:6px; }

  /* ==========================================================
     THE PLAYER.
     ----------------------------------------------------------
     Transport controls are muscle memory, so the shapes are the ones every
     other player uses and the middle one is the biggest. Nothing here is
     labelled with a word; a triangle has meant play for fifty years.
     ========================================================== */
  .mp-bar{ display:flex; align-items:center; gap:6px; margin-top:12px; }
  .mp-btn{ width:32px; height:32px; border-radius:50%; border:1px solid transparent;
    background:none; color:var(--text-dim); cursor:pointer; display:grid; place-items:center;
    transition:color var(--dur-fast) var(--ease-out), background var(--dur-fast) var(--ease-out); }
  .mp-btn:hover{ color:var(--text); background:color-mix(in srgb, var(--text) 8%, transparent); }
  .mp-btn:disabled{ opacity:.35; cursor:default; }
  .mp-btn:disabled:hover{ background:none; color:var(--text-dim); }
  .mp-btn svg{ width:15px; height:15px; fill:currentColor; }
  .mp-btn.big{ width:40px; height:40px; color:var(--text);
    border-color:var(--glass-border); }
  .mp-btn.big svg{ width:17px; height:17px; }
  :root:not([data-mode="light"]) .mp-btn.big:hover{ box-shadow:0 0 16px 0 var(--accent-glow);
    border-color:color-mix(in srgb, var(--accent) 45%, var(--glass-border)); }
  .mp-btn.on{ color:var(--accent-2, var(--accent)); }
  :root[data-mode="light"] .mp-btn.on{ color:var(--accent); }
  .mp-spacer{ flex:1; }

  /* The scrubber is a real input so it can be dragged, arrowed and tabbed to.
     A div with a mousedown handler is none of those things. */
  .mp-seek{ -webkit-appearance:none; appearance:none; width:100%; height:14px; background:none;
    cursor:pointer; display:block; margin:10px 0 2px; }
  .mp-seek::-webkit-slider-runnable-track{ height:3px; border-radius:2px;
    background:color-mix(in srgb, var(--text) 16%, transparent); }
  .mp-seek::-moz-range-track{ height:3px; border-radius:2px;
    background:color-mix(in srgb, var(--text) 16%, transparent); }
  .mp-seek::-webkit-slider-thumb{ -webkit-appearance:none; width:11px; height:11px; border-radius:50%;
    background:var(--text); margin-top:-4px; border:none; }
  .mp-seek::-moz-range-thumb{ width:11px; height:11px; border-radius:50%; background:var(--text); border:none; }
  .mp-seek:focus-visible{ outline:2px solid var(--accent); outline-offset:3px; }
  .mp-times{ display:flex; justify-content:space-between; font-family:'IBM Plex Mono', monospace;
    font-size:10px; color:var(--text-dim); }

  /* The video window. Sized to the video rather than the other way round: a
     16:9 box that grows with the panel, so the player is never letterboxed
     inside a box that is itself letterboxed. */
  .compose-modal.watch-card{ max-width:min(1080px, 94vw); width:100%; padding:0; overflow:hidden; }
  .watch-head{ display:flex; align-items:center; gap:14px; padding:13px 18px;
    border-bottom:1px solid var(--glass-border); }
  .watch-t{ font-size:13.5px; font-weight:600; flex:1; min-width:0;
    overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
  .watch-out{ font-size:11px; font-weight:700; letter-spacing:.08em; text-transform:uppercase;
    color:var(--text-dim); text-decoration:none; flex:none; }
  .watch-out:hover{ color:var(--text); }
  .watch-x{ cursor:pointer; color:var(--text-dim); font-size:19px; line-height:1; flex:none; padding:0 2px; }
  .watch-x:hover{ color:var(--text); }
  .watch-frame{ position:relative; width:100%; aspect-ratio:16 / 9; background:#000; }
  .watch-frame iframe{ position:absolute; inset:0; width:100%; height:100%; border:0; display:block; }

  /* WIDE RATHER THAN TALL.
     At 720px the panel was a column: track, then scrubber, then buttons, then
     speakers, then results — five stacked rows and a page that ran off the
     bottom of the screen. There is plenty of width going spare, so above 820px
     the controls sit BESIDE what is playing rather than under it, and the
     results run in two columns. Below that it stacks, which is right on a
     phone. */
  /* Two class names, so this beats .compose-modal's own 720px whichever of the
     two rules the stylesheet reaches first. */
  .compose-modal.music-card{ max-width:940px; width:100%; padding:0; overflow:hidden; }
  .music-now{ padding:20px 22px 14px; border-bottom:1px solid var(--glass-border); }
  .music-head{ display:flex; gap:16px; align-items:center; min-width:0; }
  .music-seek{ min-width:0; }

  @media(min-width:820px){
    .music-now{ display:grid; grid-template-columns:minmax(0,1fr) auto; gap:0 28px;
      grid-template-areas:"head bar" "seek seek" "dev dev"; align-items:center; }
    .music-now .music-head{ grid-area:head; }
    .music-now .music-seek{ grid-area:seek; }
    .music-now .mp-bar{ grid-area:bar; margin-top:0; }
    .music-now .music-devices{ grid-area:dev; }
    /* The spacer only exists to push volume to the far end of a full-width row;
       in the two-column layout the row is already as wide as its contents. */
    .music-now .mp-spacer{ display:none; }
    .music-now .mp-bar{ gap:4px; }
    .music-vol{ margin-left:14px; }
    #musicResults{ display:grid; grid-template-columns:1fr 1fr; column-gap:24px; align-content:start; }
    #musicResults .section-label{ grid-column:1 / -1; }
    #musicResults .ht-empty{ grid-column:1 / -1; }
  }
  /* Shorter, because it is now twice as wide. */
  #musicResults{ max-height:min(44vh, 360px); }
  .music-art{ width:76px; height:76px; border-radius:11px; object-fit:cover; flex:none;
    background:color-mix(in srgb, var(--text) 6%, transparent); }
  :root:not([data-mode="light"]) .music-art{ box-shadow:0 10px 30px -12px rgba(0,0,0,.85); }
  .music-t{ font-size:16px; font-weight:600; line-height:1.3; }
  .music-s{ font-size:13px; color:var(--text-dim); margin-top:3px; }
  .music-on{ font-size:10px; font-weight:700; letter-spacing:.14em; text-transform:uppercase;
    color:var(--text-dim); margin-top:8px; }
  .music-vol{ display:flex; align-items:center; gap:8px; min-width:0; }
  /* Sized explicitly: an inline SVG with no dimensions falls back to 300×150,
     which shoves the slider off the end of the row. */
  .music-vol > svg{ width:15px; height:15px; flex:none; fill:var(--text-dim); }
  .music-vol .mp-seek{ width:88px; margin:0; }
  .music-devices{ display:flex; gap:6px; flex-wrap:wrap; margin-top:14px; }
  .music-dev{ font-size:11.5px; font-weight:600; padding:5px 11px; border-radius:999px; cursor:pointer;
    border:1px solid var(--glass-border); color:var(--text-dim); background:none;
    transition:all var(--dur-fast) var(--ease-out); }
  .music-dev:hover{ color:var(--text); }
  .music-dev.active{ color:var(--text); border-color:color-mix(in srgb, var(--accent) 55%, var(--glass-border)); }
  :root:not([data-mode="light"]) .music-dev.active{ box-shadow:0 0 14px 0 var(--accent-glow); }

  .music-row{ display:flex; align-items:center; gap:12px; padding:8px 0; cursor:pointer;
    border-bottom:1px solid var(--glass-border); }
  .music-row:last-child{ border-bottom:none; }
  .music-row-art{ width:40px; height:40px; border-radius:7px; object-fit:cover; flex:none;
    background:color-mix(in srgb, var(--text) 6%, transparent); }
  .music-row-art.round{ border-radius:50%; }
  .music-row-b{ min-width:0; flex:1; }
  .music-row-t{ font-size:13.5px; font-weight:600; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
  .music-row:hover .music-row-t{ color:var(--accent-2, var(--accent)); }
  :root[data-mode="light"] .music-row:hover .music-row-t{ color:var(--accent); }
  .music-row-s{ font-size:11.5px; color:var(--text-dim); overflow:hidden;
    text-overflow:ellipsis; white-space:nowrap; }
  .music-queue{ font-size:11px; font-weight:700; letter-spacing:.08em; text-transform:uppercase;
    color:var(--text-dim); flex:none; opacity:0; padding:4px 8px; border-radius:7px; }
  .music-row:hover .music-queue{ opacity:1; }
  .music-queue:hover{ color:var(--text); background:color-mix(in srgb, var(--text) 8%, transparent); }
  @media(max-width:560px){ .music-queue{ opacity:1; } }

  /* ==========================================================
     THE VAULT — a filing cabinet nobody has to open.
     ----------------------------------------------------------
     Two shapes only: the renewal strip at the top, which is the reason to come
     here at all, and cards below it. No table. A table of a house, three cars
     and forty subscriptions is a spreadsheet, and a spreadsheet is a thing you
     export rather than a thing you read.
     ========================================================== */
  .vault-head{ display:flex; align-items:flex-start; justify-content:space-between;
    gap:14px; flex-wrap:wrap; margin-bottom:18px; }
  .vault-cats{ display:flex; gap:4px; flex-wrap:wrap; margin-bottom:22px; }

  /* THE LANDING: large cards, one per kind.
     A card face carries a count and the next date and NOTHING ELSE — the
     contents are one click further in, on their own page. The whole complaint
     about the old screen was that it was a flat list; a wall of tabs over the
     same list would have been the same screen with more chrome. */
  .vault-cards{ display:grid; gap:14px; margin-bottom:8px;
    grid-template-columns:repeat(auto-fill, minmax(216px, 1fr)); }
  .vault-card{ position:relative; overflow:hidden; padding:20px 20px 18px; border-radius:18px;
    cursor:pointer; min-height:132px; display:flex; flex-direction:column;
    justify-content:space-between; border:1px solid var(--glass-border);
    background:var(--glass-bg);
    transition:transform var(--dur-base) var(--ease-out), box-shadow var(--dur-base) var(--ease-out); }
  .vault-card:hover{ transform:translateY(-2px); }
  :root:not([data-mode="light"]) .vault-card{
    background-image:linear-gradient(140deg, color-mix(in srgb, var(--vc-glow, transparent) 40%, transparent) 0%, transparent 58%); }
  :root:not([data-mode="light"]) .vault-card:hover{
    box-shadow:0 0 0 1px color-mix(in srgb, var(--vc, var(--accent)) 34%, transparent),
               0 14px 40px -14px var(--vc-glow, transparent); }
  :root[data-mode="light"] .vault-card:hover{ box-shadow:0 6px 20px -8px rgba(20,20,40,.22); }
  .vault-card-icon{ width:24px; height:24px; color:var(--vc, var(--accent)); margin-bottom:auto; }
  .vault-card-name{ font-size:15px; font-weight:650; margin-top:14px; }
  .vault-card-n{ font-size:12px; color:var(--text-dim); margin-top:3px; }
  .vault-card-due{ font-size:11.5px; font-weight:600; color:var(--vc-text, var(--text-dim)); margin-top:6px; }
  .vault-card-locked{ position:absolute; top:16px; right:16px; width:15px; height:15px; color:var(--text-dim); }

  .vault-lockbox{ margin-top:34px; padding-top:20px; border-top:1px solid var(--glass-border); }
  .vault-back{ font-size:12px; font-weight:600; color:var(--text-dim); cursor:pointer; margin-bottom:6px; }
  .vault-back:hover{ color:var(--text); }
  .vault-lockstate{ font-size:11.5px; color:var(--text-dim); }

  /* The gate. Not an overlay with the page behind it — the page is not drawn
     at all while this is up. */
  .vault-gate{ display:flex; align-items:center; justify-content:center; padding:64px 20px; }
  .vault-gate-inner{ max-width:380px; text-align:center; }
  .vault-gate-icon{ width:44px; height:44px; color:var(--text-dim); margin-bottom:14px; }
  .vault-gate-title{ font-family:'Fraunces', serif; font-size:26px; font-weight:600; margin-bottom:6px; }

  /* A hidden item still occupies its row, because a vault that silently omits
     things is indistinguishable from one that lost them. */
  .vault-hidden-val{ font-family:'IBM Plex Mono', monospace; letter-spacing:.12em;
    color:var(--text-faint); }
  .vault-cat{ font-size:12px; font-weight:600; padding:6px 13px; border-radius:20px;
    cursor:pointer; color:var(--text-dim); white-space:nowrap; display:inline-flex;
    align-items:center; gap:7px;
    transition:color var(--dur-fast) var(--ease-out), background var(--dur-fast) var(--ease-out); }
  .vault-cat:hover{ color:var(--text); background:color-mix(in srgb, var(--text) 8%, transparent); }
  .vault-cat.active{ color:var(--text); background:color-mix(in srgb, var(--text) 11%, transparent); }
  :root:not([data-mode="light"]) .vault-cat.active{
    box-shadow:0 0 0 1px color-mix(in srgb, var(--accent) 38%, transparent), 0 0 18px -3px var(--accent-glow); }
  .vault-cat-n{ font-family:'IBM Plex Mono', monospace; font-size:10.5px; color:var(--text-dim); }

  .vault-renewals{ margin-bottom:26px; }
  .vault-renew{ display:flex; align-items:baseline; justify-content:space-between; gap:16px;
    padding:11px 0 11px 15px; position:relative; cursor:pointer; }
  .vault-renew + .vault-renew{ border-top:1px solid var(--glass-border); }
  /* Same margin rule as the Home cards: the mark is always there, only its
     brightness changes. See the note by .ht-card. */
  .vault-renew::before{ content:''; position:absolute; left:0; top:14px; bottom:14px; width:2px;
    border-radius:2px; z-index:0; background:color-mix(in srgb, var(--text) 13%, transparent); }
  .vault-renew.hot::before{ background:var(--accent); }
  :root:not([data-mode="light"]) .vault-renew.hot::before{ box-shadow:0 0 12px 1px var(--accent-glow); }
  .vault-renew-n{ font-size:14px; font-weight:600; position:relative; z-index:1; }
  .vault-renew-w{ font-size:11.5px; color:var(--text-dim); flex:none; position:relative; z-index:1; }
  :root[data-mode="light"] .vault-renew.hot .vault-renew-n{ font-weight:700; }

  .vault-grid{ display:grid; grid-template-columns:repeat(auto-fill, minmax(260px, 1fr));
    gap:14px; margin-bottom:26px; }

  /* THE HOUSE PLAN.
     Translucent tiles on a grid, tilted a few degrees so it reads as a plan
     rather than as another list of cards. Deliberately restrained: the tilt is
     one transform on the container, so every tile stays a rectangle and the
     text inside it stays flat and legible. A real perspective transform would
     put the room names on a slant, which looks like a demo and reads like
     nothing. Reduced motion and narrow screens drop the tilt entirely. */
  .house-plan{ display:grid; grid-template-columns:repeat(auto-fill, minmax(150px, 1fr));
    gap:12px; margin:6px 0 10px; transform:rotateX(6deg); transform-origin:top center; }
  .house-tile{ position:relative; min-height:92px; padding:13px 14px; border-radius:12px; cursor:pointer;
    border:1px solid var(--glass-border);
    background:color-mix(in srgb, var(--accent) 7%, transparent);
    box-shadow:inset 0 1px 0 color-mix(in srgb, var(--text) 8%, transparent);
    transition:background var(--t-fast, .15s) ease, transform var(--t-fast, .15s) ease; }
  .house-tile:hover{ background:color-mix(in srgb, var(--accent) 13%, transparent); transform:translateY(-2px); }
  .house-tile-name{ font-size:14px; font-weight:600; color:var(--text); }
  .house-tile-sub{ font-size:11.5px; color:var(--text-dim); margin-top:3px; }
  .house-tile-flag{ font-size:11px; color:var(--cat-orange-text, var(--text-dim)); margin-top:6px; }
  .house-filters{ display:flex; flex-wrap:wrap; gap:8px; margin:6px 0 20px; }
  .house-filter{ font-size:12px; color:var(--text-dim); padding:5px 10px; border-radius:8px;
    border:1px solid var(--glass-border); background:color-mix(in srgb, var(--text) 3%, transparent); }
  .house-filter b{ color:var(--text); font-weight:600; }
  @media (max-width:720px){ .house-plan{ transform:none; } }
  @media (prefers-reduced-motion: reduce){
    .house-plan{ transform:none; }
    .house-tile:hover{ transform:none; }
  }
  .vault-item{ padding:16px 17px; border-radius:14px; cursor:pointer;
    border:1px solid var(--glass-border);
    background:color-mix(in srgb, var(--text) 3%, transparent);
    transition:border-color var(--dur-fast) var(--ease-out), box-shadow var(--dur-fast) var(--ease-out); }
  .vault-item:hover{ border-color:var(--glass-border-hi); }
  :root:not([data-mode="light"]) .vault-item:hover{ box-shadow:0 0 22px -6px var(--accent-glow); }
  .vault-item-name{ font-size:14.5px; font-weight:600; letter-spacing:-.005em; }
  /* A definition list, because that is what these are. Two columns so the
     labels line up down the card and the eye can run past them. */
  .vault-fields{ display:grid; grid-template-columns:auto 1fr; gap:3px 12px; margin:12px 0 0;
    font-size:11.5px; }
  .vault-fields dt{ color:var(--text-dim); white-space:nowrap; }
  .vault-fields dd{ margin:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
  .vault-item-foot{ display:flex; align-items:baseline; justify-content:space-between;
    gap:10px; margin-top:12px; font-size:11.5px; color:var(--text-dim); }
  .vault-cost{ font-family:'IBM Plex Mono', monospace; }
  .vault-due.hot{ color:var(--text); font-weight:600; }
  /* Gifts. A row, not a card: the whole value is being able to run your eye
     down "what did I give" without opening anything. */
  /* People Lucid found in the mail. One line above the contact list, never a
     block of suggestions inside it — the version that lived in the list was
     removed for exactly that reason. */
  /* Picking several people at once. The box is always there rather than
     revealed on hover: a control you have to discover by waving the mouse at a
     row is a control nobody uses. */
  /* In the corner of the card, inside .cc-head — which is already a
     space-between row — rather than as a child of .contact-row, which is a
     column and would have given it a full-width band of its own. */
  .contact-pick{ flex:none; width:17px; height:17px; accent-color:var(--blue);
    cursor:pointer; margin:2px 0 0; }
  .contact-bulk{ display:flex; align-items:center; gap:10px; flex-wrap:wrap;
    margin-bottom:14px; padding:10px 14px; border-radius:11px;
    border:1px solid var(--glass-border-hi); background:var(--glass-bg); font-size:13px; }
  .contact-bulk-n{ font-weight:600; }
  .contact-tagbar{ display:flex; align-items:center; gap:7px; flex-wrap:wrap; margin-bottom:12px; }
  .tag-chip{ font-size:11.5px; font-weight:600; padding:4px 11px; border-radius:999px;
    border:1px solid var(--glass-border-hi); cursor:pointer; color:var(--text-dim); }
  .tag-chip:hover{ color:var(--text); }
  .tag-chip.on{ color:var(--ink); background:linear-gradient(135deg,var(--blue),var(--blue-2)); border-color:transparent; }
  .contact-tags{ display:inline-flex; gap:5px; flex-wrap:wrap; margin-left:6px; vertical-align:middle; }
  /* The strength band on a contact row. The WORD is always there — colour on
     its own would make the order unreadable to anyone who cannot separate the
     four, and unsearchable to everyone. */
  .cs-chip{ font-size:9.5px; font-weight:700; text-transform:uppercase; letter-spacing:.07em;
    padding:2px 7px; border-radius:999px; margin-left:7px; vertical-align:1px; }
  .cs-strong{ color:var(--green,#3E9E70); background:color-mix(in srgb, var(--green,#3E9E70) 16%, transparent); }
  .cs-active{ color:var(--blue,#5B8DEF);  background:color-mix(in srgb, var(--blue,#5B8DEF) 16%, transparent); }
  .cs-fading{ color:var(--amber,#C08A2E); background:color-mix(in srgb, var(--amber,#C08A2E) 18%, transparent); }
  .cs-dormant{ color:var(--text-dim);     background:rgba(120,120,140,.16); }
  .cs-circle{ color:var(--violet,#9A7BE0); background:color-mix(in srgb, var(--violet,#9A7BE0) 18%, transparent); }
  .cs-circle.faded{ color:var(--text-faint); background:rgba(120,120,140,.12); }
  .contact-tag{ font-size:10px; font-weight:600; padding:1px 7px; border-radius:999px;
    border:1px solid var(--glass-border-hi); color:var(--text-dim); }
  .found-bar{ display:flex; align-items:center; gap:14px; margin-bottom:14px;
    padding:11px 14px; border-radius:11px; border:1px solid var(--glass-border);
    background:var(--glass-bg); font-size:13px; }
  .found-what{ flex:1; min-width:0; color:var(--text); }
  .found-list{ max-height:min(56vh, 520px); overflow-y:auto; margin:0 -4px; padding:0 4px; }
  .found-group{ font-size:10.5px; font-weight:700; letter-spacing:.1em; text-transform:uppercase;
    color:var(--text-dim); margin:16px 0 4px; }
  .found-group:first-child{ margin-top:4px; }
  .found-row{ display:flex; align-items:center; gap:11px; padding:9px 0;
    border-bottom:1px solid var(--glass-border); font-size:13px; }
  .found-row:last-child{ border-bottom:none; }
  .found-row input[type=checkbox]{ flex:none; width:16px; height:16px; accent-color:var(--blue); cursor:pointer; }
  /* Two columns of names instead of one long scroll. A group heading must never
     be the last thing in a column with its list starting in the next one, and a
     row must never be sliced in half across the gutter — both are break rules,
     not layout rules, which is why columns are right here and grid is not. */
  @media(min-width:700px){
    .found-list{ column-count:2; column-gap:30px; }
    .found-group{ break-after:avoid; }
    .found-row{ break-inside:avoid; }
  }
  .found-main{ flex:1; min-width:0; }
  .found-name{ font-weight:600; }
  .found-meta{ display:block; font-size:11px; color:var(--text-dim); margin-top:1px;
    white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
  .found-x{ flex:none; cursor:pointer; color:var(--text-dim); font-size:11px; font-weight:600; }
  .found-x:hover{ color:var(--text); }
  .gift-row{ display:flex; align-items:baseline; gap:12px; padding:10px 0;
    border-bottom:1px solid var(--glass-border); font-size:13.5px; }
  .gift-row:last-child{ border-bottom:none; }
  .gift-dir{ font-size:10px; font-weight:700; letter-spacing:.1em; text-transform:uppercase;
    flex:none; width:52px; color:var(--text-dim); }
  /* The one place a second colour is allowed on this screen: an idea is not a
     record of something that happened, and reading it as one would be wrong. */
  .gift-idea{ color:var(--accent-2, var(--accent)); }
  :root[data-mode="light"] .gift-idea{ color:var(--accent); }
  .gift-what{ flex:1; min-width:0; font-weight:600; }
  .gift-meta{ font-size:11.5px; color:var(--text-dim); flex:none; }
  .gift-row .x{ cursor:pointer; color:var(--text-faint); font-size:15px; line-height:1; padding:0 4px; flex:none; }
  .gift-row .x:hover{ color:var(--cat-red-text); }

  .vault-row2{ display:grid; grid-template-columns:1fr 1fr; gap:14px; }
  @media(max-width:560px){ .vault-row2{ grid-template-columns:1fr; } }

  /* ==========================================================
     PROJECTS, SECOND PASS — the parts a real project tool has.
     ----------------------------------------------------------
     What was here was a board, a list and a month grid, and every one of them
     showed a title and a date. That is a task list with columns. The gap
     between it and the thing he runs his week in is not one feature, it is
     about twenty small ones: the chips that tell you a card's state without
     opening it, a drawer with every field on it rather than a modal with four,
     a timeline you can see a slip coming on, and numbers at the end of it.
     All of it in Lucid's own tokens — no borrowed palette, no second design.
     ========================================================== */

  /* ---- chips: the whole language of the board ---- */
  .pj-chip{ display:inline-flex; align-items:center; gap:4px; font-size:10.5px; font-weight:700;
    padding:2px 8px; border-radius:20px; white-space:nowrap; line-height:1.6; }
  .pj-chip-hi{ background:color-mix(in srgb, var(--cat-red) 18%, transparent); color:var(--cat-red-text); }
  .pj-chip-med{ background:color-mix(in srgb, var(--orange) 18%, transparent); color:var(--orange-text); }
  .pj-chip-low{ background:color-mix(in srgb, var(--blue) 16%, transparent); color:var(--blue-text); }
  .pj-chip-none{ background:transparent; color:var(--text-faint); border:1px dashed var(--glass-border-hi); }
  .pj-chip-overdue{ background:color-mix(in srgb, var(--cat-red) 20%, transparent); color:var(--cat-red-text); }
  .pj-chip-soon{ background:color-mix(in srgb, var(--orange) 18%, transparent); color:var(--orange-text); }
  .pj-chip-date{ background:rgba(120,120,140,.14); color:var(--text-dim); font-weight:600; }
  .pj-chip-blocked{ background:color-mix(in srgb, var(--violet) 20%, transparent); color:var(--violet-text); }
  .pj-chip-linked{ background:color-mix(in srgb, var(--teal) 18%, transparent); color:var(--teal-text); }
  .pj-chip-sub{ background:rgba(120,120,140,.14); color:var(--text-dim); font-weight:600; }
  /* A CHIP IS A TINT WITH TEXT ON IT, AND THE TINT IS THE PROBLEM IN LIGHT MODE.
     An 18% wash of a hue over cream is nearly cream; the hue at full strength on
     top of it lands around 4:1 — close enough to look deliberate and still under
     the line at 10.5px. Deepening the lettering toward the theme's darkest
     neutral keeps the hue recognisable and buys the contrast. Measured, not
     guessed: these were 3.67–4.11 before, 5.0–6.4 after. */
  :root[data-mode="light"] .pj-chip-hi,
  :root[data-mode="light"] .pj-chip-overdue{ color:color-mix(in srgb, var(--cat-red) 72%, var(--neutral-2)); }
  :root[data-mode="light"] .pj-chip-med,
  :root[data-mode="light"] .pj-chip-soon{ color:color-mix(in srgb, var(--orange) 72%, var(--neutral-2)); }
  :root[data-mode="light"] .pj-chip-low{ color:color-mix(in srgb, var(--blue) 72%, var(--neutral-2)); }
  :root[data-mode="light"] .pj-chip-blocked{ color:color-mix(in srgb, var(--violet) 72%, var(--neutral-2)); }
  :root[data-mode="light"] .pj-chip-linked{ color:color-mix(in srgb, var(--teal) 72%, var(--neutral-2)); }
  :root[data-mode="light"] .pj-chip-sub,
  :root[data-mode="light"] .pj-chip-date{ color:var(--text); }
  /* And the mirror of it on the dark side: the red chips put the pastel red on
     a red wash, which is two versions of the same hue a shade apart. Lifted
     toward white so the lettering separates from its own background. */
  :root:not([data-mode="light"]) .pj-chip-hi,
  :root:not([data-mode="light"]) .pj-chip-overdue{ color:color-mix(in srgb, var(--cat-red-2) 62%, #fff); }
  :root:not([data-mode="light"]) .pj-chip-med,
  :root:not([data-mode="light"]) .pj-chip-soon{ color:color-mix(in srgb, var(--orange-2) 70%, #fff); }
  :root[data-mode="light"] .proj-status-pill{ font-weight:800; }
  :root[data-mode="light"] .ps-on_track{ color:color-mix(in srgb, var(--cat-green) 72%, var(--neutral-2)); }
  :root[data-mode="light"] .ps-at_risk{ color:color-mix(in srgb, var(--cat-orange) 72%, var(--neutral-2)); }
  :root[data-mode="light"] .ps-off_track{ color:color-mix(in srgb, var(--cat-red) 72%, var(--neutral-2)); }
  :root[data-mode="light"] .ps-complete{ color:color-mix(in srgb, var(--cat-blue) 72%, var(--neutral-2)); }
  :root[data-mode="light"] .ps-on_hold{ color:var(--text); }
  :root[data-mode="light"] .pj-fav{ color:var(--text-dim); }
  .pj-chip[onclick]{ cursor:pointer; }
  .pj-chip[onclick]:hover{ filter:brightness(1.12); }
  /* The "-2" pastels are the light-on-dark variants; on cream they vanish. One
     token per hue that already knows which ground it is on. */
  :root{ --violet-text:var(--violet-2); --teal-text:var(--teal-2); --green-text:var(--green-2); }
  :root[data-mode="light"]{ --violet-text:var(--violet); --teal-text:var(--teal); --green-text:var(--green); }

  /* ---- header ---- */
  .pj-head{ display:flex; align-items:flex-start; gap:14px; margin-bottom:6px; flex-wrap:wrap; }
  .pj-dot{ width:12px; height:12px; border-radius:4px; flex:none; margin-top:9px; cursor:pointer; }
  .pj-head-main{ flex:1; min-width:220px; }
  .pj-progress{ height:5px; border-radius:3px; background:rgba(120,120,140,.16); overflow:hidden; max-width:320px; margin-top:8px; }
  .pj-progress > span{ display:block; height:100%; border-radius:3px; transition:width var(--dur-base) var(--ease-out); }
  .pj-toolbar{ display:flex; align-items:center; gap:10px; flex-wrap:wrap; margin:14px 0 16px; }
  .pj-search{ position:relative; flex:0 1 240px; }
  .pj-search input{ width:100%; background:rgba(120,120,140,.08); border:1px solid var(--glass-border);
    border-radius:10px; color:var(--text); font-family:'Public Sans'; font-size:13px; padding:9px 12px; }
  .pj-search input:focus{ outline:none; border-color:color-mix(in srgb, var(--accent) 50%, transparent); }
  .pj-filter{ display:flex; gap:4px; padding:4px; border-radius:11px; background:rgba(120,120,140,.08); }
  .pj-filter div{ padding:6px 13px; border-radius:8px; font-size:12.5px; font-weight:600; color:var(--text-dim); cursor:pointer; }
  .pj-filter div.active{ background:color-mix(in srgb, var(--blue) 20%, transparent); color:var(--text); }

  /* ---- board ---- */
  .pj-card-row{ display:flex; align-items:center; gap:6px; flex-wrap:wrap; margin-top:8px; }
  .pj-card-top{ display:flex; align-items:flex-start; gap:8px; }
  .pj-card-check{ width:15px; height:15px; border-radius:50%; border:2px solid var(--glass-border-hi);
    flex:none; margin-top:2px; cursor:pointer; transition:all var(--dur-fast) var(--ease-out); }
  .pj-card-check:hover{ border-color:var(--green); }
  .pj-card-check.done{ background:var(--green); border-color:var(--green); }
  .pj-add-inline{ width:100%; background:transparent; border:1px dashed var(--glass-border-hi); border-radius:10px;
    color:var(--text-faint); font-family:'Public Sans'; font-size:12.5px; padding:9px 12px; cursor:pointer; text-align:left; }
  .pj-add-inline:hover{ color:var(--text); border-color:var(--accent); }
  .pj-add-inline:focus{ outline:none; color:var(--text); border-style:solid; border-color:color-mix(in srgb, var(--accent) 50%, transparent); cursor:text; }

  /* ---- timeline / gantt ---- */
  /* A grid, not a table. Every bar is positioned in the same left-to-right
     pixel space as the day headings above it, so a bar and its date column can
     never disagree — which is the one bug every hand-built gantt has. */
  .pj-tl-wrap{ overflow-x:auto; padding-bottom:10px; }
  .pj-tl{ min-width:100%; position:relative; }
  .pj-tl-head{ display:flex; position:sticky; top:0; z-index:2; }
  .pj-tl-labels{ width:230px; flex:none; }
  .pj-tl-days{ display:flex; }
  .pj-tl-day{ width:32px; flex:none; text-align:center; font-size:9.5px; font-weight:700;
    color:var(--text-faint); padding:4px 0; border-left:1px solid var(--glass-border); }
  .pj-tl-day.wknd{ background:rgba(120,120,140,.07); }
  .pj-tl-day.today{ background:color-mix(in srgb, var(--accent) 18%, transparent); color:var(--text); border-radius:6px 6px 0 0; }
  .pj-tl-months{ display:flex; }
  .pj-tl-mon{ font-size:10.5px; font-weight:700; color:var(--text-dim); text-transform:uppercase;
    letter-spacing:.06em; padding:2px 0 3px 6px; flex:none; border-left:1px solid var(--glass-border-hi);
    overflow:hidden; white-space:nowrap; }
  .pj-tl-row{ display:flex; align-items:center; height:40px; }
  .pj-tl-name{ width:230px; flex:none; font-size:12.5px; padding-right:12px; cursor:pointer;
    overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
  .pj-tl-name:hover{ text-decoration:underline; }
  .pj-tl-track{ position:relative; height:40px; flex:none; }
  .pj-tl-grid{ position:absolute; inset:0; display:flex; pointer-events:none; }
  .pj-tl-grid span{ width:32px; flex:none; border-left:1px solid var(--glass-border); }
  .pj-tl-grid span.wknd{ background:rgba(120,120,140,.07); }
  .pj-tl-bar{ position:absolute; top:11px; height:18px; border-radius:6px; cursor:pointer; }
  .pj-tl-bar:focus{ outline:2px solid var(--ring); }
  .pj-tl-bar:hover{ filter:brightness(1.1); }
  .pj-tl-bar.done{ opacity:.5; }
  .pj-tl-nodate{ font-size:11px; color:var(--text-faint); padding-left:6px; }
  .pj-tl-today{ position:absolute; top:0; bottom:0; width:2px; background:var(--cat-red); opacity:.7; pointer-events:none; }

  /* ---- reports ---- */
  .pj-rep-grid{ display:grid; grid-template-columns:repeat(auto-fit,minmax(190px,1fr)); gap:12px; margin-bottom:18px; }
  .pj-stat{ padding:16px 18px; border-radius:13px; background:var(--glass-bg); border:1px solid var(--glass-border); }
  .pj-stat-num{ font-size:28px; font-weight:700; letter-spacing:-.02em; line-height:1.1; }
  .pj-stat-lbl{ font-size:11px; font-weight:700; text-transform:uppercase; letter-spacing:.07em; color:var(--text-faint); margin-top:5px; }
  .pj-bars{ display:flex; flex-direction:column; gap:9px; }
  .pj-bar-row{ display:flex; align-items:center; gap:10px; font-size:12.5px; }
  .pj-bar-name{ width:150px; flex:none; color:var(--text-dim); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
  .pj-bar-track{ flex:1; height:9px; border-radius:5px; background:rgba(120,120,140,.14); overflow:hidden; }
  .pj-bar-track > span{ display:block; height:100%; border-radius:5px; }
  .pj-bar-val{ width:64px; flex:none; text-align:right; color:var(--text-faint); font-size:11.5px;
    font-family:'IBM Plex Mono', monospace; }

  /* ---- the drawer ----
     A slide-over, not a centred modal. A task has twenty fields on it and the
     board behind it is the context for all of them — covering the board to edit
     a card on it is exactly backwards. */
  .pj-drawer-scrim{ position:fixed; inset:0; z-index:60; background:rgba(4,6,16,0.28);
    opacity:0; pointer-events:none; transition:opacity var(--dur-base) var(--ease-out); }
  :root[data-mode="light"] .pj-drawer-scrim{ background:rgba(62,50,38,0.18); }
  .pj-drawer-scrim.open{ opacity:1; pointer-events:auto; }
  /* AN OPAQUE PANEL, not a pane of glass.
     .glass paints `var(--glass-sheen), var(--glass-bg)` and both are
     translucent, so what is BEHIND a glass panel changes the contrast of the
     text on it. That is fine for a card sitting on the page background and
     wrong for a drawer, which slides over whatever happened to be underneath —
     and once a project's own colour started tinting the view behind it, the
     Delete label measured 4.28:1 against a 4.5 floor. It was legible over the
     old grey and would have gone on being legible right up until the day a
     project was given a pale colour.
     A solid ground under the glass fixes the whole class: the drawer now reads
     the same over any page, in any theme, whatever colour the project is. */
  .pj-drawer{ position:fixed; top:0; right:0; bottom:0; width:min(560px, 100vw); z-index:61;
    display:flex; flex-direction:column; border-radius:0; border-right:none; border-top:none; border-bottom:none;
    background-color: var(--bg-mid);
    transform:translateX(102%); transition:transform .28s cubic-bezier(.2,.8,.3,1); }
  .pj-drawer.open{ transform:none; }
  @media(max-width:600px){ .pj-drawer{ width:100vw; } }
  .pj-drawer-head{ padding:20px 22px 12px; border-bottom:1px solid var(--glass-border); flex:none; }
  .pj-drawer-crumb{ font-size:11px; font-weight:700; text-transform:uppercase; letter-spacing:.06em;
    color:var(--text-faint); margin-bottom:8px; display:flex; align-items:center; gap:8px; }
  .pj-drawer-crumb > span:first-child{ display:flex; align-items:center; gap:7px; min-width:0;
    overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
  .pj-unit{ font-size:11px; color:var(--text-faint); }
  /* 3.64:1 on dark. The one control on this drawer that destroys work should
     not be the one control you have to squint at — and a word that is hard to
     read is a word that gets clicked by accident. --text-dim; the red arrives
     on hover, where it belongs. */
  .pj-del-link{ font-size:11px; font-weight:700; letter-spacing:.06em; color:var(--text-dim);
    cursor:pointer; padding:2px 6px; border-radius:6px; }
  .pj-del-link:hover{ color:var(--cat-red-text); background:color-mix(in srgb, var(--cat-red) 12%, transparent); }
  .pj-drawer-crumb .sp{ margin-left:auto; display:flex; gap:6px; }
  .pj-drawer-title{ font-family:'Fraunces', serif; font-size:21px; font-weight:700; line-height:1.25;
    outline:none; border-radius:7px; padding:2px 4px; margin:-2px -4px; }
  .pj-drawer-title:focus{ background:rgba(120,120,140,.10); box-shadow:0 0 0 1px var(--glass-border-hi); }
  .pj-drawer-body{ flex:1; min-height:0; overflow-y:auto; padding:16px 22px 40px; scrollbar-width:thin; }
  .pj-field{ display:flex; align-items:center; gap:12px; padding:9px 0; border-bottom:1px solid var(--glass-border); }
  .pj-field > label{ width:112px; flex:none; font-size:11px; font-weight:700; text-transform:uppercase;
    letter-spacing:.06em; color:var(--text-faint); }
  .pj-field > .pj-field-val{ flex:1; min-width:0; display:flex; align-items:center; gap:7px; flex-wrap:wrap; }
  .pj-input{ width:100%; background:rgba(120,120,140,.08); border:1px solid var(--glass-border); border-radius:9px;
    color:var(--text); font-family:'Public Sans'; font-size:13px; padding:8px 11px; }
  .pj-input:focus{ outline:none; border-color:color-mix(in srgb, var(--accent) 50%, transparent); }
  .pj-input[type="date"]{ color-scheme:dark; }
  :root[data-mode="light"] .pj-input[type="date"]{ color-scheme:light; }
  .pj-seg{ display:flex; gap:4px; }
  .pj-seg div{ padding:5px 13px; border-radius:8px; font-size:12px; font-weight:700; cursor:pointer;
    color:var(--text-dim); border:1px solid var(--glass-border-hi); }
  .pj-seg div.on{ color:var(--text); border-color:transparent; }
  .pj-seg div.on[data-p="high"]{ background:color-mix(in srgb, var(--cat-red) 22%, transparent); }
  .pj-seg div.on[data-p="medium"]{ background:color-mix(in srgb, var(--orange) 22%, transparent); }
  .pj-seg div.on[data-p="low"]{ background:color-mix(in srgb, var(--blue) 22%, transparent); }
  .pj-seg div.on[data-p="none"]{ background:rgba(120,120,140,.18); }
  .pj-toggle{ width:38px; height:21px; border-radius:20px; background:rgba(120,120,140,.26); position:relative;
    cursor:pointer; flex:none; transition:background var(--dur-fast) var(--ease-out); }
  .pj-toggle::after{ content:''; position:absolute; top:2.5px; left:2.5px; width:16px; height:16px; border-radius:50%;
    background:var(--text); transition:transform var(--dur-fast) var(--ease-out); }
  .pj-toggle.on{ background:var(--green); }
  .pj-toggle.on::after{ transform:translateX(17px); background:#fff; }
  .pj-done-row{ display:flex; align-items:center; gap:10px; font-size:13.5px; font-weight:600; margin-bottom:4px; }
  .pj-sub-row{ display:flex; align-items:center; gap:9px; padding:7px 0; font-size:13px; }
  .pj-sub-row .list-check{ cursor:pointer; }
  .pj-sub-row .x{ margin-left:auto; cursor:pointer; color:var(--text-faint); padding:0 4px; }
  .pj-sub-row .x:hover{ color:var(--cat-red-text); }
  .pj-sub-open{ margin-left:auto; font-size:11px; color:var(--text-faint); cursor:pointer; }
  .pj-sub-open:hover{ color:var(--text); text-decoration:underline; }
  .pj-pill-list{ display:flex; flex-wrap:wrap; gap:6px; }
  .pj-pill{ display:inline-flex; align-items:center; gap:6px; font-size:11.5px; font-weight:600;
    padding:4px 9px; border-radius:20px; background:rgba(120,120,140,.14); }
  .pj-pill .x{ cursor:pointer; color:var(--text-faint); font-size:13px; line-height:1; }
  .pj-pill .x:hover{ color:var(--cat-red-text); }
  .pj-timer{ display:flex; align-items:center; gap:10px; flex-wrap:wrap; }
  .pj-timer-run{ font-family:'IBM Plex Mono', monospace; font-size:13px; color:var(--green-text); font-weight:700; }
  .pj-notes{ width:100%; min-height:96px; resize:vertical; background:rgba(120,120,140,.08);
    border:1px solid var(--glass-border); border-radius:10px; color:var(--text);
    font-family:'Public Sans'; font-size:13.5px; line-height:1.6; padding:11px 13px; }
  .pj-notes:focus{ outline:none; border-color:color-mix(in srgb, var(--accent) 50%, transparent); }

  /* ---- project cards & rail groups ---- */
  .pj-group-head{ display:flex; align-items:center; gap:8px; margin:22px 0 10px; }
  .pj-group-head:first-child{ margin-top:0; }
  /* Off/on is carried by COLOUR alone, never by opacity. Fading a 13px glyph to
     50% halves the ink in every stroke of it — the star measured 2.18:1 that
     way, against 3.57:1 at full strength in the same colour. Opacity is the one
     lever that cannot be compensated for by choosing a better colour. */
  .pj-fav{ cursor:pointer; color:var(--text-faint); font-size:13px;
    transition:color var(--dur-fast) var(--ease-out); }
  .pj-fav:hover{ color:var(--orange-text); }
  .pj-fav.on{ color:var(--orange-text); }
  .create-type-row{ display:flex; gap:6px; margin-bottom:14px; }
  .create-type{ flex:1; text-align:center; padding:9px 6px; border-radius:10px; border:1px solid var(--glass-border-hi); font-size:12.5px; font-weight:600; cursor:pointer; color:var(--text-dim); }
  .create-type.active{ background:color-mix(in srgb, var(--accent) 18%, transparent); color:var(--text); border-color:transparent; }
  /* Date, the Day/This Week toggle and the weather chip now share ONE row
     sitting directly on top of the calendar, instead of stacking into three
     separate bands. Saves ~60px of vertical space on the busiest view. */
  .today-title-row{ display:flex; justify-content:flex-start; align-items:center; gap:16px; margin-bottom:12px; flex-wrap:wrap; }
  .today-title-row .weather-chip{ margin-left:auto; }
  .today-title-row .page-title{ margin-bottom:3px; }
  .today-title-row .page-sub{ margin-bottom:0; }
  .weather-chip{ display:flex; align-items:center; gap:8px; padding:8px 13px; border-radius:14px; flex:none; }
  .weather-icon{ width:19px; height:19px; color:var(--blue-2); flex:none; }
  /* THE "-2" TOKENS ARE THE LIGHT-ON-DARK VARIANT. --blue-2 is a pastel: it
     exists so a blue can be read against a near-black panel. Painted on cream
     it is barely there — the rain figure measured 1.46:1, which is not a
     colour, it is a rumour. In light mode these take the solid variant. */
  :root[data-mode="light"] .weather-icon,
  :root[data-mode="light"] .weather-rain,
  :root[data-mode="light"] .weather-hero-icon,
  :root[data-mode="light"] .forecast-icon,
  :root[data-mode="light"] .forecast-rain{ color:var(--blue); }
  /* Same story for the overdue markers, which are written from script. A class
     rather than an inline style, so light mode can override it at all. */
  .overdue-mark{ color:var(--cat-red-2); }
  :root[data-mode="light"] .overdue-mark{ color:var(--cat-red); }
  .weather-temps{ display:flex; flex-direction:column; line-height:1.15; }
  .weather-hi{ font-size:13.5px; font-weight:700; }
  .weather-lo{ font-size:10.5px; color:var(--text-faint); }
  .weather-chip{ cursor:pointer; transition:all var(--dur-fast) var(--ease-out); }
  .weather-chip:hover{ border-color:var(--glass-border-hi); }
  .weather-rain{ display:flex; align-items:center; gap:3px; font-size:10.5px; color:var(--blue-2); font-weight:700; border-left:1px solid var(--glass-border); padding-left:9px; margin-left:1px; }
  .weather-rain svg{ width:11px; height:11px; }
  /* 400px and a scrolling ten-day list: the narrow-and-tall shape, in the one
     card that has the least reason to be either. Six detail tiles across three
     columns and ten forecast rows fit side by side with room over on any
     desktop, and then nothing inside it scrolls at all. */
  .weather-modal{ max-width:620px; }
  @media(min-width:781px){
    .weather-modal{ max-width:820px; }
    .weather-modal .weather-split{ display:grid; grid-template-columns:1fr 1fr; gap:0 26px; align-items:start; }
    .weather-modal .weather-split > *{ min-width:0; }
    .weather-modal .forecast-list{ max-height:none; overflow:visible; }
    .weather-modal .weather-detail-grid{ grid-template-columns:repeat(2,1fr); }
  }
  .weather-hero{ display:flex; align-items:center; gap:14px; margin-bottom:16px; }
  .weather-hero-icon{ width:42px; height:42px; color:var(--blue-2); flex:none; }
  .weather-hero-temp{ font-family:'Fraunces',serif; font-size:32px; font-weight:700; line-height:1; }
  .weather-hero-cond{ font-size:12.5px; color:var(--text-dim); margin-top:4px; }
  .weather-detail-grid{ display:grid; grid-template-columns:repeat(3,1fr); gap:9px; margin-bottom:4px; }
  .wd-item{ background:rgba(120,120,140,.06); border:1px solid var(--glass-border); border-radius:10px; padding:9px 10px; }
  .wd-label{ font-size:9.5px; text-transform:uppercase; color:var(--text-faint); letter-spacing:.05em; margin-bottom:3px; }
  .wd-value{ font-size:14px; font-weight:700; }
  .forecast-list{ max-height:280px; overflow-y:auto; margin:0 -4px; padding:0 4px; }
  .forecast-row{ display:flex; align-items:center; gap:10px; padding:8px 2px; border-bottom:1px solid var(--glass-border); font-size:12.5px; }
  .forecast-row:last-child{ border:none; }
  .forecast-day{ width:38px; font-weight:600; flex:none; }
  .forecast-icon{ width:17px; height:17px; color:var(--blue-2); flex:none; }
  .forecast-rain{ font-size:10.5px; color:var(--blue-2); width:32px; text-align:right; flex:none; }
  .forecast-temps{ margin-left:auto; font-family:'IBM Plex Mono',monospace; font-size:11.5px; display:flex; gap:6px; }
  .forecast-lo{ color:var(--text-faint); }
  /* ==========================================================
     THE BOARD CANVAS
     ==========================================================
     Three layers, and keeping them separate is what makes the rest simple:

       .bc-viewport   fixed to the window. Clips, and owns the cursor.
       .bc-layer      carries translate(pan) scale(zoom). Everything on the
                      board is a child, positioned in CANVAS coordinates.
       .bc-card       absolutely positioned at its own x/y. Never told about
                      pan or zoom at all.

     The rule that keeps it honest: nothing outside bcScreenToCanvas() ever
     mixes the two coordinate systems. Every bug in a canvas like this is a
     screen number used where a canvas number was meant.

     transform-origin is 0 0 so the arithmetic is a plain
     screen = canvas * zoom + pan, invertible in one line. With a centred
     origin every conversion needs the viewport size and half of them are
     wrong. */
  .dlg-pick{ padding:8px 10px; border-radius:8px; cursor:pointer; }
  .dlg-pick:hover{ background:color-mix(in srgb, var(--accent) 14%, transparent); }

  .bc-bar{ display:flex; align-items:center; gap:12px; padding:0 0 12px; flex:none; }
  .bc-boards{ display:flex; align-items:center; gap:6px; flex-wrap:wrap; min-width:0; }
  .bc-bar-right{ margin-left:auto; display:flex; align-items:center; gap:8px; flex:none; }
  .bc-board-tab{ display:flex; align-items:center; gap:6px; padding:6px 12px; border-radius:999px;
    border:1px solid var(--glass-border); font-size:12.5px; font-weight:600; cursor:pointer;
    color:var(--text-dim); background:transparent; white-space:nowrap;
    transition:color var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out), background var(--dur-fast) var(--ease-out); }
  .bc-board-tab:hover{ color:var(--text); border-color:var(--glass-border-hi); }
  .bc-board-tab.active{ color:var(--text); border-color:color-mix(in srgb, var(--accent) 50%, transparent);
    background:color-mix(in srgb, var(--accent) 12%, transparent); }
  .bc-board-count{ font-family:'IBM Plex Mono', monospace; font-size:10px; opacity:.65; }
  .bc-board-add{ width:28px; height:28px; border-radius:50%; border:1px dashed var(--glass-border-hi);
    display:flex; align-items:center; justify-content:center; cursor:pointer; color:var(--text-faint);
    font-size:15px; line-height:1; flex:none; }
  .bc-board-add:hover{ color:var(--text); border-color:var(--accent); }
  .bc-zoom{ display:flex; align-items:center; gap:2px; border:1px solid var(--glass-border);
    border-radius:999px; padding:2px; }
  .bc-zoom-btn{ width:26px; height:24px; display:flex; align-items:center; justify-content:center;
    cursor:pointer; border-radius:999px; color:var(--text); font-size:15px; user-select:none; }
  .bc-zoom-btn:hover{ background:color-mix(in srgb, var(--accent) 14%, transparent); color:var(--text); }
  .bc-zoom-level{ font-family:'IBM Plex Mono', monospace; font-size:11px; color:var(--text-dim);
    min-width:46px; text-align:center; cursor:pointer; user-select:none; }

  .bc-stage{ position:relative; display:flex; gap:12px; flex:1; min-height:0; }

  .bc-tray{ width:104px; flex:none; display:flex; flex-direction:column; gap:4px; padding:12px 8px;
    border-radius:14px; overflow-y:auto; }
  .bc-tray-title{ font-size:9.5px; text-transform:uppercase; letter-spacing:.09em;
    color:var(--text-faint); padding:0 4px 4px; }
  .bc-tool{ display:flex; align-items:center; gap:8px; padding:7px 8px; border-radius:9px;
    font-size:12px; font-weight:600; color:var(--text-dim); cursor:grab; user-select:none;
    transition:background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out); }
  .bc-tool:hover{ background:color-mix(in srgb, var(--accent) 12%, transparent); color:var(--text); }
  .bc-tool:active{ cursor:grabbing; }
  /* MORE INK, NOT A DARKER TOKEN.
     At 9px a stroke is narrower than one pixel, so every pixel that renders it
     is already a blend with whatever is behind — darkening the colour moves the
     number in the stylesheet and almost nothing on the glass. Size is the lever
     that works, which is why these went to 11px rather than to a new colour. */
  .bc-tool-ico{ width:16px; height:16px; border-radius:4px; flex:none; border:1px solid var(--glass-border-hi);
    display:flex; align-items:center; justify-content:center; font-size:11px; font-weight:700; }
  .bc-ico-h{ background:color-mix(in srgb, var(--text) 12%, transparent); color:var(--text); }
  .bc-ico-sw{ background:linear-gradient(135deg, var(--pink), var(--orange) 50%, var(--teal)); border:none; }
  .bc-ico-person{ background:color-mix(in srgb, var(--teal) 22%, transparent); color:var(--text); }
  /* A 9px glyph has strokes thinner than a pixel, so no pixel ever receives the
     nominal colour — the fix is more ink rather than a darker token. */
  .bc-ico-mail{ background:color-mix(in srgb, var(--blue) 22%, transparent);
    color:var(--text); font-size:11px; }
  .bc-ico-board{ background:color-mix(in srgb, var(--violet) 22%, transparent);
    color:var(--text); font-size:11px; }
  .bc-ico-tpl{ background:color-mix(in srgb, var(--green) 22%, transparent);
    color:var(--text); font-size:11px; }

  /* THE WAY BACK OUT of a sub-board. Deliberately not shaped like a tab: it is
     not another board to switch to, it is the door you came in through, and a
     tab would put it in the same row of equals as the boards it is parent to. */
  .bc-board-up{ display:flex; align-items:center; gap:6px; padding:6px 10px; border-radius:999px;
    font-size:12px; font-weight:600; cursor:pointer; color:var(--text-faint); white-space:nowrap;
    border:1px dashed var(--glass-border-hi); background:transparent; }
  .bc-board-up:hover{ color:var(--text); border-color:var(--accent); }

  /* ARROWS.
     Drawn under every card — bcAdd starts the first card at z-index 0 and
     counts up, so nothing can sit below this. An arrow painted over the cards
     would cross their text.
     pointer-events:none on the layer, and back on for the fat invisible hit
     line only: without that the SVG would swallow every drag passing over the
     region it covers, which on a busy board is most of it. */
  .bc-arrow-layer{ position:absolute; overflow:visible; pointer-events:none; z-index:0; }
  .bc-arrow{ stroke:var(--text-faint); stroke-width:1.6; fill:none; }
  .bc-arrow.dashed{ stroke-dasharray:6 5; }
  .bc-arrow-layer marker path{ fill:var(--text-faint); }
  /* Twelve pixels wide and invisible: a 1.6px line is not something anybody can
     click, and the alternative — a delete handle riding the middle of every
     arrow — puts permanent furniture on the board for a thing done once. */
  .bc-arrow-hit{ stroke:transparent; stroke-width:12; fill:none; cursor:pointer; pointer-events:stroke; }
  .bc-arrow-label{ font-size:10.5px; fill:var(--text-dim); font-weight:600;
    paint-order:stroke; stroke:var(--bg-base); stroke-width:3px; }
  /* While a join is armed the whole board says so. A cursor change and a ring
     on whatever is under the pointer are the honest signals — a banner would
     cover the cards he is trying to pick between. */
  .bc-viewport.linking{ cursor:crosshair; }
  .bc-viewport.linking .bc-card:hover{ outline:2px solid var(--accent); outline-offset:2px; }

  /* THE TEMPLATE CHOOSER. Rows rather than thumbnails: a thumbnail of six empty
     notes says nothing a sentence does not, and it would be one more thing to
     keep in step with the templates themselves. */
  .bc-tpl-list{ display:flex; flex-direction:column; gap:6px; margin-top:4px; }
  .bc-tpl-row{ padding:10px 12px; border-radius:10px; cursor:pointer;
    border:1px solid var(--glass-border); }
  .bc-tpl-row:hover{ background:color-mix(in srgb, var(--accent) 12%, transparent);
    border-color:color-mix(in srgb, var(--accent) 45%, transparent); }
  .bc-tpl-name{ font-size:13px; font-weight:700; color:var(--text); }
  .bc-tpl-blurb{ font-size:12px; color:var(--text-dim); margin-top:2px; }
  .bc-tpl-count{ font-family:'IBM Plex Mono', monospace; font-size:10px; color:var(--text-faint); margin-top:4px; }

  .bc-viewport{ position:relative; flex:1; min-width:0; border-radius:16px; overflow:hidden;
    cursor:default; touch-action:none;
    /* The grid is drawn on the viewport rather than the layer and its size is
       set from JS on every zoom, so it scales with the board without a
       repaint of a huge background image. */
    background-image:radial-gradient(circle, var(--glass-border-hi) 1px, transparent 1px);
    background-size:24px 24px; background-position:0 0; }
  .bc-viewport.panning{ cursor:grabbing; }
  .bc-viewport.pannable{ cursor:grab; }
  .bc-viewport.drop-active{ box-shadow:inset 0 0 0 2px color-mix(in srgb, var(--accent) 55%, transparent); }
  .bc-layer{ position:absolute; top:0; left:0; width:0; height:0; transform-origin:0 0; will-change:transform; }

  .bc-marquee{ position:absolute; border:1px solid color-mix(in srgb, var(--accent) 70%, transparent);
    background:color-mix(in srgb, var(--accent) 12%, transparent); border-radius:3px;
    pointer-events:none; z-index:5; }

  .bc-empty{ position:absolute; inset:0; display:flex; align-items:center; justify-content:center;
    pointer-events:none; }
  .bc-empty-inner{ text-align:center; max-width:340px; }
  .bc-empty-title{ font-size:15px; font-weight:700; color:var(--text-dim); margin-bottom:6px; }
  .bc-empty-sub{ font-size:12.5px; color:var(--text-faint); line-height:1.7; }
  .bc-key{ font-family:'IBM Plex Mono', monospace; font-size:11px; padding:1px 5px; border-radius:4px;
    border:1px solid var(--glass-border-hi); }

  /* ---- cards ---- */
  .bc-card{ position:absolute; border-radius:12px; box-sizing:border-box; overflow:hidden;
    border:1px solid var(--glass-border); background:var(--glass-bg);
    box-shadow:var(--glass-shadow); display:flex; flex-direction:column; }
  /* No transition on transform or size: those change every frame during a
     drag, and an easing curve on them turns a drag into a lag. */
  .bc-card.selected{ border-color:color-mix(in srgb, var(--accent) 65%, transparent);
    box-shadow:var(--glass-shadow), 0 0 0 2px color-mix(in srgb, var(--accent) 40%, transparent); }
  .bc-card.dragging{ opacity:.92; }
  .bc-card-head{ display:flex; align-items:center; gap:6px; padding:6px 8px 0; flex:none;
    opacity:0; transition:opacity var(--dur-fast) var(--ease-out); }
  .bc-card:hover .bc-card-head, .bc-card.selected .bc-card-head{ opacity:1; }
  .bc-card-grip{ flex:1; height:14px; cursor:grab; border-radius:4px; }
  /* --text-dim, not --text-faint. Measured at 3.88:1 as the fainter token —
     the same 4.5:1 floor every other control in Lucid has to clear. */
  .bc-card-btn{ width:18px; height:18px; border-radius:5px; display:flex; align-items:center;
    justify-content:center; cursor:pointer; color:var(--text-dim); font-size:13px; line-height:1; flex:none; }
  .bc-card-btn:hover{ background:color-mix(in srgb, var(--text) 12%, transparent); color:var(--text); }
  .bc-card-body{ flex:1; min-height:0; padding:2px 12px 12px; overflow:hidden; }

  /* THE INSPECTOR.
     Floated over the canvas rather than docked beside it: a permanent panel
     down one edge is a permanent tax on the thing it is there to serve, and
     the canvas is the whole point. It sits at the bottom, where it covers the
     least of what is usually being worked on. */
  .bc-inspector{ position:absolute; left:50%; bottom:18px; transform:translate(-50%, 10px);
    display:flex; align-items:center; gap:7px; flex-wrap:wrap; justify-content:center;
    max-width:calc(100% - 32px); padding:8px 12px; border-radius:13px;
    background:var(--glass-bg); border:1px solid var(--glass-border);
    backdrop-filter:blur(14px); -webkit-backdrop-filter:blur(14px);
    box-shadow:0 18px 44px -20px rgba(0,0,0,.6);
    opacity:0; pointer-events:none; z-index:40;
    transition:opacity var(--dur-fast) var(--ease-out), transform var(--dur-fast) var(--ease-out); }
  .bc-inspector.open{ opacity:1; pointer-events:auto; transform:translate(-50%, 0); }
  .bc-insp-n{ font-family:'IBM Plex Mono', monospace; font-size:10.5px; font-weight:600;
    letter-spacing:.08em; text-transform:uppercase; color:var(--text-dim); white-space:nowrap; }
  .bc-insp-rule{ width:1px; height:18px; background:var(--glass-border); flex:none; }
  .bc-insp-btn{ font-size:11.5px; font-weight:600; padding:5px 10px; border-radius:8px;
    border:1px solid transparent; background:none; color:var(--text-dim); cursor:pointer;
    white-space:nowrap; transition:all var(--dur-fast) var(--ease-out); }
  .bc-insp-btn:hover{ color:var(--text); background:color-mix(in srgb, var(--text) 8%, transparent); }
  .bc-insp-btn.on{ color:var(--text); border-color:color-mix(in srgb, var(--accent) 55%, var(--glass-border)); }
  .bc-insp-btn.danger:hover{ color:var(--red-text, var(--red));
    background:color-mix(in srgb, var(--red) 12%, transparent); }
  .bc-sw-row{ display:flex; gap:5px; }
  .bc-sw{ width:19px; height:19px; border-radius:50%; cursor:pointer; padding:0;
    border:1px solid var(--glass-border); background:none;
    transition:transform var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out); }
  .bc-sw:hover{ transform:scale(1.14); }
  .bc-sw.on{ border-color:var(--text); border-width:2px; }
  .bc-seg{ display:flex; border:1px solid var(--glass-border); border-radius:8px; overflow:hidden; }
  .bc-seg button{ font-family:'IBM Plex Mono', monospace; font-size:10.5px; font-weight:600;
    padding:5px 9px; border:none; background:none; color:var(--text-dim); cursor:pointer; }
  .bc-seg button:hover{ color:var(--text); }
  .bc-seg button.on{ color:var(--text); background:color-mix(in srgb, var(--text) 11%, transparent); }

  /* A locked card still selects and still opens — it just cannot be shoved
     across the board by a stray drag, which is the whole complaint. */
  .bc-card.locked{ cursor:default; }
  .bc-card.locked .bc-resize{ display:none; }
  .bc-lock-mark{ font-size:10px; color:var(--text-faint); margin-left:2px; }
  /* The line the card snapped to, drawn only while dragging. */
  .bc-guide{ position:absolute; background:var(--accent); pointer-events:none; z-index:39; opacity:.85; }
  .bc-due{ display:inline-block; margin-top:8px; font-family:'IBM Plex Mono', monospace;
    font-size:10px; font-weight:600; letter-spacing:.07em; text-transform:uppercase;
    color:var(--text-dim); padding:3px 8px; border-radius:6px;
    background:color-mix(in srgb, var(--text) 7%, transparent); }
  .bc-due.soon{ color:var(--orange-text, var(--orange));
    background:color-mix(in srgb, var(--orange) 14%, transparent); }
  .bc-resize{ position:absolute; right:0; bottom:0; width:16px; height:16px; cursor:nwse-resize;
    opacity:0; transition:opacity var(--dur-fast) var(--ease-out); }
  .bc-resize::after{ content:''; position:absolute; right:4px; bottom:4px; width:7px; height:7px;
    border-right:2px solid var(--text-faint); border-bottom:2px solid var(--text-faint); border-radius:0 0 3px 0; }
  .bc-card:hover .bc-resize, .bc-card.selected .bc-resize{ opacity:1; }

  .bc-text{ font-size:var(--bc-size, 13px); line-height:1.55; color:var(--text); outline:none; white-space:pre-wrap;
    word-break:break-word; height:100%; overflow:auto; }
  .bc-text:empty::before{ content:attr(data-placeholder); color:var(--text-faint); }
  .bc-card.type-heading .bc-text{ font-family:'Fraunces', Georgia, serif; font-size:26px; font-weight:700;
    line-height:1.2; }
  .bc-card.type-heading{ background:transparent; border-color:transparent; box-shadow:none; }
  /* A heading is one line of large type and nothing else, so it does not
     reserve a row for the hover controls the way a note does — with the row
     reserved, a 26px line inside a 56px card was clipped at the top. The
     controls float over it instead. */
  .bc-card.type-heading .bc-card-head{ position:absolute; top:2px; right:2px; left:auto; padding:0; }
  .bc-card.type-heading .bc-card-grip{ display:none; }
  .bc-card.type-heading .bc-card-body{ padding:6px 10px; display:flex; align-items:center; }
  .bc-card.type-heading.selected{ border-color:color-mix(in srgb, var(--accent) 45%, transparent); }

  .bc-todo-row{ display:flex; align-items:flex-start; gap:8px; padding:3px 0; font-size:12.5px; }
  .bc-todo-box{ width:14px; height:14px; border-radius:4px; border:1.5px solid var(--glass-border-hi);
    flex:none; margin-top:2px; cursor:pointer; display:flex; align-items:center; justify-content:center;
    font-size:11px; font-weight:700; color:var(--neutral-ink); }
  /* A DARK TICK ON A LIGHT GREEN, not a light tick on a dark one.
     White-on-green was measured at 2.25:1 — the tick is the only text in the
     box, so the fill IS its paper, and a saturated fill leaves nowhere for a
     pale glyph to go. Lightening the fill and keeping the ink dark clears the
     floor and still reads unmistakably as done. */
  .bc-todo-box.done{ background:color-mix(in srgb, var(--green) 58%, #FFFFFF);
    border-color:color-mix(in srgb, var(--green) 75%, transparent); color:#0A0D12; }
  .bc-todo-text{ font-size:var(--bc-size, inherit); flex:1; outline:none; word-break:break-word; line-height:1.5; }
  /* Struck through and dimmed with a TOKEN, not with opacity. Opacity fades the
     ink towards whatever is behind it, which is how a finished line ends up at
     4.26:1 — the same mistake the out-of-month calendar dates once made. */
  .bc-todo-row.done .bc-todo-text{ color:var(--text-dim); text-decoration:line-through; }
  .bc-todo-add{ font-size:12px; color:var(--text-dim); cursor:pointer; padding:4px 0 0; }
  .bc-todo-add:hover{ color:var(--accent); }

  .bc-img{ width:100%; height:100%; object-fit:cover; display:block; }
  .bc-img-empty{ height:100%; display:flex; align-items:center; justify-content:center; text-align:center;
    font-size:12px; color:var(--text-faint); cursor:pointer; padding:10px; line-height:1.6; }

  .bc-link{ display:block; text-decoration:none; height:100%; }
  .bc-link-title{ font-size:13px; font-weight:700; color:var(--text); margin-bottom:4px; word-break:break-word; }
  .bc-link-url{ font-size:11.5px; color:var(--accent); word-break:break-all; }

  /* The body is not a flex container by default, so `flex:1` on the fill did
     nothing and a swatch card rendered as a white rectangle with a hex code on
     it — the one card type whose entire job is to show a colour. */
  .bc-card.type-swatch .bc-card-body{ padding:0; display:flex; flex-direction:column; }
  .bc-swatch-fill{ flex:1; min-height:0; }
  .bc-swatch-foot{ padding:7px 10px; font-family:'IBM Plex Mono', monospace; font-size:11px;
    color:var(--text-dim); display:flex; justify-content:space-between; gap:8px; flex:none; }

  /* The two card types that make this Lucid's board rather than a clone. Both
     hold a reference, not a copy — see the note in the boards migration. */
  .bc-ref{ display:flex; flex-direction:column; gap:4px; height:100%; cursor:pointer; }
  .bc-ref-top{ display:flex; align-items:center; gap:8px; }
  .bc-ref-name{ font-size:13px; font-weight:700; color:var(--text); }
  .bc-ref-sub{ font-size:11.5px; color:var(--text-dim); line-height:1.5; word-break:break-word; }
  .bc-ref-open{ margin-top:auto; font-size:11px; color:var(--accent); font-weight:600; }
  .bc-ref-gone{ font-size:11.5px; color:var(--orange); line-height:1.5; }
  .bc-ref-badge{ font-size:9px; text-transform:uppercase; letter-spacing:.07em; color:var(--text-faint);
    border:1px solid var(--glass-border-hi); border-radius:4px; padding:1px 4px; flex:none; }

  .bc-colour-row{ display:flex; gap:5px; flex-wrap:wrap; padding:8px 10px 10px; }
  .bc-colour{ width:18px; height:18px; border-radius:5px; cursor:pointer; border:1px solid var(--glass-border-hi); }
  .bc-colour.active{ box-shadow:0 0 0 2px var(--accent); }

  @media(max-width:760px){
    .bc-tray{ width:auto; flex-direction:row; overflow-x:auto; padding:8px; }
    .bc-tray-title{ display:none; }
    .bc-stage{ flex-direction:column; }
    .bc-tool span:not(.bc-tool-ico){ display:none; }
  }

  .board-masonry{ column-count:3; column-gap:14px; max-width:1040px; }
  @media(max-width:1000px){ .board-masonry{ column-count:2; } }
  @media(max-width:600px){ .board-masonry{ column-count:1; } }
  .board-card{ break-inside:avoid; margin-bottom:14px; border-radius:14px; padding:14px; }
  .board-card.note{ border:1px solid var(--glass-border); }
  .board-card.image{ background:rgba(120,120,140,.05); border:1px solid var(--glass-border); }
  .board-card-head{ display:flex; justify-content:space-between; align-items:center; margin-bottom:9px; }
  .board-tag{ font-size:10px; text-transform:uppercase; letter-spacing:.05em; font-weight:700; }
  .board-card-del{ font-size:16px; color:var(--text-faint); cursor:pointer; opacity:0; transition:all var(--dur-fast) var(--ease-out); line-height:1; }
  .board-card:hover .board-card-del{ opacity:1; }
  .board-card-del:hover{ color:var(--text); }
  .board-card-text{ font-size:13px; line-height:1.55; white-space:pre-line; border-radius:6px; outline:none; }
  .board-card-text:focus{ box-shadow:0 0 0 1.5px color-mix(in srgb, var(--blue) 45%, transparent); background:rgba(120,120,140,.06); }
  .board-card-img{ width:100%; border-radius:10px; display:block; }
  .board-card-caption{ font-size:11.5px; color:var(--text-dim); margin-top:8px; }
  .icon-btn.has-reminder{ color:var(--blue); border-color:color-mix(in srgb, var(--blue) 50%, transparent); box-shadow:0 0 10px var(--blue-glow); }
  .btn.has-reminder{ color:var(--blue); border-color:color-mix(in srgb, var(--blue) 50%, transparent); box-shadow:0 0 10px var(--blue-glow); }
  .board-head-actions{ display:flex; align-items:center; gap:9px; }
  .board-bell{ cursor:pointer; color:var(--text-faint); opacity:0; transition:all var(--dur-fast) var(--ease-out); display:flex; }
  .board-card:hover .board-bell, .board-bell.active{ opacity:1; }
  .board-bell svg{ width:14px; height:14px; }
  .board-bell.active{ color:var(--blue); }
  .snooze-wrap{ position:relative; display:inline-block; }
  .snooze-menu{ display:none; position:absolute; bottom:calc(100% + 8px); left:0; width:230px; z-index:10; padding:6px; }
  .snooze-menu.open{ display:block; }
  .snooze-opt{ display:flex; justify-content:space-between; align-items:center; padding:9px 10px; border-radius:9px; font-size:12.5px; font-weight:600; cursor:pointer; }
  .snooze-opt:hover{ background:rgba(120,120,140,.1); }
  .snooze-opt.primary{ color:var(--blue); }
  .snooze-sub{ font-size:10.5px; color:var(--text-faint); font-weight:500; }
  .chip-select{ display:flex; flex-wrap:wrap; gap:6px; margin-bottom:14px; }

  /* ------------------------------------------------------------------
     C13: A CHIP ROW, FOLDED.
     "All chips need to fold into chevrons with dropdowns."

     The button is the closed state and the menu holds the chip row itself —
     see foldChipRow() in app.js, which moves the existing element rather than
     replacing it, so the swatches and the handlers are the originals.

     The menu is absolutely positioned on purpose: opening one must not push
     the fourteen controls under it down the card, because a settings pane that
     reflows every time you look at something is worse than the wall of chips
     this replaces. */
  .st-drop{ position:relative; margin-bottom:14px; }
  .st-drop-btn{
    width:100%; display:flex; align-items:center; justify-content:space-between; gap:10px;
    padding:9px 13px; border-radius:10px; border:1px solid var(--glass-border);
    background:rgba(120,120,140,.08); color:var(--text);
    font-family:'Public Sans'; font-size:13px; font-weight:600; text-align:left;
    cursor:pointer; transition:border-color .15s, background .15s;
  }
  .st-drop-btn:hover{ border-color:var(--glass-border-hi); background:rgba(120,120,140,.14); }
  /* Nothing chosen yet reads as a prompt, not as a value. */
  .st-drop.is-unset .st-drop-value{ color:var(--text-faint); font-weight:600; }
  .st-drop-value{ min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
  .st-drop-chev{ width:15px; height:15px; flex:none; color:var(--text-faint);
    transition:transform var(--dur-base) var(--ease-out); }
  .st-drop.open .st-drop-chev{ transform:rotate(180deg); }
  .st-drop-menu{
    display:none; position:absolute; top:calc(100% + 5px); left:0; right:0; z-index:22;
    background:var(--bg-mid); border:1px solid var(--glass-border-hi); border-radius:11px;
    box-shadow:0 18px 44px rgba(0,0,0,.45); padding:10px;
    max-height:min(52vh, 360px); overflow-y:auto; overscroll-behavior:contain;
  }
  .st-drop.open .st-drop-menu{ display:block; }
  /* The row inside a menu has no margin of its own to give — the menu's padding
     is the spacing now, and a trailing 14px would look like a mistake. */
  .st-drop-menu .chip-select{ margin-bottom:0; }
  /* One choice per line inside the menu. A wrapped grid of chips in a dropdown
     is the same reading problem in a smaller box. */
  .st-drop-menu .chip{ width:100%; text-align:left; }

  /* The two Serene axes. The swatches are painted from the live theme tokens,
     via the [data-paper]/[data-palette] rules further down, so a swatch cannot
     end up showing a colour the theme no longer uses — which is what happens
     every time a picker hard-codes its own hex values. */
  .serene-axis{ display:flex; align-items:center; gap:10px; margin-bottom:2px; }
  .serene-axis-label{ width:56px; flex:none; font-size:10.5px; text-transform:uppercase;
    letter-spacing:.08em; color:var(--text-faint); }
  .serene-axis .chip-select{ margin-bottom:8px; }
  .serene-sw{ width:13px; height:13px; border-radius:4px; display:inline-block; margin-right:7px;
    vertical-align:-2px; border:1px solid var(--glass-border-hi); }
  /* A palette swatch shows three of its six hues at once — one colour cannot
     say whether a palette is warm or cool, and the difference between these
     three palettes is exactly the relationship between the hues. */
  .serene-sw.sw-pal{ border:none; box-shadow:inset 0 0 0 1px var(--glass-border-hi); }
  /* Relationship score slider — thin track, small glowing thumb, color and
     fill driven entirely by --rel-color/--rel-glow/--rel-pct set inline by
     JS as the value changes. Native <input type="range"> needs full
     appearance resets per-browser to look like this instead of the stock
     OS slider. */
  .rel-slider-row{ display:flex; align-items:center; gap:12px; }
  .rel-slider{
    flex:1; -webkit-appearance:none; appearance:none; height:4px; border-radius:4px; outline:none; cursor:pointer;
    background:linear-gradient(90deg, var(--rel-color, var(--blue)) 0%, var(--rel-color, var(--blue)) var(--rel-pct, 50%), rgba(255,255,255,.12) var(--rel-pct, 50%), rgba(255,255,255,.12) 100%);
  }
  .rel-slider::-webkit-slider-thumb{
    -webkit-appearance:none; width:14px; height:14px; border-radius:50%; margin-top:0;
    background:var(--rel-color, var(--blue)); box-shadow:0 0 0 3px var(--bg-mid), 0 0 14px 3px var(--rel-glow, var(--blue-glow));
    cursor:pointer; transition:box-shadow .15s;
  }
  .rel-slider::-webkit-slider-thumb:hover{ box-shadow:0 0 0 3px var(--bg-mid), 0 0 20px 5px var(--rel-glow, var(--blue-glow)); }
  .rel-slider::-moz-range-track{ height:4px; border-radius:4px; background:rgba(255,255,255,.12); }
  .rel-slider::-moz-range-progress{ height:4px; border-radius:4px; background:var(--rel-color, var(--blue)); }
  .rel-slider::-moz-range-thumb{
    width:14px; height:14px; border-radius:50%; border:none;
    background:var(--rel-color, var(--blue)); box-shadow:0 0 14px 3px var(--rel-glow, var(--blue-glow));
    cursor:pointer;
  }
  /* The number takes the ordinary ink; the COLOUR of the relationship is
     carried by the slider track beside it, which is a shape rather than text.
     At 13px the accent measured 3.46:1 on dark — and a score you cannot read
     is worse than one with no colour on it. */
  .rel-slider-value{ font-family:'IBM Plex Mono',monospace; font-size:13px; font-weight:700; min-width:28px; text-align:right; color:var(--text); }
  .rel-slider-label{ font-size:11px; color:var(--text-faint); margin-top:8px; text-transform:uppercase; letter-spacing:.04em; font-weight:700; }
  .spine-toggle{ display:flex; justify-content:space-between; align-items:center; cursor:pointer; padding:2px 4px 2px 0; }
  .spine-toggle:hover{ color:var(--text-dim); }
  .spine-count{ background:rgba(120,120,140,.16); border-radius:20px; padding:1px 7px; font-size:9.5px; color:var(--text-dim); margin-left:4px; }
  .spine-chevron{ width:11px; height:11px; color:var(--text-faint); flex:none; }
  .week-strip{ display:grid; grid-template-columns:repeat(7,1fr); gap:8px; margin-bottom:22px; }
  .week-cell{ padding:11px 6px; border-radius:12px; text-align:center; cursor:pointer; border:1px solid var(--glass-border); transition:all var(--dur-fast) var(--ease-out); }
  .week-cell:hover{ border-color:var(--glass-border-hi); }
  .week-cell.today{ background:color-mix(in srgb, var(--blue) 16%, transparent); border-color:transparent; box-shadow:0 0 0 1px color-mix(in srgb, var(--blue) 40%, transparent); }
  .week-dow{ font-size:10px; text-transform:uppercase; color:var(--text-faint); margin-bottom:4px; letter-spacing:.05em; }
  .week-daynum{ font-size:17px; font-weight:700; }
  .week-dot{ width:4px; height:4px; border-radius:50%; background:var(--green); margin:6px auto 0; }
  /* Seven real columns, aligned under the seven headers above. It used to be a
     vertical list under a horizontal strip, which is why the header row and the
     content underneath looked like two unrelated widgets. */
  #weekDays{ display:grid; grid-template-columns:repeat(7,1fr); gap:8px; align-items:start; }
  .week-day-section{ min-width:0; padding:10px 8px 12px; border-radius:12px;
    background:rgba(120,120,140,.05); border:1px solid var(--glass-border); }
  .week-day-section.is-today{ border-color:color-mix(in srgb, var(--accent) 45%, transparent);
    background:color-mix(in srgb, var(--accent) 7%, transparent); }
  .week-day-section .week-event-row{ display:block; padding:6px 5px; }
  .week-day-section .week-event-time{ display:block; width:auto; margin-bottom:1px; }
  .week-day-section .kind-dot{ display:inline-block; margin-right:5px; }
  /* Below tablet width seven columns stop being readable — stack them. */
  @media(max-width:1100px){
    #weekDays{ grid-template-columns:repeat(2,1fr); }
  }
  @media(max-width:700px){
    #weekDays{ grid-template-columns:1fr; }
  }
  .week-day-head{ font-family:'Fraunces',serif; font-size:15px; font-weight:700; margin-bottom:9px; padding-bottom:7px; border-bottom:1px solid var(--glass-border); }
  .week-event-row{ display:flex; align-items:center; gap:10px; padding:8px 4px; font-size:13px; cursor:pointer; border-radius:9px; }
  .week-event-row:hover{ background:rgba(120,120,140,.07); }
  .week-event-time{ font-size:11.5px; color:var(--text-faint); width:64px; flex:none; }

  /* Search. Sits in the mail toolbar rather than in a global header, because
     it searches mail specifically — a single box that means different things
     depending on which page you are on is worse than an honest, scoped one. */
  .mail-search{ position:relative; display:flex; align-items:center; flex:0 1 300px; min-width:170px; }
  .mail-search-icon{ position:absolute; left:11px; width:15px; height:15px; color:var(--text-faint); pointer-events:none; }
  .mail-search-input{
    width:100%; padding:8px 30px 8px 33px; border-radius:20px;
    border:1px solid var(--glass-border); background:rgba(120,120,140,.08);
    color:var(--text); font-family:'Public Sans', sans-serif; font-size:13px;
    outline:none; transition:border-color var(--dur-fast) var(--ease-out), box-shadow var(--dur-fast) var(--ease-out);
  }
  .mail-search-input::placeholder{ color:var(--text-faint); }
  .mail-search-input::-webkit-search-cancel-button{ display:none; }
  .mail-search-input:focus{ border-color:var(--accent); box-shadow:0 0 0 3px var(--accent-glow); }
  .mail-search-clear{ position:absolute; right:10px; color:var(--text-faint); cursor:pointer; font-size:17px; line-height:1; display:none; }
  .mail-search.has-value .mail-search-clear{ display:block; }
  .search-status{ padding:10px 12px; font-size:12px; color:var(--text-faint); border-bottom:1px solid var(--glass-border); }
  .search-status b{ color:var(--text-dim); font-weight:600; }
  .search-status .search-warn{ color:var(--orange); }
  /* What the search actually covered. Quieter than the result count, because
     it is a caveat rather than the answer — but present, because a caveat
     nobody reads is still better than one nobody was given. */
  .search-status .search-sub{ margin-top:3px; font-size:11.5px; opacity:.85; }

  .shortcut-sheet{ position:fixed; inset:0; z-index:60; display:flex; align-items:center; justify-content:center;
    background:rgba(4,6,16,0.55); backdrop-filter:blur(4px); }
  .shortcut-card{ width:min(520px, calc(100vw - 40px)); padding:24px 26px; border-radius:16px; }
  .shortcut-grid{ display:grid; grid-template-columns:auto 1fr; gap:9px 18px; align-items:center; margin-top:14px; }
  .shortcut-grid kbd{ font-family:'IBM Plex Mono', monospace; font-size:11.5px; color:var(--text);
    background:rgba(120,120,140,.13); border:1px solid var(--glass-border-hi); border-radius:6px;
    padding:4px 9px; white-space:nowrap; text-align:center; }
  .shortcut-grid span{ font-size:13px; color:var(--text-dim); }

  /* MODAL / FAB */
  /* FIFTEEN FULL-SCREEN BLURS THAT NOBODY COULD SEE.
     Every modal backdrop in the app is in the DOM from the first paint,
     covering the whole viewport at opacity:0. They were each declaring
     backdrop-filter unconditionally, so the compositor was maintaining fifteen
     full-screen blur layers permanently — for dialogs that were closed.
     The blur now belongs to .open. Nothing looks different, because a blur
     behind an invisible element was never visible. */
  .modal-backdrop{ display:flex; position:fixed; inset:0; background:rgba(4,6,16,0.5); z-index:50; align-items:flex-end; justify-content:center;
    opacity:0; pointer-events:none; transition:opacity var(--dur-base) var(--ease-out), backdrop-filter var(--dur-base) var(--ease-out); }
  .modal-backdrop.open{ opacity:1; pointer-events:auto; backdrop-filter:blur(4px); -webkit-backdrop-filter:blur(4px); }
  /* WIDE AND SHORT, NOT NARROW AND TALL.
     Matt, 25 Aug: "Every card is too tall. Edit-a-contact especially. The
     prevailing theme is that cards are narrow and tall when they should be wide
     and short."
     He is describing one rule, not eleven bugs. Every popup in Lucid is a
     .compose-modal, and it was 720px wide with NO height limit — so a form with
     fourteen fields simply grew downwards until it ran off the screen, on a
     desktop with 700 unused pixels either side of it.
     Two changes fix all of them: more width, and a ceiling on height with the
     overflow scrolling inside the card rather than growing it. */
  /* A4: the notification card is a press. Only the cursor and a hover lift say
     so — a border or a colour would make twenty-four of them into a wall of
     buttons, which is the thing the collapsed tray already exists to avoid. */
  .sig.is-press{ cursor:pointer; }
  .sig.is-press:hover{ background:color-mix(in srgb, var(--accent) 9%, transparent); }
  /* The two controls inside it stay their own targets. */
  .sig .sig-act, .sig .sig-x{ position:relative; z-index:1; }

  /* C15: THE MEDIA GRID. A card per feed.
     Artwork-first, because artwork is what a person actually recognises — a
     podcast has a cover and a channel has a face, and a row of small grey words
     gave you neither. Sized so a shelf of eight fits without scrolling on a
     laptop and reflows to three on a phone. */
  .mf-grid{ display:grid; grid-template-columns:repeat(auto-fill, minmax(132px, 1fr)); gap:10px; }
  .mf-card{ position:relative; display:flex; flex-direction:column; align-items:center;
    gap:8px; padding:12px 10px 14px; border-radius:14px; cursor:pointer; text-align:center;
    border:1px solid var(--glass-border); background:transparent; color:var(--text-dim);
    font-family:inherit; transition:border-color var(--dur-fast) var(--ease-out),
    background var(--dur-fast) var(--ease-out), transform var(--dur-fast) var(--ease-out); }
  .mf-card:hover{ color:var(--text); transform:translateY(-2px);
    border-color:color-mix(in srgb, var(--accent) 45%, transparent);
    background:color-mix(in srgb, var(--accent) 10%, transparent); }
  .mf-art{ width:72px; height:72px; border-radius:12px; object-fit:cover; flex:none;
    background:rgba(120,120,140,.14); }
  /* The source's own initial rather than a generic icon: eight identical grey
     squares are as hard to tell apart as eight grey chips were. */
  .mf-art-none{ display:flex; align-items:center; justify-content:center;
    font-family:'Fraunces', Georgia, serif; font-size:26px; font-weight:700; color:var(--text); }
  .mf-name{ font-size:12.5px; font-weight:600; line-height:1.25; color:var(--text);
    display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden; }
  .mf-meta{ font-size:10.5px; color:var(--text-faint); }
  .mf-card.has-error{ border-color:color-mix(in srgb, var(--orange) 50%, transparent); }
  .mf-warn{ position:absolute; top:6px; right:8px; color:var(--orange); font-weight:700; }

  /* Quick launch. A grid rather than a row: a row of eight scrolls sideways,
     and sideways scroll on a dashboard tile is a thing nobody finds. */
  .ql-grid{ display:grid; grid-template-columns:repeat(auto-fill, minmax(104px, 1fr)); gap:8px; }
  .ql-item{ position:relative; display:flex; flex-direction:column; align-items:center;
    justify-content:center; gap:6px; padding:12px 8px; min-height:76px; border-radius:12px;
    border:1px solid var(--glass-border); text-decoration:none; color:var(--text-dim);
    cursor:pointer; transition:border-color var(--dur-fast) var(--ease-out),
    background var(--dur-fast) var(--ease-out); }
  .ql-item:hover{ color:var(--text); border-color:color-mix(in srgb, var(--accent) 45%, transparent);
    background:color-mix(in srgb, var(--accent) 10%, transparent); }
  .ql-mark{ width:26px; height:26px; border-radius:8px; display:flex; align-items:center;
    justify-content:center; font-weight:700; font-size:13px; color:var(--text);
    background:color-mix(in srgb, var(--accent) 22%, transparent); }
  .ql-name{ font-size:11.5px; font-weight:600; text-align:center; line-height:1.2;
    overflow:hidden; text-overflow:ellipsis; white-space:nowrap; max-width:100%; }
  .ql-add{ border-style:dashed; }
  /* The remove cross appears on hover only. Eight permanent crosses on a
     dashboard is eight invitations to delete something by accident. */
  .ql-x{ position:absolute; top:3px; right:5px; font-size:14px; line-height:1;
    color:var(--text-faint); opacity:0; transition:opacity var(--dur-fast) var(--ease-out); }
  .ql-item:hover .ql-x{ opacity:1; }
  .ql-x:hover{ color:var(--cat-red); }

  /* The brain dump. The box grows to the tile and the buttons sit under it. */
  .ht-dump{ display:flex; flex-direction:column; gap:8px; height:100%; }
  .ht-dump .ht-scratch{ flex:1; min-height:70px; }
  .ht-dump-row{ display:flex; flex-wrap:wrap; gap:6px; }

  /* A line for today. Serif and centred, because it is the one thing on the
     page that is not information — it should not look like a row of data. */
  .ht-quote{ display:flex; flex-direction:column; align-items:center; justify-content:center;
    gap:6px; text-align:center; height:100%; padding:4px 10px; }
  .ht-quote-text{ font-family:'Fraunces', Georgia, serif; font-size:15px; line-height:1.45;
    color:var(--text); }
  .ht-quote-who{ font-size:11px; letter-spacing:.06em; text-transform:uppercase;
    color:var(--text-faint); }

  /* A6: THE LOGO TOOK ITS COLOUR FROM THE ORB, AND STOPPED.
     The mark is three brand arcs around a lit core. The arcs are the brand and
     stay fixed; the CORE is the orb, and when the orb became configurable the
     core kept the blue it was born with — so changing the accent left a blue
     dot sitting in the middle of a green app.
     Done by setting the GRADIENT STOPS rather than the fill. `fill` on an SVG
     shape does not accept a CSS gradient in any shipping browser — it needs a
     paint server — but `stop-color` is an ordinary CSS property on a <stop>,
     so the gradient stays exactly where it is and only its colours move. */
  .brand-mark .lm-core-in{  stop-color:color-mix(in srgb, var(--accent) 25%, #fff); }
  .brand-mark .lm-core-mid{ stop-color:var(--accent); }
  .brand-mark .lm-core-out{ stop-color:color-mix(in srgb, var(--accent) 70%, #000); }
  .brand-mark .lm-pool-in{  stop-color:var(--accent); stop-opacity:0.42; }
  .brand-mark .lm-pool-mid{ stop-color:var(--accent); stop-opacity:0.13; }
  .brand-mark .lm-pool-out{ stop-color:var(--accent); stop-opacity:0; }

  .compose-modal{ width:100%; max-width:860px; padding:26px; border-radius:16px 16px 0 0;
    max-height:min(88vh, 860px); overflow-y:auto; overscroll-behavior:contain;
    transform:translateY(18px) scale(.97); transition:transform .25s cubic-bezier(.2,.8,.3,1); }
  .modal-backdrop.open .compose-modal{ transform:none; }
  @media(min-width:781px){ .modal-backdrop{ align-items:center; } .compose-modal{ border-radius:16px; margin-bottom:6vh; } }

  /* ============================================================
     A POPUP IS FOR READING. AN AMBIENT PANEL IS FOR LOOKING AT.
     ------------------------------------------------------------
     "All of the popups are a cool frosted look, but when white/grey it is
     impossible to read."

     The two are not the same job and they should not have the same fill. A
     panel in the page sits on the app's own background: at 55% opacity it is
     55% white over more cream, which is still cream. A dialog sits above the
     WHOLE PAGE with a dimming scrim between — so the same 55% white was 55%
     white over a darkened, blurred inbox. That mixture is the grey, and no
     text colour survives being asked to sit on a background that changes
     depending on what happens to be scrolled behind it.

     Two changes, both light-mode only, because the dark themes are the ones
     that work and are the ones he uses:
       1. the scrim stops being near-black. Dimming a cream page toward black
          is what dragged the card grey in the first place;
       2. the card itself becomes almost opaque. The frost is still there in
          the blur, the sheen and the border — it is just no longer allowed to
          decide what colour the paper is.
     ============================================================ */
  :root[data-mode="light"] .modal-backdrop{ background:rgba(62,50,38,0.30); }
  :root[data-mode="light"] .modal-backdrop .glass{
    background:var(--glass-sheen), color-mix(in srgb, var(--bg-mid) 96%, transparent);
  }
  /* Same reasoning, same problem: a menu floating over the page. These already
     use an opaque --bg-mid, but the ones built on .glass did not. */
  :root[data-mode="light"] .from-menu,
  :root[data-mode="light"] .acct-switcher-menu,
  :root[data-mode="light"] .more-menu,
  :root[data-mode="light"] .global-fab-menu{
    background:color-mix(in srgb, var(--bg-mid) 97%, transparent);
    box-shadow:0 16px 40px rgba(92,72,48,.22);
  }

  /* Settings was 939px tall against a 700px window — cut off at BOTH ends, with
     max-height:none and overflow hidden, so there was nothing to scroll and no
     way to reach the bottom half. Measured, not guessed.
     Two fixes, and they are different problems: the card now has a height
     ceiling with its own scroll region (so it can never exceed the window), and
     the General pane runs in two columns (so on any normal screen it doesn't
     need to scroll at all). Declared after .glass, which sets overflow and
     would otherwise win the cascade. */
  .compose-modal.settings-modal{
    max-width:900px; margin-bottom:0;
    max-height:min(86vh, 780px);
    display:flex; flex-direction:column;
    padding-bottom:20px; overflow:hidden;
  }
  .settings-body{
    flex:1; min-height:0; overflow-y:auto; overflow-x:hidden;
    /* Pull the scrollbar to the card edge without the content hugging it. */
    padding-right:10px; margin-right:-10px;
    scrollbar-width:thin;
  }
  .settings-body::-webkit-scrollbar{ width:8px; }
  .settings-body::-webkit-scrollbar-thumb{ background:rgba(255,255,255,0.10); border-radius:4px; }
  .settings-body::-webkit-scrollbar-thumb:hover{ background:rgba(255,255,255,0.18); }
  .settings-cols{ display:grid; grid-template-columns:1fr 1fr; gap:0 34px; align-items:start; }
  /* A column must not be able to push the grid wider than the card — long
     account emails and signatures will otherwise do exactly that. */
  .settings-cols > *{ min-width:0; }
  .settings-col-sep{ border-left:1px solid var(--glass-border); padding-left:34px; margin-left:0; }
  @media(max-width:900px){
    .settings-cols{ grid-template-columns:1fr; }
    .settings-col-sep{ border-left:none; padding-left:0; }
  }
  /* From-selector: single button + dropdown instead of every account shown
     as its own chip — saves space now that there are 3+ accounts. */
  .from-selector{ position:relative; margin-bottom:12px; }
  .from-btn{ width:100%; text-align:left; padding:11px 16px; border-radius:10px; border:1px solid var(--glass-border); background:rgba(120,120,140,.08); color:var(--text); font-family:'Public Sans'; font-size:13.5px; font-weight:600; cursor:pointer; display:flex; align-items:center; justify-content:space-between; transition:box-shadow .15s; }
  :root:not([data-mode="light"]) .from-btn{ box-shadow:0 0 0 1px color-mix(in srgb, var(--accent) 40%, transparent), 0 0 18px 0 var(--accent-glow); }
  :root:not([data-mode="light"]) .rp-close-btn{ box-shadow:0 0 0 1px color-mix(in srgb, var(--accent) 40%, transparent), 0 0 18px 0 var(--accent-glow); transition:box-shadow .15s; }
  :root:not([data-mode="light"]) .rp-close-btn:hover{ box-shadow:0 0 0 1px color-mix(in srgb, var(--accent) 55%, transparent), 0 0 26px 2px var(--accent-glow); }
  :root:not([data-mode="light"]) .from-btn:hover{ box-shadow:0 0 0 1px color-mix(in srgb, var(--accent) 55%, transparent), 0 0 26px 2px var(--accent-glow); }
  .send-split{ display:flex; position:relative; }
  .send-split .btn{ border-radius:0; }
  .send-split .send-main{ border-radius:10px 0 0 10px; }
  .send-split .send-chevron{ border-radius:0 10px 10px 0; padding:11px 12px; }
  .send-split .send-main.tinted{ border-right:1px solid color-mix(in srgb, var(--accent) 30%, transparent); }
  .from-btn::after{ content:'⌄'; color:var(--text-faint); font-size:13px; margin-left:8px; }
  .from-menu{ position:absolute; top:calc(100% + 4px); left:0; right:auto; min-width:230px; background:var(--bg-mid); border:1px solid var(--glass-border-hi); border-radius:10px; box-shadow:0 16px 40px rgba(0,0,0,.5); z-index:20; display:none; overflow:hidden; }
  #inlineSendMenu, #composeMoreMenu{ left:auto; right:0; } /* menus anchored to a right-aligned trigger open leftward, not off-screen to the right */
  .from-menu.open{ display:block; }
  .from-menu .chip{ display:block; border-radius:0; border:none; border-bottom:1px solid var(--glass-border); padding:10px 14px; }
  .from-menu .chip:last-child{ border-bottom:none; }
  .from-menu .chip.active{ background:color-mix(in srgb, var(--accent) 14%, transparent); }
  .more-item{ display:flex; align-items:center; justify-content:space-between; gap:14px; padding:10px 14px; font-size:13px; font-weight:600; cursor:pointer; border-bottom:1px solid var(--glass-border); white-space:nowrap; }
  .more-item-label{ display:flex; align-items:center; gap:9px; }
  .more-item-icon{ width:15px; height:15px; flex:none; color:var(--text-dim); }
  .more-item:last-child{ border-bottom:none; }
  .more-item:hover{ background:rgba(120,120,140,.10); }
  .more-item-badge{ font-size:10px; font-weight:600; color:var(--text-faint); text-transform:uppercase; letter-spacing:.03em; }
  .compose-extra-row{ display:flex; gap:8px; align-items:center; padding:10px 12px; margin-bottom:10px; border-radius:10px; background:rgba(120,120,140,.06); border:1px solid var(--glass-border); font-size:12.5px; }
  .compose-extra-row input[type="date"], .compose-extra-row input[type="time"]{ background:rgba(0,0,0,.2); border:1px solid var(--glass-border); border-radius:7px; color:var(--text); padding:5px 8px; font-family:inherit; font-size:12px; }
  .quoted-trail{ max-height:44px; overflow:hidden; border-radius:10px; border:1px solid var(--glass-border); background:rgba(0,0,0,.15); padding:10px 12px; font-size:12px; color:var(--text-dim); margin-bottom:10px; transition:max-height var(--dur-base) var(--ease-out); }
  .quoted-trail.open{ max-height:260px; overflow-y:auto; }
  /* Wide, not tall — the create modal has too many fields to read well as one
     long vertical scroll on a desktop screen with room to spare. Two columns
     side by side instead; collapses back to one column on narrow screens. */
  /* AND WIDER STILL ON A REAL MONITOR.
   *
   * Matt, 25 Aug: "New event card is too tall. Make it wider." Then the general
   * form: "Prevailing theme is all the cards are too tall. Make them all wider
   * to fit screen."
   *
   * An 820px card on a 2,560px screen leaves seventeen hundred pixels of dimmed
   * inbox either side while the card itself runs off the bottom. That width was
   * chosen when two columns was all there was to put in it.
   *
   * The card grows in steps and the grid grows with it, because extra width is
   * only worth having when it turns into fewer rows. The three-column rule is
   * written as a rule about the CHILDREN — a card that has a third .cm-col gets
   * a third column, and one that does not keeps two rather than growing an
   * empty stripe. */
  .cm-wide{ max-width:780px; }
  .cm-grid{ display:grid; grid-template-columns:1fr; gap:0 28px; }
  /* The second column arrives at 700px rather than 781px. Between those two
     numbers sits a very common window width where the card had room for two
     columns and used one, which is precisely how it got tall. */
  @media(min-width:700px){ .cm-wide{ max-width:1000px; } .cm-grid{ grid-template-columns:1fr 1fr; } }
  @media(min-width:1180px){
    .cm-wide{ max-width:min(1120px, calc(100vw - 96px)); }
    .cm-grid:has(> .cm-col:nth-child(3)){ grid-template-columns:repeat(3, 1fr); }
  }
  .cm-col{ min-width:0; }
  /* ------------------------------------------------------------------
     THE WHOLE WIDTH SCALE, IN ONE PLACE.

     Matt, 25 Aug: "Fix all the cards (not just these) to be wide like in other
     areas. (should be site wide for consistency)."

     Consistency is the operative word. Before this there were eleven widths in
     the app and seven of them were written inline in index.html — 560, 440,
     440, 560, 620, 360, 470 — and an inline style beats every rule in this
     stylesheet, which is why the two previous card-width passes changed
     nothing on those seven. The inline values are gone; every card now wears
     one of three classes and the numbers live here.

       .cm-narrow  a question with one answer  — confirm, one-field prompts
       .cm-mid     a short form                — three or four fields
       .cm-wide    anything with real content  — pairs with .cm-grid

     A card only gets .cm-wide if it has enough in it to fill two columns. A
     three-field dialog stretched to 1,120px is not "wide and short", it is a
     letterbox with three fields floating in it, so the scale has a middle. */
  .cm-narrow{ max-width:470px; }
  .cm-mid{ max-width:620px; }
  @media(min-width:700px){ .cm-mid{ max-width:720px; } }
  /* Native date/time inputs have a hard intrinsic width; min-width:0 is what
     actually lets them share a row without overflowing the column. */
  .ev-time-row{ display:flex; align-items:center; gap:8px; margin-bottom:12px; }
  .ev-time-row .field{ flex:1 1 0; min-width:0; }
  .ev-time-dash{ flex:none; font-size:12px; color:var(--text-faint); }
  .modal-title{ font-family:'Fraunces', serif; font-size:19px; font-weight:700; margin-bottom:14px; display:flex; justify-content:space-between; }
  @keyframes itemFade{ from{opacity:0; transform:translateY(4px);} to{opacity:1; transform:none;} }
  /* The single worst offender in the "everything pops" complaint.
     This entrance animation used to apply to every list item unconditionally.
     Lists are rebuilt with innerHTML on every poll (mail 60s, read receipts
     15s, tasks 5min), so the ENTIRE inbox re-ran its fade-in once a minute
     while Matt was just sitting there reading it — a full-list flash, entirely
     unprompted. Now it only runs while a view is actually being entered
     (.entering is added by switchView and removed when it finishes), so
     background refreshes are silent, and arriving at a view gets a soft
     cascade instead of every row firing on the same frame. */
  /* The staggered row cascade lived here and has been removed deliberately.
     Measured first: nothing in the shell actually moves during a view switch —
     sidebar width, main offset and scrollbar width are identical before,
     during and after. So the "herky-jerky" was never a layout shift; it was
     this animation. Eight rows fading in at 18ms intervals, each over 220ms,
     meant the page kept ARRIVING for about half a second after the click. And
     because `.entering` stayed on the view for 500ms, any re-render inside that
     window (a mail poll, say) restarted the whole cascade and flickered rows
     that were already on screen.

     What replaces it is nothing: the outgoing view dissolves underneath and the
     incoming one is simply there, at full opacity, on the first frame. A page
     that is instantly present reads as faster and calmer than one that
     performs its own arrival. */
  .modal-x{ cursor:pointer; color:var(--text-dim); font-size:20px; line-height:1; }
  .field-row{ display:flex; gap:8px; }
  .field-row .field{ flex:1; }
  /* Was a fixed 520px with no height limit, so a meeting with a long invitee
     list ran straight off the bottom of the screen with no way to scroll.
     Wider, capped, and scrolling inside — same treatment as Settings. */
  /* A7: "New-event card is too tall. Make it wider." Same fix, and it is the
     card that shows the problem most clearly — it already had the height
     ceiling, so all its content did was squeeze into a narrow column and
     scroll. Width was the missing half. */
  .event-modal{ max-width:820px; padding:26px; border-top-width:4px; border-top-style:solid;
    max-height:min(86vh, 760px); display:flex; flex-direction:column; overflow:hidden; }
  .event-modal > *{ flex:none; }
  /* .em-body is the general form of the two ids below it.
     .event-modal is `overflow:hidden` with `> * { flex:none }`, so a child that
     is not named here cannot scroll AND cannot grow — it is simply clipped at
     the card edge with no way to reach the rest. That is what was happening to
     Needs Attention, which wears .event-modal for its coloured top border and
     had no scroll region of its own: with more than about eight items the list
     just stopped, mid-row, with nothing to drag. Any card in this family now
     marks its scrolling region with .em-body and the two ids stay as they were
     so nothing that already worked has to change. */
  .event-modal .em-body,
  .event-modal #eventDetailContent, .event-modal #taskDetailContent{
    flex:1; min-height:0; overflow-y:auto; overflow-x:hidden;
    padding-right:8px; margin-right:-8px; scrollbar-width:thin;
  }
  /* A wide card in this family needs the wide ceiling too — .event-modal's own
     820px would otherwise beat .cm-wide on source order. */
  .event-modal.cm-wide{ max-width:1000px; }
  @media(min-width:1180px){ .event-modal.cm-wide{ max-width:min(1120px, calc(100vw - 96px)); } }
  .event-modal ::-webkit-scrollbar{ width:8px; }
  .event-modal ::-webkit-scrollbar-thumb{ background:rgba(255,255,255,0.10); border-radius:4px; }
  .ed-head{ display:flex; align-items:center; gap:10px; margin-bottom:6px; }
  .ed-dot{ width:12px; height:12px; border-radius:50%; flex:none; }
  .ed-title{ font-family:'Fraunces',serif; font-size:22px; font-weight:700; flex:1; }
  .ed-time{ font-size:14px; color:var(--text-dim); margin-bottom:18px; font-family:'IBM Plex Mono',monospace; }
  .ed-rows{ margin-bottom:16px; }
  .ed-row{ display:flex; justify-content:space-between; align-items:center; padding:9px 0; border-bottom:1px solid var(--glass-border); font-size:13.5px; }
  .ed-row:last-child{ border:none; }
  .ed-label{ color:var(--text-faint); font-size:11px; text-transform:uppercase; letter-spacing:.05em; }
  .ed-swatch{ width:9px; height:9px; border-radius:50%; display:inline-block; margin-right:7px; }
  .ed-muted{ color:var(--text-faint); }
  .teams-btn{ width:100%; margin-bottom:16px; background:color-mix(in srgb, #5B5FC7 22%, transparent); border-color:color-mix(in srgb,#5B5FC7 48%, transparent); color:var(--text); font-weight:700; }
  .teams-btn:hover{ background:color-mix(in srgb, #5B5FC7 32%, transparent); }
  .ed-invitee{ display:flex; align-items:center; gap:10px; padding:8px 0; font-size:13.5px; cursor:pointer; }
  .ed-invitee:hover{ text-decoration:underline; }
  .global-fab-wrap{ position:fixed; bottom:26px; left:26px; z-index:998; }
  /* Moved into the spine. It keeps its look — same round, glowing, accent
     button — but it now sits in flow at the top of the rail instead of
     floating over the sidebar and the reading pane. Event / Compose /
     Reminder were removed from the rail because this menu already contains
     all three; having both was the same action in two places. */
  .global-fab-wrap.in-spine{ position:static; z-index:auto; display:flex; justify-content:center; margin:0 0 14px; }
  .sidebar:not(.collapsed) .global-fab-wrap.in-spine{ justify-content:flex-start; }
  .global-fab-wrap.in-spine .global-fab{
    width:48px; height:48px;
    /* Deliberately NOT the accent colour. Every nav item glows blue on hover
       and active; if create glowed blue too it would read as a seventh
       destination instead of the one thing that makes something. Violet/teal
       marks it as a different KIND of control. */
    background:color-mix(in srgb, var(--violet) 22%, rgba(18,18,32,.72));
    border-color:color-mix(in srgb, var(--violet) 55%, transparent);
    color:#E8DEFF;
    box-shadow:0 8px 26px -4px rgba(0,0,0,.55), 0 0 26px 1px var(--violet-glow), 0 0 46px -6px var(--teal-glow);
  }
  .global-fab-wrap.in-spine .global-fab svg{ width:24px; height:24px; }
  .global-fab-wrap.in-spine .global-fab:hover{
    transform:scale(1.07);
    box-shadow:0 10px 32px -4px rgba(0,0,0,.6), 0 0 40px 3px var(--violet-glow), 0 0 66px -4px var(--teal-glow);
  }
  /* Opens downward now that the button is at the top, not the bottom. */
  /* The menu is 170px wide and the collapsed rail is 70px with
     overflow-x:hidden, so an absolutely-positioned menu was simply clipped
     out of existence — and because .in-spine is position:static it also
     resolved its coordinates against the wrong ancestor and landed off the
     bottom of the screen. Fixed positioning takes it out of the sidebar's
     clipping context entirely; toggleGlobalFab sets the coordinates. */
  /* The menu cannot live inside the sidebar at all. .sidebar carries .glass,
     which sets BOTH backdrop-filter and overflow:hidden — and backdrop-filter
     makes an element a containing block for position:fixed descendants. So a
     "fixed" menu was still being positioned against the sidebar and still
     clipped by it. The only reliable fix is to move the element out of the
     sidebar in the DOM; it is re-parented to <body> on first open and
     positioned in viewport coordinates from then on. */
  body > .global-fab-menu{ position:fixed; top:auto; bottom:auto; left:auto; right:auto; z-index:999; }
  .global-fab{ width:52px; height:52px; border-radius:50%; border:1px solid color-mix(in srgb, var(--blue) 45%, transparent); cursor:pointer; display:flex; align-items:center; justify-content:center; background:color-mix(in srgb, var(--blue) 20%, rgba(18,18,32,.7)); backdrop-filter:blur(10px); color:var(--blue-2); box-shadow:0 8px 26px -4px rgba(0,0,0,.55), 0 0 28px 2px var(--blue-glow); transition:box-shadow .15s, transform .15s; }
  .global-fab:hover{ transform:scale(1.08); box-shadow:0 10px 30px -4px rgba(0,0,0,.6), 0 0 42px 4px var(--blue-glow); }
  .global-fab svg{ width:22px; height:22px; }
  .global-fab-menu{ top:auto; bottom:calc(100% + 8px); left:0; right:auto; min-width:170px; }

  /* THE TWO PLACES A DARK VALUE WAS HARD-CODED.
     Both of these mix their colour into rgba(18,18,32,…) — a literal, written
     when there was only ever a dark room to sit in. On cream they are navy
     blobs: the create button reads as a hole in the page, and it is the first
     thing the eye lands on in the rail.
     The mix here is the same idea against the light ground: the accent, held
     at a tint that keeps the glyph readable, over the card surface rather than
     over an invented dark. */
  :root[data-mode="light"] .global-fab{
    background:color-mix(in srgb, var(--blue) 16%, rgba(255,255,255,.92));
    border-color:color-mix(in srgb, var(--blue) 40%, transparent);
    color:var(--blue);
    box-shadow:0 4px 14px -4px rgba(80,64,48,.28), 0 1px 3px rgba(80,64,48,.16);
  }
  :root[data-mode="light"] .global-fab:hover{
    transform:scale(1.08);
    box-shadow:0 8px 22px -4px rgba(80,64,48,.32), 0 2px 5px rgba(80,64,48,.18);
  }
  :root[data-mode="light"] .global-fab-wrap.in-spine .global-fab{
    background:color-mix(in srgb, var(--violet) 16%, rgba(255,255,255,.92));
    border-color:color-mix(in srgb, var(--violet) 42%, transparent);
    color:var(--violet);
  }

  /* A LETTER ON THE DESK.
     Email HTML is authored for a white page and Lucid shows it as the sender
     made it — that is right, and it is why light mode does not run the
     darkening pass. But a raw white rectangle butted against cream is a hole,
     not a message. Framing it as a card with a warm border and a radius keeps
     the sender's design untouched while making it read as a letter sitting on
     the page rather than a gap in it. */
  :root[data-mode="light"] #rpBodyContainer{
    background:#FFFDFA;
    border:1px solid var(--glass-border);
    border-radius:12px;
    padding:16px 18px;
    box-shadow:0 1px 2px rgba(92,72,48,.05), 0 6px 16px -8px rgba(92,72,48,.10);
    overflow:hidden;
  }
  :root[data-mode="light"] #rpBodyContainer .rp-body{ margin:0; }

  /* CHAT ASSISTANT */
  .orb-ctl{ display:flex; align-items:center; gap:12px; margin-bottom:8px; }
  .orb-ctl-lab{ font-size:12.5px; color:var(--text-dim); width:118px; flex:none; }
  .orb-ctl input[type=range]{ flex:1; accent-color:var(--accent); }
  /* The chat window prefers the orb-derived tint and falls back to the app
     accent, so turning "chat follows orb" off needs no other code path — the
     variables simply stop existing. */
  .chat-card{ border-top-color: var(--orb-accent, var(--blue)) !important;
    box-shadow: 0 24px 60px -18px rgba(0,0,0,.8), 0 0 44px -14px var(--orb-glow, var(--blue-glow)); }
  /* Real class names, verified against the markup rather than assumed. */
  .msg-user{ background:linear-gradient(135deg, var(--orb-accent, var(--blue)), var(--orb-accent-2, var(--blue-2))) !important;
    color:var(--orb-ink, var(--ink)) !important; }
  .chat-chip{ border-color:color-mix(in srgb, var(--orb-accent, var(--blue)) 40%, transparent) !important; }
  .chat-empty-icon{ color:var(--orb-accent-2, var(--blue-2)) !important; }
  .chat-send, .chat-card .btn.primary{ background:linear-gradient(135deg, var(--orb-accent, var(--blue)), var(--orb-accent-2, var(--blue-2))) !important;
    color:var(--orb-ink, var(--ink)) !important; }
  .chat-input:focus{ border-color:color-mix(in srgb, var(--orb-accent, var(--blue)) 55%, transparent) !important;
    box-shadow:0 0 14px -3px var(--orb-glow, var(--blue-glow)) !important; }
  .chat-pill-aurora{ background:radial-gradient(closest-side, var(--orb-glow, rgba(160,125,255,.34)), transparent 78%) !important; }
  .chat-pill{
    position:fixed; bottom:26px; right:26px; z-index:20;
    width:88px; height:88px; border-radius:50%;
    display:flex; align-items:center; justify-content:center; cursor:pointer;
    background:transparent; border:none; box-shadow:none; padding:0;
    transition: transform var(--dur-base) var(--ease-spring), filter var(--dur-base) var(--ease-out);
    filter: drop-shadow(0 10px 26px rgba(0,0,0,.55)) drop-shadow(0 0 26px rgba(160,125,255,.32));
  }
  /* A DIFFUSE OBJECT CANNOT WEAR A SOLID OBJECT'S SHADOW.
     drop-shadow follows the alpha of what it is shadowing, so an offset one
     under a vortex draws a second, darker vortex ten pixels below it — which
     reads as a dark ball with the disc sitting on top. Symmetric and tighter
     when the vortex is up; the original when it is a sphere. */
  /* None at all, in the end. A drop-shadow follows the ALPHA of what it is
     shadowing, and a vortex is mostly a wide field of very low alpha — so even
     a symmetric shadow renders as a dark disc the size of the whole canvas
     with the bright part floating in the middle of it. The vortex carries its
     own glow; it does not need a shadow underneath it. */
  .chat-pill.pill-vortex,
  .chat-pill.pill-vortex:hover{ filter:none; }
  .chat-pill:hover{ transform:scale(1.06); filter: drop-shadow(0 14px 32px rgba(0,0,0,.6)) drop-shadow(0 0 40px rgba(160,125,255,.5)); }
  .chat-pill:active{ transform:scale(.97); }

  /* THE ORB ON PAPER.
     The brief called this the make-or-break question for Serene, and it is:
     the orb is an ADDITIVE light model — the shader starts at black and adds
     energy — so it is a dark object by construction, and it is Lucid's
     identity. It cannot simply be dropped onto cream.
     What breaks on a light ground is not the sphere. It is the treatment
     around it: a violet halo, which on cream reads as a smudge, and a heavy
     black drop-shadow, which reads as dirt. Both exist to separate a glowing
     object from a dark room, and neither has a job here.
     Replaced with what a physical object on paper actually has — one soft
     warm cast shadow, offset downward, tinted toward the ground's own colour.
     The sphere then reads as an iridescent marble resting on the page, which
     is a better answer than a dark blob and a better one than washing the orb
     out until it disappears. See also the uLight uniform in the shader, which
     lifts the sphere itself toward pearl rather than bubble. */
  /* LUCID HAS SOMETHING TO SAY.
     One dot, on the orb, and nothing else — no panel, no sound, no badge with a
     number in it. The smallest mark that can mean "there is something" without
     taking the screen away from what he was doing. It appears at most once a
     day and only when the thing is genuinely urgent; on a quiet day it never
     appears at all, which is the feature working rather than failing. */
  .chat-pill.has-word::after{
    content:''; position:absolute; top:12px; right:12px; width:11px; height:11px;
    border-radius:50%; background:var(--accent);
    box-shadow:0 0 0 2px var(--bg-mid), 0 0 12px 2px var(--accent-glow);
    animation: wordPulse 2.6s var(--ease-out) infinite;
  }
  @keyframes wordPulse{
    0%, 100%{ transform:scale(1); opacity:1; }
    50%{ transform:scale(1.18); opacity:.82; }
  }
  @media (prefers-reduced-motion: reduce){
    .chat-pill.has-word::after{ animation:none; }
  }
  /* WHAT LUCID SAYS FIRST.
     One line, above the window rather than instead of it, marked with the
     accent rule the Home lead uses so it reads as the app speaking rather than
     as another row in a list. */
  .ask-first{ border-left:2px solid var(--accent); padding:2px 0 2px 10px; margin:0 0 14px; }
  .ask-first-line{ font-size:13.5px; color:var(--text); line-height:1.5; }
  .ask-first-acts{ display:flex; gap:8px; margin:8px 0 0; }

  :root[data-mode="light"] .chat-pill{
    filter: drop-shadow(var(--orb-shadow-1, 0 8px 20px rgba(92,72,48,0.20)));
    box-shadow:none;
  }
  :root[data-mode="light"] .chat-pill:hover{
    filter: drop-shadow(var(--orb-shadow-2, 0 12px 26px rgba(92,72,48,0.26)));
  }
  /* The halo becomes ambient occlusion instead of a glow: a soft darkening
     UNDER the sphere rather than a coloured bloom around it. */
  :root[data-mode="light"] .chat-pill-aurora{
    inset:-10px -10px -14px -10px;
    background:radial-gradient(closest-side at 50% 62%, var(--orb-halo, rgba(150,130,110,0.20)), transparent 72%) !important;
    filter:blur(10px);
  }
  /* ==========================================================================
     THE ORB.
     --------------------------------------------------------------------------
     Was a live WebGL sphere. Is now four gradients and two keyframes, and the
     reason is the whole of the presence bench:

       · The sphere cost enough that reduced-effects mode had to freeze it,
         which is what "Lucid doesn't morph any more" actually was — not a bug,
         a deliberate stop that looked like one.
       · It could fail. WebGL is unavailable often enough that there was a
         fallback gradient, a .no-webgl class on three elements, and a whole
         second appearance nobody ever designed properly.
       · It could not be small. A real lit sphere is unreadable below about
         32px, so the 30px chat-header copy was a smudge.

     THE RULE THAT REPLACED IT: the edge is a function of size.

     At 104px there is room for the glow to be the whole object, so the border
     is at zero opacity and it is a pure halo. At 30px there is no room for a
     bloom — the spread would be wider than the icon and would fog the text
     beside it — so the border comes up to full and it is a defined rim. In
     between it slides. Same breath, same timing, same colour, so it never
     reads as two different objects; see orbEdgeFor().

     On light paper the edge takes over regardless of size, because a glow
     needs dark to be a glow and a bloom on cream is a smudge.

     Everything here animates opacity and transform only, so it stays on the
     compositor and off the main thread. It costs the same whether it is
     visible or not, which is why it no longer has to be frozen to be cheap.
     ========================================================================== */
  .lorb{ position:relative; width:var(--d,88px); height:var(--d,88px); border-radius:50%;
         flex:none; display:block; z-index:2; }
  .lorb > span{ position:absolute; inset:0; border-radius:50%; display:block; }

  /* THE VORTEX SITS OVER THE ORB, NOT INSTEAD OF IT.
     The canvas is a child of the same .lorb element, so everything that
     already knows where the orb is — the FAB, the chat header, the login
     card — needs no changes at all. It is bigger than its host because a
     tilted disc is wider than the sphere it replaced, and centred on it. */
  .lorb > canvas.lorb-vx{
    position:absolute; left:50%; top:50%; transform:translate(-50%,-50%);
    pointer-events:none; display:block;
  }
  /* When the vortex is up, the ball underneath it goes. Only the halo stays,
     which gives the disc a ground to sit on rather than floating in nothing. */
  .lorb.has-vortex > .lorb-body,
  .lorb.has-vortex > .lorb-ring,
  .lorb.has-vortex > .lorb-arc,
  .lorb.has-vortex > .lorb-ping{ display:none; }
  /* No halo either. The halo is a blurred disc of the band colour, and behind
     a vortex it reads as a hard-edged glowing sphere with the disc trapped
     inside it — the exact ball this replaced. The vortex makes its own light. */
  .lorb.has-vortex > .lorb-halo{ display:none; }
  /* The body makes it an OBJECT rather than a patch of light — a dark sphere
     lit from the upper left, which is what survives on a pale background even
     before the rim arrives. */
  .lorb-body{
    background:
      radial-gradient(circle at 34% 28%,
        color-mix(in srgb, var(--orb-accent, #9A7BE0) 62%, transparent), transparent 60%),
      radial-gradient(circle at 62% 74%,
        color-mix(in srgb, var(--orb-accent-2, #C6B4F0) 26%, transparent), transparent 52%),
      radial-gradient(circle at 50% 50%, #1B2030, #0B0E16 72%);
  }
  .lorb-halo{
    box-shadow: 0 0 calc(var(--d,88px) * .22) calc(var(--d,88px) * .05) var(--orb-accent, #9A7BE0);
    animation: lorbHalo calc(6.2s / var(--orb-speed, 1)) ease-in-out infinite;
  }
  .lorb-ring{
    border: max(1px, calc(var(--d,88px) * .013)) solid
      color-mix(in srgb, var(--orb-line, var(--orb-accent, #9A7BE0)) calc(var(--edge, 0) * 100%), transparent);
    box-shadow: inset 0 0 calc(var(--d,88px) * .14) 0
      color-mix(in srgb, var(--orb-line, var(--orb-accent, #9A7BE0)) calc(var(--edge, 0) * 55%), transparent);
    animation: lorbRim calc(6.2s / var(--orb-speed, 1)) ease-in-out infinite;
  }
  @keyframes lorbHalo{
    0%,100%{ opacity:.24; transform:scale(.97); }
    50%    { opacity:.86; transform:scale(1.09); }
  }
  @keyframes lorbRim{
    0%,100%{ opacity:.36; transform:scale(1); }
    50%    { opacity:1;   transform:scale(1.04); }
  }
  /* On paper the glow has nothing to do, so the rim carries it at any size and
     the line darkens to something that can actually be seen on cream. */
  :root[data-mode="light"] .lorb{ --edge:1; }
  :root[data-mode="light"] .lorb-body{
    background:
      radial-gradient(circle at 34% 28%,
        color-mix(in srgb, var(--orb-accent, #9A7BE0) 74%, transparent), transparent 62%),
      radial-gradient(circle at 50% 50%,
        color-mix(in srgb, var(--orb-accent, #9A7BE0) 34%, #FFFFFF), #FFFFFF 74%);
  }
  :root[data-mode="light"] .lorb-halo{ opacity:.5; }
  /* REDUCED EFFECTS AND REDUCED MOTION LAND ON A POSE, NOT A PAUSE.
     animation-play-state:paused freezes wherever the cycle happened to be,
     which is how you get an orb stopped at 24% opacity looking broken. These
     stop the animation and state the resting appearance instead, so a still
     orb still looks like something somebody drew. */
  :root[data-fx="lite"] .lorb-halo{ animation:none; opacity:.62; transform:scale(1.04); }
  :root[data-fx="lite"] .lorb-ring{ animation:none; opacity:.78; }
  @media (prefers-reduced-motion: reduce){
    .lorb-halo{ animation:none; opacity:.62; transform:scale(1.04); }
    .lorb-ring{ animation:none; opacity:.78; }
  }
  /* ---- the other two shapes -------------------------------------------
     Same object, same colour, same breath underneath — one extra layer on
     top. Both are a single rotating or scaling element behind a mask, which
     is one composited layer and no repaint, so neither costs measurably more
     than the halo it sits on. */
  /* CHILD SELECTORS, NOT CLASS SELECTORS, AND THAT IS NOT A STYLE CHOICE.
     `.lorb > span` above sets display:block at specificity (0,1,1). A bare
     `.lorb-arc{display:none}` is (0,1,0) and LOSES to it — so both extra
     layers were on screen at once whichever shape was chosen, and the swoosh
     and the sonar rings ran on top of each other. Caught by asserting that
     picking one shape turns the other one off. */
  .lorb > .lorb-arc, .lorb > .lorb-ping{ display:none; }

  .lorb[data-style="swoosh"] > .lorb-arc{
    display:block;
    background: conic-gradient(from 0deg, transparent 0 52%,
      color-mix(in srgb, var(--orb-accent, #9A7BE0) 70%, transparent) 74%,
      #fff 82%, transparent 88%);
    -webkit-mask: radial-gradient(circle, transparent 0 63%, #000 66%);
            mask: radial-gradient(circle, transparent 0 63%, #000 66%);
    filter: drop-shadow(0 0 calc(var(--d,88px) * .07) var(--orb-accent, #9A7BE0));
    animation: lorbSpin calc(3.6s / var(--orb-speed, 1)) linear infinite;
  }
  @keyframes lorbSpin{ to{ transform: rotate(360deg); } }

  /* Sonar is the still one: nothing moves until Lucid has something to say.
     That is the entire proposition, so the ping is bound to the state rather
     than running on a loop. */
  .lorb[data-style="sonar"] > .lorb-ping{
    display:block; border: max(1px, calc(var(--d,88px) * .015)) solid var(--orb-accent, #9A7BE0);
    opacity:0;
  }
  :root[data-orb-state="thinking"] .lorb[data-style="sonar"] > .lorb-ping,
  :root[data-orb-state="news"] .lorb[data-style="sonar"] > .lorb-ping{
    animation: lorbPing calc(9s / var(--orb-speed, 1)) cubic-bezier(.2,.7,.3,1) infinite;
  }
  .lorb[data-style="sonar"] > .lorb-ping.p2{ animation-delay: calc(-4.5s / var(--orb-speed, 1)); }
  @keyframes lorbPing{
    0%  { transform: scale(.52); opacity:.55; }
    70% { opacity:.14; }
    100%{ transform: scale(1.6); opacity:0; }
  }
  :root[data-fx="lite"] .lorb-arc, :root[data-fx="lite"] .lorb-ping{ animation:none; }
  @media (prefers-reduced-motion: reduce){
    .lorb-arc, .lorb-ping{ animation:none !important; }
  }

  /* WHAT LUCID IS DOING, in one variable. Idle is slower and dimmer; thinking
     is quicker; news shifts the accent so it is a different colour rather than
     just a faster version of the same thing. */
  :root[data-orb-state="idle"]     .lorb{ --orb-speed:.62; }
  :root[data-orb-state="thinking"] .lorb{ --orb-speed:1.9; }
  :root[data-orb-state="news"]     .lorb{ --orb-speed:1.15; --orb-accent: var(--orb-accent-2, #C6B4F0); }

  /* Halo sits behind the orb so it separates from the page without putting a
     hard edge around it — the orb should look like it is floating, not pasted. */
  .chat-pill-aurora{
    position:absolute; inset:-16px; border-radius:50%; pointer-events:none; z-index:0;
    background:radial-gradient(closest-side, rgba(160,125,255,.34), rgba(61,107,255,.12) 55%, transparent 78%);
    filter:blur(12px); animation:breathe 4.2s ease-in-out infinite;
  }
  @media (max-width:640px){
    .chat-pill{ width:66px; height:66px; }
    /* 66px is small enough that the bloom starts to lose the edge — see
       orbEdgeFor(), which is where this number comes from. */
    #orbMain{ --d:66px; --edge:.47; }
  }
  @keyframes auroraShift{ 0%,100%{ background-position:0% 50%; } 50%{ background-position:100% 50%; } }
  /* On a narrow screen the label costs more than it earns — collapse to the
     original circle rather than letting a wide pill crowd the content. */
  @media (max-width:640px){
    .chat-pill{ width:56px; padding:0; }
    .chat-pill-label{ display:none; }
  }
  @keyframes pillRing{ 0%{ transform:scale(.88); opacity:.5; } 100%{ transform:scale(1.4); opacity:0; } }
  .chat-card{ position:fixed; bottom:26px; right:26px; z-index:20; width:380px; max-width:calc(100vw - 32px); height:560px; max-height:calc(100vh - 52px); display:none; flex-direction:column; border-top:3px solid var(--blue); border-top-style:solid; padding:0; overflow:hidden; }
  .chat-card.open{ display:flex; animation:chatRise .22s cubic-bezier(.2,.8,.3,1); }
  @keyframes chatRise{ from{opacity:0; transform:translateY(14px) scale(.96);} to{opacity:1; transform:none;} }
  .chat-header{ display:flex; align-items:center; gap:10px; padding:16px 16px 12px; border-bottom:1px solid var(--glass-border); flex:none; }
  /* The orb button hides when the chat opens (toggleChat), so the big sphere
     literally becomes this small one — there are never two on screen. */
  .chat-header-icon{ width:30px; height:30px; border-radius:50%; background:transparent; display:flex; align-items:center; justify-content:center; flex:none; position:relative; }
  /* The logo shifts with the orb rather than becoming it — see the size test:
     a real sphere is unreadable below ~32px, and this mark has to live at 16.
     Hue-rotating the whole mark keeps the three arcs distinguishable from each
     other while moving the family to match whatever the orb is showing. */
  .brand-mark{ filter: hue-rotate(var(--mark-rotate, 0deg)); transition: filter 700ms var(--ease-out); }
  .chat-header-icon svg{ width:14px; height:14px; color:var(--ink); }
  .chat-title{ font-family:'Fraunces',serif; font-weight:700; font-size:15.5px; flex:1; }
  .chat-close{ cursor:pointer; color:var(--text-dim); font-size:18px; width:26px; height:26px; display:flex; align-items:center; justify-content:center; border-radius:8px; }
  .chat-close:hover{ background:rgba(120,120,140,.1); color:var(--text); }
  .chat-thread{ flex:1; overflow-y:auto; padding:16px; display:flex; flex-direction:column; gap:10px; }
  /* THE LIFE TIMELINE.
     A spine down the left with the years on it. The gaps are drawn as loudly as
     the entries — see renderTimeline() — because a timeline with four things in
     it looks broken, and the same four with the empty years named between them
     looks like something being built. */
  /* ==========================================================
     C17 — THE SPINE
     ==========================================================
     "a timeline that moves canonizing major events, (child born, trip to
     Antigua) as well as future events, (John's wedding, Grateful Dead
     concert)."

     A horizontal line, scaled at a fixed 84px a year rather than fitted to the
     entries — so a dense recent decade LOOKS dense and a quiet stretch looks
     quiet. See renderTimelineSpine() for why that scale is the whole decision.

     Three states a pin can be in, and each is a different fact:
       plain      something that happened
       .is-land   a landmark — his own mark, drawn large, always labelled
       .is-ahead  it has not happened yet — past the Today marker, dashed */
  .tl-controls{ display:flex; align-items:flex-start; justify-content:space-between;
    gap:14px; flex-wrap:wrap; margin-bottom:16px; }
  .tl-shape{ flex:none; }

  .tl-spine-head{ display:flex; align-items:center; gap:8px; flex-wrap:wrap; margin-bottom:10px; }
  .tl-spine-hint{ font-size:11.5px; color:var(--text-faint); }

  /* THE RAIL SCROLLS; THE PAGE DOES NOT.
     overscroll-behavior-x:contain stops reaching the end of a life from
     handing the gesture to the browser's back-navigation, which on a trackpad
     is a very expensive way to look at 1974. */
  .tl-rail{
    position:relative; overflow-x:auto; overflow-y:hidden; overscroll-behavior-x:contain;
    padding:0; border-radius:16px; border:1px solid var(--glass-border);
    background:rgba(120,120,140,.05);
    cursor:grab; scrollbar-width:thin;
    /* Room for three rows of stacked labels above and two below. */
    min-height:330px;
  }
  .tl-rail:focus-visible{ outline:2px solid var(--accent); outline-offset:2px; }
  .tl-rail.is-dragging{ cursor:grabbing; scroll-behavior:auto; }
  .tl-rail::-webkit-scrollbar{ height:8px; }
  .tl-rail::-webkit-scrollbar-thumb{ background:rgba(120,120,140,.28); border-radius:4px; }
  .tl-track{ position:relative; height:330px; padding:0 70px; }

  /* The line itself sits at the vertical middle, so pins have somewhere to go
     in both directions. */
  .tl-line{ position:absolute; left:0; right:0; top:50%; height:2px;
    background:linear-gradient(90deg, transparent, var(--glass-border-hi) 6%, var(--glass-border-hi) 94%, transparent); }
  /* Everything after today, dashed and dimmer. Same line, not a second one. */
  .tl-future{ position:absolute; right:0; top:50%; height:2px; transform:translateY(-1px);
    background-image:repeating-linear-gradient(90deg,
      color-mix(in srgb, var(--accent) 55%, transparent) 0 8px, transparent 8px 16px);
    opacity:.7; }

  .tl-tick{ position:absolute; top:50%; transform:translateX(-50%); pointer-events:none; }
  .tl-tick i{ position:absolute; left:50%; top:-9px; width:1px; height:18px;
    background:var(--glass-border-hi); }
  .tl-tick span{ position:absolute; left:50%; top:14px; transform:translateX(-50%);
    font-family:'IBM Plex Mono', monospace; font-size:10.5px; color:var(--text-faint); }

  /* WHERE TODAY IS. The one mark on the line that is not an entry, and it has
     to read as a different kind of thing — hence the accent and the label. */
  .tl-now{ position:absolute; top:50%; transform:translateX(-50%); z-index:3; pointer-events:none; }
  .tl-now i{ position:absolute; left:50%; top:-118px; width:2px; height:236px;
    background:linear-gradient(180deg, transparent, var(--accent) 22%, var(--accent) 78%, transparent);
    opacity:.5; }
  .tl-now span{ position:absolute; left:50%; top:-140px; transform:translateX(-50%);
    font-size:10px; font-weight:700; letter-spacing:.09em; text-transform:uppercase;
    color:var(--accent); white-space:nowrap; }

  .tl-pin{
    position:absolute; top:50%; transform:translateX(-50%);
    background:none; border:none; padding:0; cursor:pointer; z-index:2;
    color:inherit; font-family:inherit; text-align:left;
  }
  .tl-pin-stem{ position:absolute; left:50%; width:1px; background:var(--tl-c, var(--accent)); opacity:.45; }
  .tl-pin.up .tl-pin-stem{ bottom:0; height:var(--tl-lift, 26px); }
  .tl-pin.down .tl-pin-stem{ top:0; height:var(--tl-lift, 26px); }
  .tl-pin-dot{
    position:absolute; left:50%; top:0; transform:translate(-50%, -50%);
    width:9px; height:9px; border-radius:50%;
    background:var(--tl-c, var(--accent));
    box-shadow:0 0 0 3px var(--bg-deep, #0b0e18);
    transition:width .16s var(--ease-out), height .16s var(--ease-out);
  }
  .tl-pin-label{
    position:absolute; left:50%; transform:translateX(-50%);
    width:132px; text-align:center; pointer-events:none;
  }
  .tl-pin.up .tl-pin-label{ bottom:var(--tl-lift, 26px); margin-bottom:5px; }
  .tl-pin.down .tl-pin-label{ top:var(--tl-lift, 26px); margin-top:5px; }
  .tl-pin-t{ display:block; font-size:12px; font-weight:600; line-height:1.3; color:var(--text);
    /* Two lines and then an ellipsis. A pin is a way in, not the entry. */
    display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden; }
  .tl-pin-w{ display:block; font-family:'IBM Plex Mono', monospace; font-size:10px;
    color:var(--text-faint); margin-top:2px; }
  .tl-pin:hover .tl-pin-dot{ width:13px; height:13px; }
  .tl-pin:hover .tl-pin-t{ color:var(--tl-c, var(--accent)); }
  .tl-pin:focus-visible{ outline:2px solid var(--accent); outline-offset:4px; border-radius:6px; }

  /* A LANDMARK. Larger dot, a ring around it, and its title at heading weight —
     it should be findable while scrubbing past at speed without reading a word. */
  .tl-pin.is-land .tl-pin-dot{
    width:15px; height:15px;
    box-shadow:0 0 0 3px var(--bg-deep, #0b0e18), 0 0 0 5px color-mix(in srgb, var(--tl-c, var(--accent)) 55%, transparent),
               0 0 20px 2px color-mix(in srgb, var(--tl-c, var(--accent)) 45%, transparent);
  }
  .tl-pin.is-land:hover .tl-pin-dot{ width:18px; height:18px; }
  .tl-pin.is-land .tl-pin-label{ width:164px; }
  .tl-pin.is-land .tl-pin-t{ font-family:'Fraunces', serif; font-size:14.5px; font-weight:700;
    -webkit-line-clamp:3; }
  .tl-pin.is-land .tl-pin-stem{ opacity:.8; width:2px; }

  /* NOT YET. Hollow rather than filled — the difference between a thing that
     happened and a thing that is going to is worth being able to see from the
     other end of the line. */
  .tl-pin.is-ahead .tl-pin-dot{
    background:var(--bg-deep, #0b0e18);
    box-shadow:0 0 0 2px var(--tl-c, var(--accent)), 0 0 0 4px var(--bg-deep, #0b0e18);
  }
  .tl-pin.is-ahead.is-land .tl-pin-dot{
    box-shadow:0 0 0 3px var(--tl-c, var(--accent)), 0 0 0 6px var(--bg-deep, #0b0e18),
               0 0 20px 2px color-mix(in srgb, var(--tl-c, var(--accent)) 40%, transparent);
  }
  .tl-pin.is-ahead .tl-pin-stem{ opacity:.3; }
  .tl-pin.is-ahead .tl-pin-t{ color:var(--text-dim); }

  /* On paper the dot's knockout ring must be paper, not ink. */
  :root[data-mode="light"] .tl-pin-dot{ box-shadow:0 0 0 3px var(--bg, #fff); }
  :root[data-mode="light"] .tl-pin.is-land .tl-pin-dot{
    box-shadow:0 0 0 3px var(--bg, #fff), 0 0 0 5px color-mix(in srgb, var(--tl-c, var(--accent)) 55%, transparent); }
  :root[data-mode="light"] .tl-pin.is-ahead .tl-pin-dot{
    background:var(--bg, #fff); box-shadow:0 0 0 2px var(--tl-c, var(--accent)), 0 0 0 4px var(--bg, #fff); }
  :root[data-mode="light"] .tl-now i{ opacity:.35; }

  /* A phone has no room for a 132px label either side of a 9px dot, and a spine
     that has to be scrubbed with a thumb through overlapping text is worse than
     a list. The rail still works; it just gets tighter. */
  @media(max-width:700px){
    .tl-rail, .tl-track{ min-height:280px; height:auto; }
    .tl-track{ height:280px; padding:0 40px; }
    .tl-pin-label{ width:104px; }
    .tl-pin.is-land .tl-pin-label{ width:124px; }
    .tl-pin.is-land .tl-pin-t{ font-size:13px; }
  }

  /* The landmark checkbox in the add/edit card. */
  .tl-land-check{ display:flex; align-items:flex-start; gap:10px; margin:12px 0 4px;
    font-size:12.5px; color:var(--text-dim); line-height:1.5; cursor:pointer; }
  .tl-land-check input{ flex:none; width:16px; height:16px; margin-top:1px;
    accent-color:var(--accent); cursor:pointer; }
  .tl-land-check b{ color:var(--text); }

  .tl-year{ display:flex; align-items:baseline; gap:14px; margin:26px 0 10px; }
  .tl-year-n{ font-family:'Fraunces', serif; font-size:22px; font-weight:700; color:var(--text);
      min-width:74px; }
  .tl-year-rule{ flex:1; height:1px; background:var(--glass-border); }
  .tl-row{ display:flex; gap:14px; padding:9px 0 9px 0; }
  .tl-spine{ width:74px; flex:none; text-align:right; font-size:11.5px; color:var(--text-faint);
      padding-top:3px; }
  .tl-dot{ width:11px; height:11px; border-radius:50%; flex:none; margin-top:5px;
      background:var(--tl-c, var(--accent)); box-shadow:0 0 10px var(--tl-g, var(--accent-glow)); }
  .tl-body{ min-width:0; flex:1; }
  .tl-t{ font-size:14px; font-weight:600; color:var(--text); }
  .tl-w{ font-size:12px; color:var(--text-dim); margin-top:2px; }
  .tl-guess{ font-size:10.5px; font-weight:700; letter-spacing:.05em; text-transform:uppercase;
      color:var(--orange); margin-left:8px; }
  .tl-acts{ display:flex; gap:8px; align-items:center; flex:none; opacity:0; transition:opacity var(--dur-fast); }
  .tl-row:hover .tl-acts{ opacity:1; }
  .tl-gap{ display:flex; align-items:center; gap:14px; margin:12px 0 12px 88px;
      font-size:12px; color:var(--text-faint); font-style:italic; }
  .tl-gap i{ display:block; width:34px; height:1px; background:var(--glass-border);
      font-style:normal; }
  /* A category with nothing in it is still shown — turning it on is how he
     finds out it is empty — but it does not compete with the ones that have
     something behind them. */
  .chip.is-empty{ opacity:.55; }
  /* A chart in the answer. See renderAskChart(): the numbers are always
     printed underneath as well, so the picture is never the only copy. */
  .ask-chart{ align-self:flex-start; max-width:100%; width:100%; margin:2px 0 4px;
      padding:12px 13px 10px; border-radius:14px;
      background:rgba(120,120,140,.06); border:1px solid var(--glass-border); }
  .ask-chart-t{ font-size:12.5px; font-weight:700; color:var(--text); margin-bottom:2px; }
  .ask-chart-s{ font-size:11px; color:var(--text-faint); margin-bottom:9px; }
  .ask-chart svg{ display:block; width:100%; height:auto; overflow:visible; }
  .ask-chart .axis{ font-size:9px; fill:var(--text-faint); }
  .ask-chart .vlab{ font-size:9.5px; font-weight:700; fill:var(--text-dim); }
  .ask-chart-legend{ display:flex; flex-wrap:wrap; gap:10px; margin-top:8px;
      font-size:10.5px; color:var(--text-dim); }
  .ask-chart-legend i{ width:9px; height:9px; border-radius:3px; display:inline-block;
      margin-right:5px; vertical-align:-1px; }
  .ask-chart-num{ font-family:'Fraunces', serif; font-size:30px; font-weight:700; color:var(--text);
      line-height:1.1; }
  .ask-chart-table{ margin-top:9px; }
  .ask-chart-table summary{ font-size:10.5px; color:var(--text-faint); cursor:pointer;
      list-style:none; }
  .ask-chart-table summary::-webkit-details-marker{ display:none; }
  .ask-chart-table summary:hover{ color:var(--text-dim); }
  .ask-chart-table table{ width:100%; border-collapse:collapse; margin-top:6px; font-size:11px; }
  .ask-chart-table th{ text-align:left; font-weight:700; color:var(--text-faint);
      padding:2px 6px 4px 0; }
  .ask-chart-table td{ padding:2px 6px 2px 0; color:var(--text-dim); }
  /* The opening window. Left-aligned and dense on purpose: this is a briefing,
     and a briefing centred like a splash screen reads as decoration. */
  .ask-open{ text-align:left; padding:14px 4px 6px; }
  .ask-open-hello{ font-family:'Fraunces', serif; font-size:17px; font-weight:700;
      color:var(--text); margin:0 0 2px; }
  .ask-open-sub{ font-size:12px; color:var(--text-faint); margin:0 0 14px; }
  .ask-note{ display:flex; align-items:flex-start; gap:10px; padding:9px 10px; border-radius:11px;
      cursor:pointer; border:1px solid transparent; }
  .ask-note:hover{ background:rgba(120,120,140,.09); border-color:var(--glass-border); }
  .ask-note-mark{ width:3px; align-self:stretch; border-radius:2px; flex:none;
      background:color-mix(in srgb, var(--accent) 70%, transparent); }
  .ask-note.hot .ask-note-mark{ background:var(--pink); }
  .ask-note-body{ min-width:0; flex:1; }
  .ask-note-t{ font-size:13px; font-weight:600; color:var(--text); overflow:hidden;
      text-overflow:ellipsis; white-space:nowrap; }
  .ask-note-w{ font-size:11.5px; color:var(--text-dim); margin-top:2px; }
  .ask-note-go{ font-size:11px; color:var(--text-faint); flex:none; align-self:center; }
  .ask-note:hover .ask-note-go{ color:var(--accent); }
  .ask-open-label{ font-size:10.5px; font-weight:700; letter-spacing:.06em; text-transform:uppercase;
      color:var(--text-faint); margin:16px 0 7px; }
  .chat-empty{ text-align:center; padding:26px 10px 10px; }
  .chat-empty-icon{ font-size:24px; margin-bottom:10px; color:var(--blue); }
  /* On cream paper this measured 1.7:1 — a decorative sparkle, but the harness
     counts every glyph as text and it was right to: a mark that faint reads as
     a smudge rather than as anything. The accent it inherits is tunable by the
     orb sliders, so no accent value can be trusted here; on light papers it
     wears the ordinary dim-text token instead, which is the one colour on the
     page that has already been measured against every background. */
  :root[data-mode="light"] .chat-empty-icon{ color:var(--text-dim) !important; }
  .chat-empty-title{ font-size:13px; color:var(--text-dim); margin-bottom:18px; }
  .chat-suggestions{ display:flex; flex-direction:column; gap:8px; }
  .chat-chip{ padding:10px 14px; border-radius:12px; border:1px solid var(--glass-border-hi); font-size:12.5px; text-align:left; cursor:pointer; background:rgba(120,120,140,.05); }
  .chat-chip:hover{ background:rgba(120,120,140,.1); border-color:color-mix(in srgb, var(--blue) 40%, transparent); }
  .msg{ max-width:85%; padding:10px 14px; font-size:13px; line-height:1.55; white-space:pre-line; }
  .msg-user{ align-self:flex-end; background:linear-gradient(135deg,var(--blue),var(--blue-2)); color:var(--ink); border-radius:14px 14px 4px 14px; font-weight:500; }
  /* WHERE THIS CAME FROM. An assistant that reads your mail and then answers
     with no visible provenance is asking to be believed. These are the actual
     records the answer was built from, and each one opens the thing itself. */
  .ask-src{ align-self:flex-start; max-width:100%; display:flex; flex-wrap:wrap; gap:6px;
      margin:-2px 0 2px; align-items:center; }
  .ask-src-label{ font-size:10.5px; font-weight:700; letter-spacing:.05em; text-transform:uppercase;
      color:var(--text-faint); margin-right:2px; }
  .ask-src-chip{ display:inline-flex; align-items:baseline; gap:6px; max-width:320px;
      padding:4px 9px; border-radius:8px; font-size:11.5px; cursor:default;
      background:rgba(120,120,140,.10); border:1px solid var(--glass-border); color:var(--text-dim); }
  .ask-src-chip.can-open{ cursor:pointer; }
  .ask-src-chip.can-open:hover{ color:var(--text); border-color:var(--glass-border-hi);
      background:rgba(120,120,140,.16); }
  .ask-src-kind{ font-size:9.5px; font-weight:800; letter-spacing:.06em; text-transform:uppercase;
      color:var(--accent); flex:none; }
  .ask-src-t{ overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
  .msg-assistant{ align-self:flex-start; background:rgba(120,120,140,.08); border:1px solid var(--glass-border); border-radius:14px 14px 14px 4px; color:var(--text); }
  .msg-action{ align-self:flex-start; font-size:11.5px; color:var(--blue); margin-top:-2px; cursor:pointer; padding-left:2px; font-weight:600; }
  .msg-action:hover{ text-decoration:underline; }
  .typing{ align-self:flex-start; display:flex; gap:4px; padding:13px 14px; background:rgba(120,120,140,.08); border-radius:14px 14px 14px 4px; }
  .typing i{ width:6px; height:6px; border-radius:50%; background:var(--text-faint); display:block; animation:typingDot 1.2s infinite; }
  .typing i:nth-child(2){ animation-delay:.15s; } .typing i:nth-child(3){ animation-delay:.3s; }
  @keyframes typingDot{ 0%,60%,100%{ opacity:.3; transform:translateY(0);} 30%{ opacity:1; transform:translateY(-3px);} }
  /* Ask Lucid, now that it does something.
     .ask-status is the "Searching your mail…" line that sits above the typing
     dots — the browser drives the tool loop, so it knows what is happening and
     there is no reason to make you guess. */
  .ask-free{ font-size:11.5px; color:var(--text-faint); padding:2px 4px 8px; }
  .ask-free .mini-link{ text-decoration:underline; }
  .ask-status{ font-size:12px; color:var(--text-faint); padding:2px 4px 6px; font-style:italic;
    animation:askPulse 1.6s ease-in-out infinite; }
  @keyframes askPulse{ 0%,100%{ opacity:.55; } 50%{ opacity:1; } }
  @media (prefers-reduced-motion: reduce){ .ask-status{ animation:none; opacity:.8; } }
  /* Month-to-date spend, quietly visible. Matt pays per message now, and
     finding out at the end of the month is how people get surprised. */
  .chat-cost{ font-size:11px; color:var(--text-faint); text-align:right; padding:0 14px 8px; min-height:14px; }
  .chat-clear{ font-size:11.5px; color:var(--text-faint); cursor:pointer; margin-left:auto; margin-right:10px;
    padding:3px 9px; border-radius:8px; border:1px solid var(--glass-border); }
  .chat-clear:hover{ color:var(--text); }
  /* The drawer of earlier conversations. Sits over the top of the thread
     rather than replacing it, so pressing Earlier and changing your mind
     costs nothing. */
  .ask-past{ border-bottom:1px solid var(--glass-border); max-height:230px; overflow-y:auto;
    padding:8px 14px 10px; }
  .ask-past-head{ display:flex; align-items:center; gap:10px; font-size:10.5px; font-weight:700;
    letter-spacing:.1em; text-transform:uppercase; color:var(--text-dim); padding:4px 0 6px; }
  .ask-past-head span:first-child{ flex:1; }
  .ask-past-row{ display:flex; align-items:center; gap:10px; padding:7px 0; font-size:12.5px;
    border-bottom:1px solid var(--glass-border); cursor:pointer; }
  .ask-past-row:last-child{ border-bottom:none; }
  .ask-past-row:hover .ask-past-title{ color:var(--blue); }
  .ask-past-title{ flex:1; min-width:0; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
  .ask-past-when{ flex:none; font-size:11px; color:var(--text-dim); }
  .chat-input:disabled{ opacity:.5; }
  .msg-assistant b{ font-weight:700; }
  .msg-assistant code{ font-family:'IBM Plex Mono',monospace; font-size:12.5px;
    background:color-mix(in srgb, var(--text) 8%, transparent); padding:1px 5px; border-radius:5px; }
  .chat-input-row{ display:flex; gap:8px; padding:12px 14px; border-top:1px solid var(--glass-border); flex:none; }
  .chat-input{ flex:1; background:rgba(120,120,140,.08); border:1px solid var(--glass-border); border-radius:20px; padding:9px 15px; color:var(--text); font-family:'Public Sans'; font-size:13px; }
  .chat-input:focus{ outline:none; border-color:color-mix(in srgb, var(--blue) 50%, transparent); }
  .chat-send{ width:36px; height:36px; border-radius:50%; background:linear-gradient(135deg,var(--accent),var(--accent-2)); color:var(--ink); border:none; cursor:pointer; flex:none; font-weight:800; font-size:15px; }

  /* Both hidden by default and revealed by the phone breakpoint below — never
     the other way round. A sheet that is display:none only inside a media query
     is a sheet sitting in the middle of the desktop layout at every other
     width. */
  .bottom-nav{ display:none; }
  .bn-sheet{ display:none; }
  /* The breakpoint that used to be here is gone — .profile-grid is now
     container-driven and does not need one. See the note there. */
  @media (max-width: 780px){
    .sidebar{ display:none; }
    .app{ flex-direction:column; }
    .main{ flex:1; padding:16px 14px 90px; order:1; }
    .inbox-wrap{ height:auto; }
    /* The picker row goes to the phone too. It used to be replaced by a
     * sideways-scrolling strip of pills, which was the same six tabs with a
     * scrollbar — and still had nowhere to put New. One control that names
     * the folder and one that writes an email fit on any screen. */
    .folder-mobile{ display:none; }
    .mail-list-col{ width:100%; }
    .inbox-wrap.reading .mail-list-col{ display:none; }
    .inbox-wrap:not(.reading) .reading-pane{ display:none; }
    .profile-header{ flex-direction:column; text-align:center; }
    .icon-row{ justify-content:center; }
    .edit-btn{ margin-left:0; align-self:center; }
    .bottom-nav{ display:flex; order:2; position:fixed; bottom:0; left:0; right:0; background:color-mix(in srgb, var(--bg-base) 85%, transparent); backdrop-filter:blur(16px); border-top:1px solid var(--glass-border); padding:6px 0 max(10px, env(safe-area-inset-bottom)); z-index:10; }
    /* THE TARGET IS THE ITEM, NOT THE TEXT.
       The padding used to sit on the bar, so each item was exactly as tall as
       its own 11px line — a twelve-pixel target under a finger. Moving the
       padding onto the item and setting a floor is the whole fix, and 48px is
       Google's number; Apple's 44pt lands inside it. */
    .bn-item{ flex:1; text-align:center; font-size:11px; color:var(--text-dim);
      display:flex; flex-direction:column; align-items:center; justify-content:center;
      gap:3px; cursor:pointer; min-height:48px; padding:6px 2px; border-radius:12px;
      -webkit-tap-highlight-color:transparent; }
    .bn-item:active{ background:rgba(120,120,140,.16); }
    .bn-item.active{ color:var(--blue); }
    .bn-icon{ width:21px; height:21px; flex:none; }

    /* THE OTHER SIX. A sheet from the bottom, because that is the one place a
       thumb reaches on a phone held in one hand. */
    .bn-sheet{ position:fixed; inset:0; z-index:60; display:none;
      align-items:flex-end; background:rgba(0,0,0,.42); backdrop-filter:blur(3px); }
    .bn-sheet.open{ display:flex; }
    .bn-sheet-card{ width:100%; border-radius:20px 20px 0 0; padding:8px 14px max(18px, env(safe-area-inset-bottom));
      background:color-mix(in srgb, var(--bg-base) 94%, transparent);
      border-top:1px solid var(--glass-border);
      animation:bnSheetUp 220ms cubic-bezier(0.22,1,0.36,1) both; }
    @keyframes bnSheetUp{ from{ transform:translateY(18px); opacity:0; } to{ transform:none; opacity:1; } }
    .bn-sheet-grip{ width:38px; height:4px; border-radius:2px; margin:2px auto 12px;
      background:var(--glass-border-hi); }
    .bn-sheet-grid{ display:grid; grid-template-columns:repeat(4, 1fr); gap:6px; }
    .bn-more-item{ display:flex; flex-direction:column; align-items:center; justify-content:center;
      gap:5px; min-height:70px; padding:8px 4px; border-radius:14px; cursor:pointer;
      font-size:11px; line-height:1.25; text-align:center; color:var(--text-dim);
      -webkit-tap-highlight-color:transparent; }
    .bn-more-item:active{ background:rgba(120,120,140,.16); }
    .bn-more-item.active{ color:var(--blue); }
    .accounts-mobile{ display:flex; overflow-x:auto; gap:6px; padding-bottom:14px; margin-bottom:8px; border-bottom:1px solid var(--glass-border); }
    .chat-pill{ bottom:96px; right:20px; }
    .chat-card{ bottom:96px; right:14px; left:14px; width:auto; height:min(560px, calc(100vh - 180px)); }
  }
  .accounts-mobile{ display:none; }
  /* ---- Sign-in gate -----------------------------------------------------
     Sits above everything and outside #appZoomRoot, so the font-scale zoom
     can't shrink the one screen you need to be able to read. The app behind
     it is fully built but has loaded no data — nothing is fetched until a
     session exists. */
  #loginGate{
    position:fixed; inset:0; z-index:99999;
    display:none; align-items:center; justify-content:center;
    background:
      radial-gradient(900px 620px at 50% 34%, rgba(61,107,255,0.14), transparent 70%),
      radial-gradient(700px 520px at 82% 78%, rgba(160,125,255,0.10), transparent 72%),
      linear-gradient(180deg, #080A1A 0%, #050710 55%, #020306 100%);
    font-family:'Public Sans', -apple-system, sans-serif;
    -webkit-font-smoothing:antialiased;
  }
  #loginGate.show{ display:flex; }
  .login-card{
    width:min(400px, calc(100vw - 40px));
    padding:38px 34px 30px;
    text-align:center;
    border-radius:22px;
    background:rgba(20,28,60,0.42);
    border:1px solid rgba(255,255,255,0.10);
    box-shadow:0 30px 90px rgba(0,0,0,0.55), inset 0 1px 0 rgba(255,255,255,0.07);
    backdrop-filter:blur(22px) saturate(1.3);
    -webkit-backdrop-filter:blur(22px) saturate(1.3);
    animation:loginRise 520ms cubic-bezier(0.22,1,0.36,1) both;
  }
  @keyframes loginRise{ from{ opacity:0; transform:translateY(14px) scale(0.985); } to{ opacity:1; transform:none; } }

  /* The orb is Lucid's whole identity, so the first thing you ever see is it —
     not a form. The same element as Ask Lucid at 104px, where there is room
     for the edge to be at zero and the glow to be the whole object. */
  .login-orb{ width:104px; height:104px; margin:0 auto 20px; position:relative; }
  .login-orb .lorb{ margin:0 auto; }

  .login-title{ font-family:'Fraunces', serif; font-size:30px; font-weight:600;
    color:#F2F5FF; letter-spacing:-0.01em; margin:0 0 6px; }
  .login-sub{ font-size:13.5px; color:rgba(226,232,255,0.56); margin:0 0 24px; line-height:1.5; }

  .login-btn{
    display:flex; align-items:center; justify-content:center; gap:9px;
    width:100%; padding:13px 16px; margin:0 0 10px;
    border-radius:12px; border:1px solid rgba(255,255,255,0.13);
    background:rgba(255,255,255,0.05); color:#EEF2FF;
    font-family:inherit; font-size:14.5px; font-weight:600; cursor:pointer;
    transition:background 160ms cubic-bezier(0.22,1,0.36,1), border-color 160ms, transform 160ms;
  }
  .login-btn:hover{ background:rgba(255,255,255,0.09); border-color:rgba(255,255,255,0.22); }
  .login-btn:active{ transform:translateY(1px); }
  .login-btn[disabled]{ opacity:0.55; cursor:default; transform:none; }
  .login-btn.primary{
    background:linear-gradient(180deg, var(--orb-accent, #3D6BFF), var(--orb-accent-2, #2954D9));
    border-color:transparent; color:#fff;
    box-shadow:0 8px 26px var(--orb-glow, rgba(61,107,255,0.34));
  }
  .login-btn.primary:hover{ filter:brightness(1.08); }
  .login-btn svg{ width:17px; height:17px; flex:none; }

  .login-btn.ghost{ background:transparent; border-color:transparent;
    color:rgba(226,232,255,0.5); font-weight:500; font-size:13px; padding:8px; }
  .login-btn.ghost:hover{ background:rgba(255,255,255,0.05); color:#EEF2FF; }
  .login-or{ display:flex; align-items:center; gap:10px; margin:16px 0 14px;
    color:rgba(226,232,255,0.32); font-size:11px; letter-spacing:0.09em; text-transform:uppercase; }
  .login-or::before, .login-or::after{ content:''; flex:1; height:1px; background:rgba(255,255,255,0.09); }

  .login-input{
    width:100%; padding:12px 14px; margin:0 0 10px;
    border-radius:11px; border:1px solid rgba(255,255,255,0.13);
    background:rgba(6,10,26,0.55); color:#EEF2FF;
    font-family:inherit; font-size:14.5px; outline:none;
    transition:border-color 160ms, box-shadow 160ms;
  }
  .login-input::placeholder{ color:rgba(226,232,255,0.34); }
  .login-input:focus{ border-color:var(--orb-accent, #3D6BFF);
    box-shadow:0 0 0 3px var(--orb-glow, rgba(61,107,255,0.20)); }

  .login-msg{ min-height:18px; margin:12px 0 0; font-size:12.5px; line-height:1.5; }
  .login-msg.err{ color:#FF9A9A; }
  .login-msg.ok{ color:#8FE3B0; }
  .login-msg.info{ color:rgba(226,232,255,0.55); }
  .login-foot{ margin:18px 0 0; font-size:11.5px; color:rgba(226,232,255,0.30); }

  /* Post-sign-in nudge to register Touch ID, shown once. Deliberately a small
     card in the corner rather than another full-screen step — the person is
     already in and working, and this is an offer, not a requirement. */
  #passkeyNudge{
    position:fixed; right:18px; bottom:126px; z-index:9000;
    display:none; width:290px; padding:16px 18px;
    border-radius:15px; background:rgba(20,28,60,0.72);
    border:1px solid rgba(255,255,255,0.12);
    box-shadow:0 18px 50px rgba(0,0,0,0.5);
    backdrop-filter:blur(18px); -webkit-backdrop-filter:blur(18px);
    font-family:'Public Sans', sans-serif; color:#EEF2FF;
    animation:loginRise 420ms cubic-bezier(0.22,1,0.36,1) both;
  }
  #passkeyNudge.show{ display:block; }
  #passkeyNudge h4{ margin:0 0 5px; font-size:14px; font-weight:600; }
  #passkeyNudge p{ margin:0 0 13px; font-size:12.5px; line-height:1.5; color:rgba(226,232,255,0.6); }
  #passkeyNudge .pn-row{ display:flex; gap:8px; }
  #passkeyNudge .pn-row button{ flex:1; padding:9px 10px; font-size:12.5px; margin:0; }
