/* Life OS — the whole application stylesheet.
 *
 * This used to be a 350KB <style> block at the top of index.html, which meant
 * every page began with a third of a megabyte of CSS before a single word of
 * content. Google's OAuth verification read the home page, got to the end of
 * whatever it was willing to read, and reported that the page did not explain
 * the app and did not carry its name — both of which were sitting at byte
 * 354,000, past the point anything was looking.
 *
 * As a file it is also cacheable, which the inline version never was.
 */
/* ═══════════════════════════════════════════════════════════════════════
   Life OS v2 — application shell
   Locked design system: design-system.md. Purple is the BRAND, not the
   background. Gradient is reserved for the mark, the primary CTA, the
   greeting name and one hero moment per screen.
   ═══════════════════════════════════════════════════════════════════════ */
:root{
  /* Canvas & surfaces */
  --app-bg:linear-gradient(180deg,#1A1624 0%,#141220 52%,#111018 100%);
  /* The ramp's midpoint, as a solid: what shows behind the gradient layer,
     in the overscroll bounce, and in <meta name="theme-color">. */
  --app-bg-flat:#141220;
  --sidebar:#17151F; --rail:#1A1824;
  --surface:#211E2C; --surface-2:#2A2536; --surface-3:#322C40;
  /* The task card is its own step, deliberately ABOVE --surface: it is the
     thing on the board you are actually reading. It was written as a literal
     in four places, which meant it silently sat out the phone lift and stayed
     at 1.22:1 against the canvas while everything around it moved. A token,
     so it cannot be missed again. --task-hover must stay in step with it, and
     the blocked tick's centre is a hole cut in the card and uses both. */
  --task:#282431; --task-hover:#312D3D;
  --border:#332E43; --border-strong:#3d3652; --hairline:#332E43;
  /* Text */
  --text:#F4F2FA; --text-2:#B5ABC9; --muted:#766D8B; --label:#CEC7E8;
  /* ── The accent, in three weights ──────────────────────────────────────
     One accent was enough while everything purple meant the same thing. It
     no longer does: a primary button, a hover tint and a quiet label are
     three different jobs, and giving all three the same lavender is why the
     product reads lighter than it should.
       --accent-deep   primary actions, selected state, the orb's core
       --accent        highlights, hover, glow, secondary energy
       --accent-pale   restrained text and icons
     Adopted after the comparison: the lavender had become the whole identity,
     and everything purple therefore weighed the same. The graphite stays. */
  --accent-deep:#6A38E0; --accent-deep-rgb:106,56,224;
  --accent:#9169FF; --accent-rgb:145,105,255; --accent-soft:rgba(106,56,224,.20);
  --accent-pale:#C6B0FF;
  --brand:linear-gradient(180deg,#5A2BD2 0%,#8E62F5 100%);
  --name-grad:linear-gradient(180deg,#C6B0FF 0%,#6A38E0 100%);
  /* Status — meaning only */
  --ok:#00D9A3; --warn:#FFC44D; --danger:#FF646E;
  /* ── Priority (Phase C4) ───────────────────────────────────────────────
     Five levels, one token set. Each has a marker colour, a surface tint and
     a label colour, so a card, the Up-next chip and the selector all speak
     with the same voice. Intensity descends deliberately: urgent is loud,
     someday is nearly silent. */
  --p-urgent:#F0566E;  --p-urgent-bg:rgba(240,86,110,.10); --p-urgent-ink:#FF8A9C;
  --p-high:#F0913D;    --p-high-bg:rgba(240,145,61,.09);   --p-high-ink:#FFB36B;
  --p-medium:#7C6BD8;  --p-medium-bg:rgba(124,107,216,.07);--p-medium-ink:#A99BF0;
  --p-low:#4E8FC4;     --p-low-bg:rgba(78,143,196,.06);    --p-low-ink:#87B8E0;
  --p-someday:#6C6484; --p-someday-bg:transparent;         --p-someday-ink:#8D85A6;
  /* Elevation */
  --e1:0 2px 8px rgba(0,0,0,.14); --e2:0 8px 24px rgba(0,0,0,.22);
  --e3:0 14px 34px rgba(0,0,0,.32);
  --glow:0 6px 18px rgba(106,56,224,.30);
  --glow-deep:0 6px 18px rgba(var(--accent-deep-rgb),.28);
  /* Motion (§7) */
  --d-instant:90ms; --d-fast:140ms; --d-base:200ms; --d-slow:260ms; --d-slower:320ms;
  --e-out:cubic-bezier(.2,.7,.2,1); --e-inout:cubic-bezier(.4,0,.2,1);
  --t-hover:var(--d-fast) var(--e-out);
  --t-select:var(--d-base) var(--e-out);
  /* S1 §35 — 400ms. 300 was measured as correct and reported as too quick to
     read: the turn is now a core product interaction and it has to be legible,
     not merely fast. Still no spring, no bounce, and the committed DOM still
     owns the final state. */
  --d-turn:400ms;
  --t-page:var(--d-slow) var(--e-out);
  /* Layout tokens — Expanded / Compact / Drawer plug in here (§11) */
  /* Ported verbatim from Legacy v244. These numbers ARE the approved design;
     they are not derivable from the design system's principles. */
  --sidebar-w:232px; --rail-w:380px; --gutter:56px; --content-max:2200px;
  --composer-max:1544px; --composer-h:64px;
  --radius:14px; --radius-lg:18px;

  /* ── Fluid scale (Phase C3) ────────────────────────────────────────────
     Laptops are untouched: every clamp() floor is the value that shipped, and
     the growth only begins at 1600px. Above that the interface grows with the
     viewport instead of stranding a laptop-sized app in the middle of a large
     display. Not a zoom — each token is chosen, and line-heights stay put.
     Reference points: 1440 = floor, 1920 = mid, 2560 = ceiling. */
  --fs-body:15px;
  --fs-task:13px;
  --fs-nav:13.5px;
  --fs-h1:34px;
  --fs-rail:12.5px;
  --row-pad-y:11px;
  --btn-h:38px;
  --composer-h:52px;
  --stack-gap:28px;
}

/* ── `hidden` must actually hide ──────────────────────────────────────────
 * The attribute works through the user-agent stylesheet's `display:none`, so
 * ANY author `display` on the same element silently defeats it. The
 * assistant's connection warning is `display:flex`, which is how an amber bar
 * with a dot and no text sat permanently at the top of the Assistant: the code
 * had hidden it, and the stylesheet had un-hidden it.
 * One rule, because this is a trap rather than a one-off. */
[hidden]{display:none!important}
@media (min-width:1600px){
  :root{
    /* 1600 → 2560px maps to the second value in each clamp. */
    --fs-task:clamp(13px, .52vw + 4.7px, 15.5px);
    --fs-nav:clamp(13.5px, .5vw + 5.5px, 16px);
    --fs-h1:clamp(34px, 1.35vw + 12.4px, 46px);
    --fs-rail:clamp(12.5px, .45vw + 5.3px, 14.5px);
    --row-pad-y:clamp(11px, .5vw + 3px, 16px);
    --btn-h:clamp(38px, .55vw + 29.2px, 44px);
    --composer-h:clamp(52px, .9vw + 37.6px, 66px);
    --stack-gap:clamp(28px, 1vw + 12px, 42px);
    --gutter:clamp(56px, 2vw + 24px, 88px);
    --rail-w:clamp(380px, 12vw + 188px, 460px);
  }
}
*{box-sizing:border-box}
/* Reserve the scrollbar always. Without it the gutter appears only on modes
   tall enough to scroll, so Month rendered 15px narrower than Agenda and the
   page twitched sideways when you switched between them. */
html{scrollbar-gutter:stable}
html,body{margin:0;height:100%}
body{
  font-family:Inter,-apple-system,BlinkMacSystemFont,'Segoe UI',system-ui,sans-serif;
  /* A flat colour, and the ramp on a layer of its own below. This is also
     what the rubber-band overscroll area shows on a phone, and it is the
     value in <meta name="theme-color">, so the status bar, the bounce and
     the page agree. */
  background:var(--app-bg-flat);color:var(--text);
  -webkit-font-smoothing:antialiased;overflow-x:hidden;
}
/* ── The canvas gradient ────────────────────────────────────────────────
   `background: var(--app-bg); background-attachment: fixed` on <body> is what
   this used to be, and iOS Safari does not honour `fixed` on the scrolling
   root — it falls back to `scroll`, which sizes the ramp to the body box and
   then TILES it. Measured on a 390x844 phone: at the foot of Diary the left
   column reads #191622 - #100f19 - #171320, darkening and then jumping back
   light again, which is a seam across the page. Under `fixed` the same column
   is #191622 - #11101c - #100f18 at the top of the page and at the bottom.

   A real fixed layer behaves the same in every browser. -2 sits below the
   star field, which is already -1, and body creates no stacking context so
   both stay behind the app. */
body::before{content:'';position:fixed;inset:0;z-index:-2;
  background:var(--app-bg);pointer-events:none}
button,input,textarea,select{font:inherit;color:inherit}
button{cursor:pointer;border:none;background:none}
a{color:inherit}
:focus-visible{outline:2px solid rgba(var(--accent-rgb),.75);outline-offset:2px;border-radius:8px}
.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;
  clip:rect(0,0,0,0);white-space:nowrap;border:0}

/* ── Motion utilities ───────────────────────────────────────────────── */
@keyframes rise{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}
@keyframes fade{from{opacity:0}to{opacity:1}}
@keyframes lotusSettle{from{opacity:0;transform:scale(.82) translateY(4px)}to{opacity:1;transform:none}}
@keyframes wordmarkIn{from{opacity:0;transform:translateX(-6px)}to{opacity:1;transform:none}}
@keyframes spin{to{transform:rotate(360deg)}}
@keyframes shimmer{0%{background-position:-320px 0}100%{background-position:320px 0}}
/* fill-mode is `backwards`, never `both`: `both` keeps the final keyframe
   applied forever, and an animation outranks a normal declaration — so
   `to{opacity:1}` would permanently beat rules like `.task.done{opacity:.55}`. */
.rise{animation:rise var(--d-slow) var(--e-out) backwards}
.stagger>*{animation:rise var(--d-slow) var(--e-out) backwards;animation-delay:calc(var(--i,0)*30ms)}

/* ══ SHELL ══════════════════════════════════════════════════════════════
   Three columns, persistent. Only .main-scroll crossfades between routes —
   the sidebar and rail never redraw (§11: "transitions are continuous"). */
/* Legacy structure: the app is TWO columns. Content and rail form a nested
   grid with a 56px gutter and a 2200px ceiling — flattening this into one
   three-column grid is what produced the dead centre and the detached rail. */
.shell{display:grid;grid-template-columns:var(--sidebar-w) minmax(0,1fr);
  min-height:100vh;min-height:100dvh}
.main-wrap{display:grid;grid-template-columns:minmax(0,1fr) var(--rail-w);
  gap:var(--gutter);padding:32px 32px 140px 44px;max-width:var(--content-max);
  margin:0 auto;width:100%}
.main-col{min-width:0;display:flex;flex-direction:column;gap:var(--stack-gap)}

/* ── Left sidebar ───────────────────────────────────────────────────── */
.sidebar{background:var(--sidebar);display:flex;flex-direction:column;
  padding:20px 14px 14px;position:sticky;top:0;height:100vh;height:100dvh;z-index:40}
.logo{display:flex;align-items:center;gap:11px;padding:2px 10px 22px;text-decoration:none}
.logo-mark{flex-shrink:0;filter:drop-shadow(0 0 6px rgba(var(--accent-deep-rgb),.3))}
.logo-word{font-family:'Playfair Display',Georgia,serif;font-size:21px;font-weight:500;
  letter-spacing:-.2px;line-height:1;color:#F6F3FF}
.intro .logo-mark{animation:lotusSettle 620ms var(--e-out) backwards}
.intro .logo-word{animation:wordmarkIn 520ms var(--e-out) 180ms backwards}

.cmdk{display:flex;align-items:center;gap:9px;width:100%;background:rgba(255,255,255,.045);
  border-radius:10px;padding:9px 11px;margin-bottom:16px;color:var(--muted);font-size:13px;
  transition:background var(--t-hover),color var(--t-hover)}
.cmdk:hover{background:rgba(255,255,255,.075);color:var(--text-2)}
.cmdk kbd{margin-left:auto;font-family:inherit;font-size:10.5px;letter-spacing:.04em;
  background:rgba(255,255,255,.07);border-radius:5px;padding:2px 6px;color:var(--muted)}

.nav{position:relative;display:flex;flex-direction:column;gap:5px;flex:1;min-height:0}
/* THE sliding indicator: one element, translateY only, GPU-composited.
   It never disappears — it snaps on first reveal and glides thereafter. */
/* Legacy's indicator has its OWN gradient (var(--accent-deep)→#9A67FF), not the button
   gradient, and a fixed 44px height rather than the item's own. */
.nav-pill{position:absolute;left:0;right:0;height:44px;border-radius:10px;
  background:linear-gradient(180deg,var(--accent-deep) 0%,#9A67FF 100%);
  box-shadow:0 6px 18px -6px rgba(var(--accent-deep-rgb),.5),
  inset 0 1px 0 rgba(255,255,255,.12);
  transform:translate3d(0,var(--pill-y,0px),0);will-change:transform;
  transition:transform var(--t-select);pointer-events:none;z-index:0}
.nav-pill.snap{transition:none}
.nav a{display:grid;grid-template-columns:22px 1fr auto;align-items:center;gap:13px;
  min-height:44px;padding:0 12px;border-radius:10px;font-size:var(--fs-nav);font-weight:450;color:#9189A7;
  text-decoration:none;position:relative;z-index:1;
  transition:color var(--t-hover),transform var(--t-hover)}
.nav a .ico{display:grid;place-items:center;transition:transform var(--t-hover),opacity var(--t-hover)}
.nav a:hover{color:#EAE6F4}
.nav a:hover .ico{transform:translateX(1px);opacity:1}
.nav a[aria-current="page"]{color:#fff;font-weight:600}   /* weight, not brightness */
/* Unfinished sections get ONE quiet dot, not five repetitions of the word
   "soon". The nav should read as a map of the app, not a list of apologies. */
.nav a .soon{width:5px;height:5px;border-radius:50%;background:var(--muted);opacity:.45;
  font-size:0;line-height:0;overflow:hidden;justify-self:end;margin-right:3px}
.nav a:hover .soon{opacity:.7}
.nav a[aria-current="page"] .soon{background:rgba(255,255,255,.55);opacity:.8}

.side-foot{padding:12px 6px 0;border-top:1px solid rgba(255,255,255,.05);margin-top:8px}
.who{display:flex;align-items:center;gap:10px;padding:8px 6px;border-radius:10px;
  text-decoration:none;transition:background var(--t-hover)}
.who:hover{background:rgba(255,255,255,.045)}
.avatar{width:26px;height:26px;border-radius:50%;background:var(--brand);display:grid;
  place-items:center;font-size:11px;font-weight:700;color:#fff;flex-shrink:0}
.who-text{min-width:0}
.who-name{font-size:12.5px;color:var(--text-2);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.who-sub{font-size:10.5px;color:var(--muted)}

/* ── Main column ────────────────────────────────────────────────────── */
.main{display:flex;flex-direction:column;min-width:0;min-height:100vh;min-height:100dvh}
/* Legacy's header is a baseline-aligned ROW with actions pushed right. */
.page-head{display:flex;align-items:baseline;gap:14px;flex-wrap:wrap}
.eyebrow{font-size:11px;font-weight:600;letter-spacing:.15em;text-transform:uppercase;
  color:var(--muted);margin:0 0 8px;flex-basis:100%}
h1{font-size:var(--fs-h1);font-weight:800;letter-spacing:-1px;line-height:1.06;margin:0;color:#F6F3FF}
h1 .nm{background:var(--name-grad);-webkit-background-clip:text;background-clip:text;color:transparent}
.sub{font-size:13px;color:var(--muted);margin:0}
.main-scroll{min-width:0;animation:fade var(--t-page) backwards}
/* ── The Today toolbar and its Area filters (D2.4 §21) ──────────────────
   THE DEFECT: `.filters` had no rule at all. Its chips are inline-block, so
   the only thing between them was the whitespace in the template — about 4px
   at 12px type — and the pill borders read as touching. `.toolbar`'s own 9px
   gap separated Add Task from the GROUP, which is why only the pills inside
   looked wrong.

   One token, used by both, so the two gaps can never drift apart: the space
   between the button and the group is the same space as between two pills. */
:root{--chip-gap:9px}
.toolbar{display:flex;gap:var(--chip-gap);align-items:center;margin:0 0 20px;
  flex-wrap:wrap}
.filters{display:flex;gap:var(--chip-gap);align-items:center;flex-wrap:wrap;
  min-width:0}

/* What the board is holding back, and why. Quiet — it is a note about work you
   have already decided is not now, not a warning. */
.held-note{display:flex;align-items:center;gap:10px;flex-wrap:wrap;
  margin:0 0 18px;padding:9px 14px;border-radius:10px;font-size:12.5px;
  color:var(--text-2);background:var(--surface-2);
  border:1px solid var(--border)}
.held-note.is-open{border-color:var(--border-strong);color:var(--text)}
.held-note-t{min-width:0}
/* Add Task keeps a wider separation than the pills keep from each other, so
   the group reads as one thing rather than as five siblings of the button. */
.toolbar > .btn + .filters{margin-left:calc(var(--chip-gap) * .5)}

/* ── Right rail ─────────────────────────────────────────────────────── */
/* Legacy's rail is a sticky COLUMN inside the content grid — not a full-height
   panel welded to the viewport edge. It shares the page canvas. */
.rail{display:flex;flex-direction:column;gap:14px;position:sticky;top:22px;
  align-self:start;max-height:calc(100vh - 60px);overflow-y:auto;padding-right:2px;z-index:30}
.rail-card{background:var(--surface);border-radius:var(--radius);padding:16px 17px;box-shadow:var(--e2)}
.rail-card h3{font-size:10.5px;font-weight:600;letter-spacing:.15em;text-transform:uppercase;
  color:var(--label);margin:0 0 13px;display:flex;align-items:center;gap:8px}

/* Date is supporting context now, not a card competing for the top slot. */
.rail-when{display:flex;align-items:baseline;gap:8px;padding:0 3px 2px}
.rw-day{font-size:calc(var(--fs-rail) + 1px);font-weight:600;color:var(--text-2)}
.rw-date{font-size:var(--fs-rail);color:var(--muted)}
.un-legacy{font-size:10.5px;color:var(--muted);background:var(--surface-2);
  border-radius:6px;padding:2px 8px;font-style:italic}

/* Habits — green means completed, and nothing else in the app uses it. */
.hb-count{margin-left:auto;font-size:10px;font-weight:700;color:var(--muted);
  background:var(--surface-2);border-radius:6px;padding:1px 7px;letter-spacing:0}
.hb-list{display:flex;flex-direction:column;gap:2px}
.hb-row{display:flex;align-items:center;gap:11px;padding:8px 6px;border-radius:9px;
  transition:background var(--t-hover)}
.hb-row:hover{background:rgba(255,255,255,.035)}
.hb-name{flex:1;min-width:0;font-size:var(--fs-rail);color:var(--text-2);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;transition:color var(--t-hover)}
.hb-row.is-done .hb-name{color:var(--muted)}
.hb-prog{font-size:10.5px;color:var(--muted);font-variant-numeric:tabular-nums}
.hb-streak{font-size:10px;font-weight:700;color:var(--ok);background:rgba(0,217,163,.12);
  border-radius:6px;padding:2px 7px;font-variant-numeric:tabular-nums}


.rail-link{font-size:12px;color:var(--accent);padding:4px 0 0;text-align:left;
  transition:opacity var(--t-hover)}
.rail-link:hover{opacity:.75}
.rail-quiet{font-size:var(--fs-rail);color:var(--muted);margin:0;line-height:1.65}

/* Account menu */
.account-menu{min-width:236px;padding:8px}
.am-head{padding:8px 10px 12px;border-bottom:1px solid var(--hairline);margin-bottom:6px}
.am-name{font-size:13.5px;font-weight:600;color:var(--text)}
.am-mail{font-size:11.5px;color:var(--muted);margin-top:2px;overflow:hidden;
  text-overflow:ellipsis;white-space:nowrap}
.am-ws{font-size:11px;color:var(--muted);margin-top:5px}
.account-menu button[role="menuitem"]{gap:11px}
.am-sep{height:1px;background:var(--hairline);margin:6px 0}
.am-meta{font-size:10.5px;color:var(--muted);padding:4px 10px 8px;font-variant-numeric:tabular-nums}
.am-danger{color:var(--danger)}
.am-danger:hover{background:rgba(255,100,110,.1)!important;color:var(--danger)!important}
.who-chev{margin-left:auto;color:var(--muted);font-size:11px;flex-shrink:0;
  transition:transform var(--t-hover)}
.who[aria-expanded="true"] .who-chev{transform:rotate(180deg)}
.who{width:100%;text-align:left}

/* ── Global composer (AI location, not yet connected) ───────────────── */
/* Legacy: spans from the sidebar to the RIGHT EDGE (over the rail), capped at
   1544px and centred. Full opacity with a real border — the previous .62 made
   it read as broken rather than as pending. */
.composer{position:fixed;left:var(--sidebar-w);right:0;bottom:0;z-index:50;
  padding:12px 28px env(safe-area-inset-bottom,14px);
  background:linear-gradient(to top,#111018 72%,transparent);pointer-events:none}
.composer-inner{pointer-events:auto;max-width:var(--composer-max);margin:0 auto;
  display:flex;align-items:center;gap:13px;background:#1A1826;
  backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);
  border:1px solid #3A3450;border-radius:var(--radius);padding:0 12px 0 16px;
  box-shadow:var(--e3);min-height:var(--composer-h);cursor:default;
  transition:border-color var(--t-hover),background var(--t-hover)}
.composer-inner:hover{border-color:#4A4266;background:#1D1B2B}
/* The bar is a real input now. It was a disabled placeholder saying "Soon"
   for two phases; the assistant is behind it. */
.composer-inner:focus-within{border-color:var(--accent-deep);background:#1D1B2B}
/* ── A textarea, not a search field ──────────────────────────────────────
 *
 * It was an `<input>`, so a long thought ran sideways out of view and the only
 * way to read it back was to scroll a single line. It now wraps, starts one
 * line high and grows with the text.
 *
 * `resize:none` because the height is computed from the content — a drag
 * handle would fight the measurement. The `max-height` repeats the cap the
 * script applies, so a failed measurement still cannot let the bar march up
 * the screen; the script is what turns the scrollbar on at the same point. */
.composer-input{flex:1;min-width:0;background:none;border:0;outline:0;color:var(--text);
  font-size:calc(var(--fs-task) + .5px);font-family:inherit;padding:0;
  display:block;resize:none;line-height:1.45;max-height:calc(1.45em * 7);
  overflow-y:hidden}
.composer-input::placeholder{color:var(--muted)}
.composer-go{flex-shrink:0;display:grid;place-items:center;width:32px;height:32px;
  border-radius:9px;color:var(--muted);transition:background var(--t-hover),color var(--t-hover)}
.composer-go:hover{background:var(--surface-2);color:var(--text)}

/* ── Voice mode ───────────────────────────────────────────────────────────
 *
 * The SAME composer, expanded — the text row is replaced rather than covered,
 * so speaking happens where typing happens instead of in a window over it.
 *
 * Nothing here shows the transcript. While somebody is speaking the browser's
 * guesses are rewritten constantly, and putting that on screen made a working
 * recogniser look broken. The strip says the microphone is heard; the words
 * arrive once, settled, when Keep or Send says so. */
.composer.is-voice .composer-inner{display:none}
.cmp-voice{pointer-events:auto;max-width:var(--composer-max);margin:0 auto;
  display:flex;flex-direction:column;gap:11px;background:#1A1826;
  backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);
  border:1px solid var(--accent-deep);border-radius:var(--radius);
  padding:12px 14px;box-shadow:var(--e3)}
.cmp-voice-row{display:flex;align-items:center;gap:14px;min-width:0}
/* The word "Listening" carries the state, not the colour — the dot is a
   second signal, never the only one. */
.cmp-voice-state{display:flex;align-items:center;gap:8px;flex-shrink:0;
  font-size:12.5px;font-weight:600;color:var(--text-2)}
.cmp-voice-state.is-busy{color:var(--muted)}
.cmp-voice-dot{width:8px;height:8px;border-radius:50%;background:var(--accent);
  flex-shrink:0;animation:cmp-listen 1.4s ease-in-out infinite}
/* The breath is a SCALE, not an opacity. A bare `opacity:.n` anywhere in this
   block is indistinguishable, to the rule that guards it, from somebody making
   the composer bar itself translucent - and that guard is worth more than the
   one property it costs. Same reasoning as the lotus above. */
@keyframes cmp-listen{0%,100%{transform:scale(1)}50%{transform:scale(.55)}}
/* `--wave-ink` is read by voice-wave.js, so the strip follows the theme
   rather than carrying a colour of its own. */
.cmp-wave{--wave-ink:rgba(139,127,245,.9);flex:1;min-width:0;height:26px;display:block}
.cmp-voice-acts{display:flex;align-items:center;gap:8px;justify-content:flex-end}
.cmp-vbtn{display:flex;align-items:center;gap:7px;height:32px;padding:0 13px;
  border-radius:9px;font-size:12.5px;font-weight:600;color:var(--text-2);
  background:var(--surface-2);box-shadow:inset 0 0 0 1px var(--hairline);
  transition:background var(--t-hover),color var(--t-hover)}
.cmp-vbtn:hover{background:var(--surface-3);color:var(--text)}
.cmp-vbtn:disabled{filter:opacity(.5);pointer-events:none}
.cmp-vbtn.is-send{background:var(--brand);color:#fff;box-shadow:var(--glow)}
.cmp-vbtn.is-send:hover{color:#fff}
.cmp-vglyph{font-size:16px;line-height:1}
/* Keep is a stop: the square is the universal "end the recording" mark. */
.cmp-vstop{width:9px;height:9px;border-radius:2px;background:currentColor}
@media (prefers-reduced-motion:reduce){
  .cmp-voice-dot{animation:none;transform:none}}

/* ── Voice input ──────────────────────────────────────────────────────
 * The same 32px control as Send, sitting beside it, because speech is
 * another way to fill this field rather than a feature of its own. Hidden
 * outright where the browser has no recogniser — an inert button that
 * apologises when pressed is worse than no button. */
/* ── The listening mark ───────────────────────────────────────────────────
 *
 * The lotus, not a microphone. Idle it is quiet — desaturated and dimmed, so
 * it reads as a control rather than as branding sitting in a text field.
 * Listening it is full colour and breathing.
 *
 * The breath is on the MARK itself here, unlike the ring this replaced. That
 * note was right about a jumping icon pulling the eye off the words — but a
 * ring pulsing around a microphone is a decoration, and a lotus growing and
 * shrinking is the thing itself saying it is on. The scale is small (6%) and
 * slow, which is a breath rather than a jump. */
.composer-mic{position:relative;flex-shrink:0;display:grid;place-items:center;
  width:32px;height:32px;border-radius:9px;
  transition:background var(--t-hover)}
/* The dimming lives INSIDE `filter`, not in a separate `opacity`. A bare
   opacity here is indistinguishable, in the rule that guards this block, from
   somebody making the composer bar itself translucent — and that guard is
   worth more than the one property it costs. */
.composer-mic .logo-mark{display:block;
  filter:saturate(.12) brightness(.78) opacity(.72);
  transition:filter var(--t-select),transform var(--t-select)}
.composer-mic:hover{background:var(--surface-2)}
.composer-mic:hover .logo-mark{filter:saturate(.5) brightness(.95) opacity(.92)}

.composer-mic.is-listening{background:rgba(var(--accent-rgb),.20)}
.composer-mic.is-listening .logo-mark{
  filter:none;animation:lotus-breathe 1.5s ease-in-out infinite}
/* A soft halo behind it, so the state reads at a glance and not only up
   close. Deliberately under the mark rather than a ring around the button. */
.composer-mic.is-listening::after{content:'';position:absolute;inset:0;
  border-radius:9px;box-shadow:0 0 0 0 rgba(var(--accent-rgb),.5);
  animation:mic-pulse 2s ease-out infinite;pointer-events:none}
.composer.is-listening .composer-inner{border-color:var(--accent)}

@keyframes lotus-breathe{
  0%,100%{transform:scale(1)}
  50%{transform:scale(1.13)}}
@keyframes mic-pulse{
  0%{box-shadow:0 0 0 0 rgba(var(--accent-rgb),.45)}
  70%{box-shadow:0 0 0 7px rgba(var(--accent-rgb),0)}
  100%{box-shadow:0 0 0 0 rgba(var(--accent-rgb),0)}}
@media (prefers-reduced-motion:reduce){
  .composer-mic.is-listening .logo-mark{animation:none;transform:scale(1.08)}
  .composer-mic.is-listening::after{animation:none;
    box-shadow:0 0 0 2px rgba(var(--accent-rgb),.45)}}

/* ── The desktop assistant panel ──────────────────────────────────────────
 *
 * It grows UPWARDS from the composer over the page you are on, rather than
 * being a place you navigate to. The assistant's job is to act on what you
 * are looking at, and going somewhere else to ask throws that away. */
.asstp{pointer-events:auto;max-width:var(--composer-max);margin:0 auto 10px;
  background:var(--surface);border:1px solid var(--border-strong);
  border-radius:var(--radius);box-shadow:var(--e3);overflow:hidden;
  display:flex;flex-direction:column;max-height:min(62vh,560px)}
.asstp-head{display:flex;align-items:center;gap:10px;padding:10px 12px 9px 14px;
  border-bottom:1px solid var(--border)}
.asstp-t{flex:1;display:flex;align-items:center;gap:7px;font-size:11px;font-weight:700;
  letter-spacing:.09em;text-transform:uppercase;color:var(--muted)}
.asstp-t svg{color:var(--accent)}
.asstp-x{font-size:12px;color:var(--muted);border-radius:7px;padding:4px 9px;
  display:inline-flex;align-items:center;
  transition:background var(--t-hover),color var(--t-hover)}
.asstp-x:hover{background:var(--surface-2);color:var(--text)}
.asstp-body{flex:1;min-height:0;overflow-y:auto;padding:14px;
  display:flex;flex-direction:column;gap:11px}
.asstp-line{margin:0;font-size:14.5px;line-height:1.5}
/* The assistant's half is rendered prose, not one string: paragraphs, a bold
   run, a short list. The subset is fixed in assistant-prose.js, so these are
   the only elements that can appear here. */
/* `> *` rather than `> p`, or the type selector outspecifies the sibling rule
   below it and the paragraphs run together with no gap at all. */
.asstp-line.is-los > *{margin:0}
.asstp-line.is-los > * + *{margin-top:8px}
.asstp-line.is-los ul,.asstp-line.is-los ol{margin:0;padding-left:19px;
  display:flex;flex-direction:column;gap:4px}
.asstp-line.is-los li{line-height:1.5}
.asstp-line.is-los strong{color:var(--text);font-weight:600}
.asstp-line.is-los code{font-family:var(--font-mono,ui-monospace,monospace);font-size:.92em}
.asstp-line.is-you{color:var(--text);font-weight:600}
.asstp-line.is-los{color:var(--text-2)}
.asstp-line.is-busy{color:var(--muted);font-style:italic}
/* An error is a sentence the server wrote, not a colour. It is legible
   because the person has to act on it. */
.asstp-line.is-error{color:var(--warn)}
.asstp-foot{display:flex;gap:10px;justify-content:flex-end;padding:11px 14px;
  border-top:1px solid var(--border);background:var(--surface-2)}

/* Editing a field in place. A modal over four cards hides the other three. */
.ap-field.is-editing{background:rgba(255,255,255,.09)}
.ap-finput{background:none;border:0;outline:0;color:var(--text);font:inherit;
  font-weight:600;min-width:90px;max-width:190px}
/* The mark, not a disabled input glyph — it reads as a capability waiting to
   arrive rather than a control that is broken. */
/* A softened accent, expressed as a COLOUR not an opacity — translucency is
   what made the old composer read as a disabled control. */
.composer-inner .ico{color:var(--accent);flex-shrink:0;display:grid;place-items:center}
.composer-text{flex:1;font-size:calc(var(--fs-task) + .5px);color:var(--muted);min-width:0;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.composer-badge{font-size:9.5px;font-weight:600;letter-spacing:.1em;text-transform:uppercase;
  color:#B9A6E8;background:rgba(var(--accent-rgb),.14);border-radius:7px;padding:4px 9px;flex-shrink:0}


/* ── Buttons / chips ────────────────────────────────────────────────── */
.btn{background:var(--surface-2);border-radius:10px;padding:0 15px;min-height:var(--btn-h);
  display:inline-flex;align-items:center;justify-content:center;gap:7px;font-size:13px;color:#fff;
  transition:transform var(--t-hover),background var(--t-hover),box-shadow var(--t-hover)}
.btn:hover{background:var(--surface-3);transform:translateY(-2px)}
.btn:active{transform:scale(.97)}
.btn-primary{background:var(--brand);font-weight:600;box-shadow:var(--glow)}
.btn-primary:hover{background:linear-gradient(180deg,#9163FF,#D09CFF)}
.btn-ghost{background:none;color:var(--text-2)}
.btn-ghost:hover{background:rgba(255,255,255,.05)}
.btn-danger{background:none;color:var(--danger)}
.btn-danger:hover{background:rgba(255,100,110,.1)}
.btn[disabled]{opacity:.42;pointer-events:none}
.chip{background:var(--surface-2);border-radius:99px;padding:6px 13px;font-size:12px;color:var(--text-2);
  transition:background var(--t-hover),color var(--t-hover)}
.chip:hover{background:var(--surface-3);color:var(--text)}
.chip[aria-pressed="true"]{background:var(--accent-soft);color:#fff;
  border-color:var(--accent-deep)}

/* ── Buckets ────────────────────────────────────────────────────────── */
.buckets{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:20px;
  margin-bottom:20px;align-items:start}
.bucket{background:var(--surface);border-radius:16px;padding:15px 14px 13px;box-shadow:var(--e2)}
.bucket.future{grid-column:1/-1}
.bucket-head{display:flex;align-items:center;gap:8px;margin-bottom:12px;padding-left:2px}
.bucket-head h2{font-size:11px;font-weight:600;letter-spacing:.16em;text-transform:uppercase;
  color:var(--label);margin:0}
.bucket-count{font-size:10px;font-weight:700;color:var(--muted);background:var(--surface-2);
  border:1px solid var(--border);border-radius:6px;padding:1px 7px;font-variant-numeric:tabular-nums}
.drop{min-height:46px;display:flex;flex-direction:column;gap:7px;border-radius:12px;
  transition:background var(--t-hover)}
.drop.is-empty{border:1.5px dashed var(--border);align-items:center;justify-content:center}
.drop.over{background:var(--accent-soft);outline:2px dashed rgba(var(--accent-rgb),.45);outline-offset:3px}
.empty{color:var(--muted);font-size:12px;font-style:italic;padding:13px 4px;
  text-align:center;pointer-events:none;line-height:1.5}

/* ── Task card ──────────────────────────────────────────────────────
   Title and metadata read as ONE unit: the meta line sits directly beneath
   the title, dot-separated, and is omitted entirely when there is nothing to
   say. Actions form a single vertically-centred group on the right. */
/* The card is a COLUMN: the task row, then its steps beneath it.
   The three-column grid moved to `.t-row`, so an expanded step panel sits
   under the whole row rather than becoming a fourth grid cell. */
.task{position:relative;display:block;background:var(--task);border-radius:12px;
  padding:10px 10px 10px 16px;overflow:hidden;
  box-shadow:0 1px 3px rgba(0,0,0,.16);
  transition:background var(--t-hover),box-shadow var(--t-hover),transform var(--t-hover)}
.t-row{display:grid;grid-template-columns:auto minmax(0,1fr) auto;
  align-items:center;gap:11px}
.task:hover{background:var(--task-hover);box-shadow:var(--e2)}
.task.dragging{opacity:.45}
.task::before{content:'';position:absolute;left:0;top:0;bottom:0;width:3px;
  background:var(--p-marker,var(--p-someday))}

/* Priority. Urgent alone earns an outline — and a soft one, so it reads as
   important rather than as an invalid form field. */
.task.pri-urgent{--p-marker:var(--p-urgent);background:linear-gradient(90deg,var(--p-urgent-bg),transparent 46%),var(--task);
  box-shadow:inset 0 0 0 1px rgba(240,86,110,.42),0 1px 3px rgba(0,0,0,.16)}
.task.pri-urgent:hover{box-shadow:inset 0 0 0 1px rgba(240,86,110,.6),var(--e2)}
.task.pri-high{--p-marker:var(--p-high);background:linear-gradient(90deg,var(--p-high-bg),transparent 40%),var(--task)}
.task.pri-medium{--p-marker:var(--p-medium)}
.task.pri-low{--p-marker:var(--p-low)}
.task.pri-someday{--p-marker:var(--p-someday)}
.task.pri-someday .t-title{color:var(--text-2)}

.t-tick{width:20px;height:20px;flex-shrink:0;border-radius:50%;
  border:1.6px solid var(--muted);background:none;position:relative;
  transition:border-color var(--t-hover),background var(--t-hover),transform var(--d-fast) var(--e-out)}
.t-tick:hover{border-color:var(--ok);transform:scale(1.08)}
.t-tick:active{transform:scale(.92)}
.t-main{min-width:0}
.t-title{display:block;width:100%;text-align:left;font-size:var(--fs-task);line-height:1.4;
  color:var(--text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
  transition:color var(--t-hover)}
.t-title:hover{color:#fff}
.t-meta{display:flex;align-items:center;gap:6px;margin-top:3px;font-size:11px;
  color:var(--muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.tm-sep{opacity:.45}
.tm-area{color:var(--text-2)}
.tm-due{color:var(--text-2)}
.tm-legacy{font-style:italic;opacity:.85}
.tm-steps.is-all{color:var(--ok)}

/* ── Inline steps ──
   A checklist inside a task, not a list of tasks. Everything here is quieter
   and smaller than the parent row, because a step is subordinate to it. */
.tm-steps{display:inline-flex;align-items:center;gap:3px;border:0;background:none;
  padding:0;font:inherit;color:inherit;cursor:pointer;border-radius:5px}
.tm-steps:hover{color:var(--text-2)}
.tm-steps-caret{width:0;height:0;border-left:3.5px solid transparent;
  border-right:3.5px solid transparent;border-top:4px solid currentColor;
  opacity:.7;transition:rotate var(--t-hover)}
.tm-steps[aria-expanded="true"] .tm-steps-caret{rotate:180deg}

/* ── ONE step control, ONE row grid ──
   Four different controls existed before this: 15px, 16px, 15px and 16.7px,
   each pushing the text column to a different x. The controls were vertically
   centred to the pixel; the raggedness was horizontal. Tokens fix the size in
   one place and the grid fixes the columns, so every row — completed, current,
   locked preview, or an editor row — shares one left edge. */
:root{
  --step-tick:18px;          /* the control, everywhere */
  --step-glyph:11px;         /* the checkmark inside it */
  --step-gap:9px;            /* control to text */
  --step-row-pad:2px;        /* the row's own left padding */
  /* Where step TEXT begins. Everything that is not a step row — the Current
     and Next labels, "N more steps", the ready line, the add field — hangs off
     this one value, so they cannot drift 2px away from the text they belong to.
     Measured: all three step texts start at exactly this x. */
  --step-text:calc(var(--step-row-pad) + var(--step-tick) + var(--step-gap));
}
.step-tick{width:var(--step-tick);height:var(--step-tick);flex-shrink:0;
  border-radius:6px;border:1.5px solid var(--border-strong);background:none;
  display:grid;place-items:center;color:transparent;cursor:pointer;padding:0;
  transition:background var(--t-hover),border-color var(--t-hover),color var(--t-hover)}
.step-tick svg{width:var(--step-glyph);height:var(--step-glyph);display:block}
.step-tick:not([disabled]):hover{border-color:var(--ok)}
/* Completed: a filled control with a CENTRED, legible tick — not a green block
   with a mark hidden in the corner. The glyph is 61% of the box. */
.step-tick.is-done{background:var(--ok);border-color:var(--ok);color:#07231d}
.step-tick[disabled]{cursor:default}
.step-tick[disabled].is-done{opacity:.78}
/* Locked: a dashed outline of the SAME size, so the text column does not move.
   Not a disabled checkbox — this step is not unavailable, it is not yet. */
.step-tick.is-locked{border-style:dashed;border-color:var(--border);
  background:none;cursor:default}

.ts-row{display:grid;grid-template-columns:var(--step-tick) minmax(0,1fr) auto;
  align-items:center;gap:var(--step-gap);
  padding:3px 6px 3px var(--step-row-pad);border-radius:7px;
  transition:background var(--t-hover)}
.ts-row:hover{background:rgba(255,255,255,.035)}
.ts-name{min-width:0;background:none;border:none;font-size:12.5px;
  color:var(--text-2);padding:2px 4px;margin-left:-4px;border-radius:5px;
  font-family:inherit;line-height:1.45}
.ts-name:focus{outline:none;background:var(--surface-2);color:var(--text)}
.ts-row.is-done .ts-name{color:var(--muted);text-decoration:line-through;
  text-decoration-color:var(--border-strong)}
.ts-del{width:24px;height:24px;border:0;background:none;border-radius:6px;
  color:transparent;font-size:15px;line-height:1;cursor:pointer;padding:0;
  display:grid;place-items:center;transition:background var(--t-hover),color var(--t-hover)}
.ts-row:hover .ts-del,.ts-del:focus-visible{color:var(--muted)}
.ts-del:hover{background:rgba(255,100,110,.12);color:var(--danger)}
.ts-trail-spacer{width:24px;height:1px}

.t-steps{margin:8px 0 2px 31px;display:flex;flex-direction:column;gap:1px}
.t-steps[hidden]{display:none}
.ts-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:1px}

/* ── Sequence hierarchy ──
   Three levels, and only the middle one is loud: what you did (quiet), what to
   do now (clear), what comes after (a preview). Nothing here is bright — a
   step list in four colours reads as an alert, not as guidance. */
.ts-group{display:flex;flex-direction:column;gap:2px;margin-top:7px}
.ts-label{font-size:9.5px;font-weight:700;letter-spacing:.09em;text-transform:uppercase;
  color:var(--muted);padding-left:var(--step-text)}
.ts-group-current .ts-label{color:var(--accent)}

.ts-done-list .ts-name{color:var(--muted)}
.ts-done-list .ts-row{opacity:.72}
.ts-done-list .ts-row:hover{opacity:1}

/* Current: the only step with any weight to it. The text stays dominant over
   the label above it. */
.ts-current{background:var(--surface)}
.ts-current .ts-name{color:var(--text);font-size:13px;font-weight:500}
.ts-current .step-tick{border-color:var(--accent)}
.ts-current .step-tick:hover{background:rgba(0,217,163,.10)}

.ts-next .ts-name-locked{background:none;border:0;padding:2px 4px;margin-left:-4px;
  text-align:left;font:inherit;font-size:12.5px;color:var(--text-2);cursor:pointer;
  border-radius:5px;min-width:0;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}
.ts-next .ts-name-locked:hover{color:var(--text);background:var(--surface)}

/* Aligned to the TEXT column, not the card edge — it is another step, so it
   starts where the step names start. */
.ts-more{align-self:flex-start;margin:6px 0 0 var(--step-text);
  border:0;background:none;padding:2px 0;font-size:11px;color:var(--muted);
  cursor:pointer;border-radius:5px;text-decoration:underline;text-underline-offset:2px;
  text-decoration-color:var(--border-strong)}
.ts-more:hover{color:var(--text-2)}

.ts-add{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:6px;align-items:center;
  margin:6px 0 0 var(--step-text)}
.ts-new{min-width:0;background:var(--surface);border:1px solid transparent;
  border-radius:7px;padding:5px 8px;font-size:12.5px;color:var(--text);font-family:inherit}
.ts-new::placeholder{color:var(--muted)}
.ts-new:focus{outline:none;border-color:var(--accent);background:var(--surface-2)}
.ts-add-btn{border:0;background:var(--surface-2);color:var(--text-2);border-radius:7px;
  padding:5px 11px;font-size:11.5px;font-weight:600;cursor:pointer;
  transition:background var(--t-hover),color var(--t-hover)}
.ts-add-btn:hover{background:var(--accent-soft);color:var(--text)}

/* Ready to finish. A note, not a trophy. */
.ts-ready{margin:7px 0 0 var(--step-text);font-size:11px;
  color:var(--ok);font-weight:600;letter-spacing:.01em}
.ts-error{margin:6px 0 0 var(--step-text);font-size:11px;
  color:var(--danger)}

/* Ready to finish: the ordinary checkbox, outlined in the success colour. It
   must be the SAME control a task without steps has — reaching the end of the
   sequence hands back the normal way to finish, it does not invent one. */
.task.is-ready .t-tick,.t-tick.is-ready{border-color:var(--ok)}
.t-tick.is-ready:hover{background:rgba(0,217,163,.12)}

/* The parent control while steps remain: a progress ARC, 22px, on the same
   centre line as an ordinary checkbox so the two never appear to jump.
   No number inside it — 7.5px digits in a 20px circle were unreadable, and the
   `1/3 steps` chip in the meta line already carries the count legibly. */
.t-tick.is-blocked{width:22px;height:22px;border:0;border-radius:50%;
  position:relative;cursor:pointer;
  background:conic-gradient(var(--ok) calc(var(--step-frac) * 360deg),
    var(--surface-3) 0)}
.t-tick.is-blocked::after{content:'';position:absolute;inset:3px;border-radius:50%;
  background:var(--task);transition:background var(--t-hover)}
.task:hover .t-tick.is-blocked::after{background:var(--task-hover)}
/* A 44px hit area on a 22px control, without changing the layout it sits in. */
.t-tick.is-blocked::before{content:'';position:absolute;inset:-11px;border-radius:50%}
.t-tick.is-blocked:focus-visible{outline:2px solid var(--accent);outline-offset:3px}

/* Collapsed cards say it in words too, so the state does not depend on
   noticing a ring — or on expanding the task to find out. */
.tm-ready{color:var(--ok);font-weight:650}

/* One aligned action group. Every control is the same size, centred on the
   card's vertical axis — nothing floats at the top edge. */
.t-actions{display:flex;align-items:center;gap:2px;flex-shrink:0;
  opacity:0;transition:opacity var(--t-hover)}
.task:hover .t-actions,.task:focus-within .t-actions{opacity:1}
.t-btn{width:30px;height:30px;border-radius:8px;display:grid;place-items:center;
  color:var(--muted);transition:background var(--t-hover),color var(--t-hover)}
.t-btn:hover{background:rgba(255,255,255,.08);color:#fff}
.t-grip{width:22px;height:30px;display:grid;place-items:center;color:var(--muted);
  cursor:grab;opacity:.5}
.t-grip:active{cursor:grabbing}

/* The quiet save indicator — silent by default, a whisper when it speaks. */
.save-state{position:fixed;bottom:calc(var(--composer-h) + 34px);left:50%;
  transform:translateX(-50%) translateY(6px);z-index:280;
  background:var(--surface-2);border-radius:99px;padding:6px 15px;font-size:11.5px;
  color:var(--text-2);box-shadow:var(--e2);opacity:0;pointer-events:none;
  transition:opacity var(--d-base) var(--e-out),transform var(--d-base) var(--e-out)}
.save-state.is-on{opacity:1;transform:translateX(-50%) translateY(0)}

/* ── History ────────────────────────────────────────────────────────── */
.history{max-width:840px}
.hist-row{display:grid;grid-template-columns:18px 1fr auto;align-items:center;gap:12px;
  padding:11px 12px;border-radius:10px;cursor:pointer;border-bottom:1px solid var(--hairline);
  transition:background var(--t-hover)}
.hist-row:last-child{border-bottom:none}
.hist-row:hover{background:rgba(255,255,255,.035)}
.hist-tick{color:var(--ok);font-size:12px;opacity:.75}
.hist-title{font-size:13px;color:var(--text-2);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.hist-meta{display:flex;gap:10px;align-items:center;font-size:10.5px;color:var(--muted)}
.hist-when.unknown{font-style:italic;opacity:.7}
.hist-end{font-size:12px;color:var(--muted);margin:16px 0 0;padding-left:2px}

/* ── Placeholder pages ──────────────────────────────────────────────── */
.placeholder{max-width:560px;padding:44px 0 0}
.placeholder .ph-ico{width:56px;height:56px;border-radius:16px;background:var(--surface);
  display:grid;place-items:center;color:var(--accent);margin-bottom:22px;box-shadow:var(--e1)}
.placeholder h2{font-size:19px;font-weight:700;letter-spacing:-.3px;margin:0 0 10px}
.placeholder p{font-size:14px;line-height:1.7;color:var(--text-2);margin:0 0 10px}
.placeholder .ph-note{font-size:12.5px;color:var(--muted);margin-top:20px;padding-top:18px;
  border-top:1px solid var(--hairline);line-height:1.65}

/* ── Settings ────────────────────────────────────────────────────────────
   A category column and one panel. Settings is an administrative surface:
   the job is to find a control, understand what it does, and change it —
   so consistency and scanability beat expression everywhere on this page. */

/* The Habits rail belongs to Today, not here. Same mechanism the Calendar,
   Projects, Library and Diary already use to claim the full width. */
body:has(.set-page) .main-wrap{grid-template-columns:minmax(0,1fr) 0}
body:has(.set-page) .rail{display:none}

:root{--set-nav-w:196px}
.set-page{display:grid;grid-template-columns:var(--set-nav-w) minmax(0,1fr);
  gap:38px;max-width:1080px;align-items:start;
  /* The one positioned ancestor the shared dropdown anchors inside. */
  position:relative}

/* ── Category column ──────────────────────────────────────────────────── */
.set-nav{display:flex;flex-direction:column;gap:1px;position:sticky;top:22px}
.set-nav-item{text-align:left;padding:9px 12px;border-radius:9px;font-size:13px;
  color:var(--muted);transition:background var(--t-hover),color var(--t-hover)}
.set-nav-item:hover{background:rgba(255,255,255,.045);color:var(--text-2)}
.set-nav-item:focus-visible{outline:0;box-shadow:0 0 0 2px var(--accent)}
.set-nav-item[aria-current="page"]{background:var(--accent-soft);color:#fff;font-weight:600}

/* ── Panel ────────────────────────────────────────────────────────────── */
.set-main{min-width:0;display:flex;flex-direction:column;gap:26px}
.set-head{display:flex;flex-direction:column;gap:5px}
.set-head h2{font-size:19px;font-weight:600;color:var(--text);margin:0;letter-spacing:-.01em}
.set-head p{margin:0;font-size:12.5px;color:var(--muted);line-height:1.6;max-width:64ch}

.set-sec{display:flex;flex-direction:column;gap:9px}
/* The group title sits ABOVE its card. A card inside a card is just noise. */
.set-sec-h{margin:0;font-size:10.5px;font-weight:600;letter-spacing:.13em;
  text-transform:uppercase;color:var(--muted)}
.set-card{background:var(--surface);border:1px solid var(--border);
  border-radius:var(--radius);overflow:hidden}
.set-sec-note{margin:0;font-size:12px;color:var(--muted);line-height:1.65;max-width:72ch}

/* Rows are divided, not stacked as separate cards, so a group reads as one
   object rather than as five things that happen to be near each other. */
.set-row{display:flex;align-items:center;justify-content:space-between;gap:22px;
  padding:15px 18px}
.set-row + .set-row{border-top:1px solid var(--hairline)}
.set-row-text{min-width:0;display:flex;flex-direction:column;gap:4px}
.set-label{font-size:13px;font-weight:500;color:var(--text)}
.set-desc{font-size:12px;color:var(--muted);line-height:1.6;max-width:62ch}
.set-desc b{color:var(--text-2);font-weight:600}
.set-ctl{flex-shrink:0;display:flex;align-items:center;gap:9px}
.set-row-full{display:block}

/* Read-only facts: two columns where there is room, one where there is not. */
.set-facts{display:grid;grid-template-columns:repeat(auto-fit,minmax(210px,1fr));
  gap:2px 26px}
.set-fact{display:flex;flex-direction:column;gap:3px;padding:5px 0}
.set-fact-k{font-size:10.5px;font-weight:600;letter-spacing:.1em;
  text-transform:uppercase;color:var(--muted)}
.set-fact-v{font-size:13.5px;color:var(--text);word-break:break-word}
.set-role{font-size:11px;color:var(--muted);text-transform:capitalize}

/* ── Status ───────────────────────────────────────────────────────────── */
.set-badge{flex-shrink:0;font-size:11px;font-weight:600;border-radius:7px;
  padding:4px 10px;white-space:nowrap}
.set-badge.is-on{color:var(--ok);background:color-mix(in srgb,var(--ok) 14%,transparent)}
.set-badge.is-warn{color:#F0913D;background:color-mix(in srgb,#F0913D 14%,transparent)}
.set-badge.is-off{color:var(--muted);background:var(--surface-2)}
.set-badge.is-later{color:var(--muted);background:var(--surface-2);font-weight:500}
.set-stamp{font-size:12px;color:var(--text-2);font-variant-numeric:tabular-nums}

/* ── Editable list rows: areas and habits ─────────────────────────────── */
.set-item{gap:12px;justify-content:flex-start}
.set-item-dot{width:8px;height:8px;border-radius:50%;background:var(--accent);flex-shrink:0}
.set-item-dot-ok{background:var(--ok)}
.set-item-name{flex:1;min-width:0;background:transparent;border:1px solid transparent;
  border-radius:8px;padding:6px 9px;font-size:13.5px;color:var(--text);
  transition:background var(--t-hover),border-color var(--t-hover)}
/* A memory is a sentence and may be a long one. It is a textarea so it can
   wrap, sized to its content by app.js — never scrolled sideways, because the
   whole purpose of this screen is that the person can read exactly what is
   believed about them. */
.set-mem-fact{resize:none;overflow:hidden;line-height:1.45;font-family:inherit;
  min-height:0;word-break:break-word}
.set-item-name:hover{background:rgba(255,255,255,.035)}
.set-item-name:focus{outline:0;background:var(--surface-2);border-color:var(--accent)}
/* Fixed width, or the column of dropdowns beside it steps in and out by
   however long "3 days running" happens to be against "No streak yet". */
.set-item-meta{font-size:11.5px;color:var(--muted);flex:0 0 108px;text-align:right;
  font-variant-numeric:tabular-nums}
/* Named, not a bare arrow. The old down-arrow gave no clue what it would do. */
.set-item-x{flex-shrink:0;font-size:11.5px;color:var(--muted);border-radius:7px;
  padding:5px 10px;transition:background var(--t-hover),color var(--t-hover)}
.set-item-x:hover{background:color-mix(in srgb,var(--danger) 16%,transparent);
  color:var(--danger)}
.set-item-x:focus-visible{outline:0;box-shadow:0 0 0 2px var(--accent)}
/* Same shape as Remove, none of its meaning: opening a thing is not a
   destructive act and must not borrow the destructive colour. */
.set-item-go{flex-shrink:0;font-size:11.5px;color:var(--muted);border-radius:7px;
  padding:5px 10px;transition:background var(--t-hover),color var(--t-hover)}
.set-item-go:hover{background:var(--surface-2);color:var(--text)}
.set-item-go:focus-visible{outline:0;box-shadow:0 0 0 2px var(--accent)}
.set-item .cf-menu-btn{flex:0 0 170px;height:32px}

.set-add{display:flex;gap:9px}
.set-add .input{flex:1;background:var(--surface-2)}
.set-empty{margin:0;font-size:12.5px;color:var(--muted);line-height:1.65;max-width:72ch}

/* ── Colour legend ────────────────────────────────────────────────────── */
.set-legend{display:flex;flex-direction:column;gap:11px}
.set-legend-row{display:grid;grid-template-columns:18px 62px 1fr;align-items:center;gap:12px}
.set-sw{width:18px;height:18px;border-radius:6px}
.set-sw-brand{background:var(--brand)}
.set-sw-ok{background:var(--ok)}
.set-sw-warn{background:#F0913D}
.set-sw-danger{background:var(--danger)}
.set-legend-n{font-size:12.5px;font-weight:600;color:var(--text)}
.set-legend-m{font-size:12px;color:var(--muted);line-height:1.5}

/* ── Prose and diagnostics ────────────────────────────────────────────── */
.set-prose{font-size:12.5px;color:var(--text-2);line-height:1.7}
.set-prose p{margin:0 0 10px}
.set-prose ul{margin:0;padding-left:18px}
.set-prose li{margin-bottom:6px}
.set-prose b{color:var(--text)}
.set-build{font-size:11.5px;color:var(--text-2);background:var(--surface-2);
  border-radius:6px;padding:4px 9px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace}
/* Scopes and error strings are real, and belong behind a disclosure rather
   than in the middle of a page someone is trying to read. */
.set-diag summary{font-size:12px;color:var(--muted);cursor:pointer;
  list-style:none;display:inline-flex;align-items:center;gap:7px}
.set-diag summary::-webkit-details-marker{display:none}
.set-diag summary::before{content:'';width:5px;height:5px;border-right:1.5px solid currentColor;
  border-bottom:1.5px solid currentColor;transform:rotate(-45deg);transition:transform var(--t-hover)}
.set-diag[open] summary::before{transform:rotate(45deg)}
.set-diag summary:hover{color:var(--text-2)}
.set-diag-body{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));
  gap:4px 26px;margin-top:12px;padding-top:12px;border-top:1px solid var(--hairline)}

/* ── Narrow ───────────────────────────────────────────────────────────── */
@media (max-width:900px){
  .set-page{grid-template-columns:1fr;gap:20px}
  /* The column becomes a strip that scrolls sideways — a squeezed desktop
     column is worse than either shape. */
  .set-nav{position:static;flex-direction:row;gap:4px;overflow-x:auto;
    padding-bottom:4px;scrollbar-width:none;-webkit-overflow-scrolling:touch}
  .set-nav::-webkit-scrollbar{display:none}
  .set-nav-item{flex:0 0 auto;padding:8px 14px;white-space:nowrap}
  .set-row{flex-direction:column;align-items:stretch;gap:11px}
  .set-ctl{justify-content:flex-start;flex-wrap:wrap}
  .set-item{flex-direction:row;flex-wrap:wrap;align-items:center}
  .set-item-name{flex:1 1 100%;order:-1}
  .set-item-meta{flex:0 0 auto;text-align:left}
  .set-item .cf-menu-btn{flex:1 1 auto}
  .set-legend-row{grid-template-columns:18px 1fr;gap:8px}
  .set-legend-m{grid-column:2}
}

.seg{display:flex;background:var(--surface-2);border-radius:9px;padding:3px;gap:2px;flex-shrink:0}
.seg button{padding:6px 13px;border-radius:7px;font-size:12px;color:var(--text-2);
  transition:background var(--t-hover),color var(--t-hover)}
.seg button:focus-visible{outline:0;box-shadow:0 0 0 2px var(--accent)}
.seg button[aria-pressed="true"]{background:var(--accent-soft);color:#fff;font-weight:600}
.page-actions{margin-left:auto;display:flex;gap:8px;align-items:center;flex-shrink:0}
.pa-count{font-size:10px;font-weight:700;color:var(--muted);background:var(--surface-2);
  border-radius:6px;padding:1px 7px;font-variant-numeric:tabular-nums}

/* ── Dialog / panel / menu ──────────────────────────────────────────── */
.scrim{position:fixed;inset:0;background:rgba(10,8,16,.62);z-index:150;
  animation:fade var(--d-base) var(--e-out) backwards}
.panel{position:fixed;top:0;right:0;bottom:0;width:min(460px,100%);background:var(--rail);
  z-index:151;padding:26px 24px;overflow-y:auto;box-shadow:var(--e3);
  animation:slidein var(--d-slow) var(--e-out) backwards}
@keyframes slidein{from{transform:translateX(24px);opacity:0}to{transform:none;opacity:1}}
.panel h3{font-size:11px;letter-spacing:.16em;text-transform:uppercase;color:var(--label);
  margin:0 0 16px;font-weight:600}
.field{margin-bottom:15px}
.field label{display:block;font-size:10.5px;letter-spacing:.1em;text-transform:uppercase;
  color:var(--muted);margin-bottom:6px;font-weight:600}
.input,.ta,.sel{width:100%;background:var(--surface);border:1px solid transparent;border-radius:10px;
  padding:10px 12px;font-size:14px;
  transition:border-color var(--t-hover),box-shadow var(--t-hover)}
.input:focus,.ta:focus,.sel:focus{outline:none;border-color:var(--accent);
  box-shadow:0 0 0 3px rgba(var(--accent-rgb),.15)}
.ta{min-height:96px;resize:vertical;line-height:1.6}
.row{display:flex;gap:10px}.row>*{flex:1;min-width:0}
.steps{display:flex;flex-direction:column;gap:6px;margin-bottom:10px}
.step{display:flex;align-items:center;gap:9px;background:var(--surface);border-radius:9px;
  padding:8px 10px;font-size:13px}
.step input[type=checkbox]{accent-color:var(--ok);width:15px;height:15px}
.step.done span{text-decoration:line-through;color:var(--muted)}
.step button{margin-left:auto;color:var(--muted);font-size:13px}
.panel-foot{display:flex;gap:8px;margin-top:24px;padding-top:18px;border-top:1px solid var(--hairline)}
.menu{position:fixed;z-index:200;background:var(--surface-2);border-radius:12px;padding:6px;
  box-shadow:var(--e3);min-width:196px;animation:rise var(--d-base) var(--e-out) backwards}
.menu-label{font-size:10px;letter-spacing:.12em;text-transform:uppercase;color:var(--muted);
  padding:7px 10px 5px}
.menu button{display:flex;width:100%;align-items:center;gap:9px;padding:9px 10px;border-radius:8px;
  font-size:13px;color:var(--text-2);text-align:left;
  transition:background var(--t-hover),color var(--t-hover)}
.menu button:hover{background:var(--accent-soft);color:#fff}
.menu button[disabled]{opacity:.4;pointer-events:none}
.menu kbd{margin-left:auto;font-size:10px;color:var(--muted);font-family:inherit}

/* ── Loading / error / toast ────────────────────────────────────────── */
.skeleton{background:linear-gradient(90deg,var(--surface) 8%,var(--surface-2) 18%,var(--surface) 28%);
  background-size:640px 100%;animation:shimmer 1.4s linear infinite;border-radius:12px;height:52px;
  margin-bottom:8px}
.spinner{width:17px;height:17px;border:2px solid rgba(255,255,255,.15);border-top-color:var(--accent);
  border-radius:50%;animation:spin .7s linear infinite;display:inline-block}
.state{max-width:460px;padding:40px 0;color:var(--text-2);font-size:14px;line-height:1.7}
.state b{display:block;color:var(--text);font-size:16px;font-weight:600;margin-bottom:8px}
.toast{position:fixed;bottom:calc(var(--composer-h) + 30px);left:50%;transform:translateX(-50%);
  background:var(--surface-2);border-radius:12px;padding:11px 18px;font-size:13px;box-shadow:var(--e3);
  z-index:300;animation:rise var(--d-base) var(--e-out) backwards;max-width:calc(100vw - 32px)}
/* A toast carrying a verb. One action, never two — a message with a menu in it
   is a dialog wearing the wrong clothes. */
.toast.has-action{display:flex;align-items:center;gap:14px;padding-right:8px}
.toast-action{border:0;background:var(--accent-soft);color:var(--text);
  border-radius:8px;padding:6px 13px;font-size:12.5px;font-weight:650;cursor:pointer;
  flex-shrink:0;transition:background var(--t-hover)}
.toast-action:hover{background:var(--accent)}
.toast-action:focus-visible{outline:2px solid var(--accent);outline-offset:2px}

/* ── Today subsection headings ──
   A label between two runs of cards, not a container around either. Quiet
   enough to read as structure rather than as a thing in its own right. */
.sub-head{font-size:9.5px;font-weight:700;letter-spacing:.09em;text-transform:uppercase;
  color:var(--muted);padding:2px 2px 0;margin-top:4px}
.sub-head:first-child{margin-top:0}
/* The gap above PROJECTS is what actually separates the two groups; the label
   names the separation rather than creating it. */
.sub-head[data-sub="projects"]{margin-top:14px}
.drop.is-empty .sub-head{display:none}

/* A project whose metadata could not be loaded. Stated, not hidden — the task
   is still project work and must not be mistaken for standalone. */
.tm-project.is-missing{color:var(--muted);font-style:italic;cursor:default;
  background:none;border:0;padding:0;font-size:inherit}
.toast.err{color:var(--danger)}

/* ── Update prompt (PWA) ────────────────────────────────────────────── */
.updater{position:fixed;bottom:calc(var(--composer-h) + 30px);left:50%;transform:translateX(-50%);
  z-index:310;background:var(--surface-2);border-radius:14px;padding:13px 15px 13px 18px;
  box-shadow:var(--e3);display:flex;align-items:center;gap:14px;max-width:calc(100vw - 32px);
  animation:rise var(--d-slow) var(--e-out) backwards}
.updater .u-text{font-size:13px;line-height:1.5}
.updater .u-text b{display:block;font-weight:600}
.updater .u-text span{color:var(--muted);font-size:11.5px}

/* ── Mobile shell ───────────────────────────────────────────────────── */
.mobile-bar{display:none}

/* ── Modal — centred, never a drawer ────────────────────────────────
   A dialog over the board keeps context visible; the old right drawer covered
   the rail and compressed the workspace into an admin form. */
.modal-scrim{position:fixed;inset:0;background:rgba(9,8,14,.66);z-index:200;
  backdrop-filter:blur(3px);-webkit-backdrop-filter:blur(3px)}
body.modal-open{overflow:hidden}
.modal{position:fixed;z-index:201;left:50%;top:50%;transform:translate(-50%,-50%);
  width:min(620px,calc(100vw - 48px));max-height:min(86vh,780px);
  display:flex;flex-direction:column;background:var(--rail);border-radius:18px;
  box-shadow:var(--e3),0 0 0 1px rgba(255,255,255,.05);overflow:hidden}
.modal-narrow{width:min(480px,calc(100vw - 48px))}

.m-head{display:flex;align-items:flex-start;gap:12px;padding:20px 20px 14px;
  border-bottom:1px solid var(--hairline);flex-shrink:0}
.m-title{flex:1;min-width:0;background:none;border:none;resize:none;overflow:hidden;
  font-size:19px;font-weight:600;line-height:1.35;color:var(--text);padding:0;
  font-family:inherit}
.m-title:focus{outline:none}
.m-title::placeholder{color:var(--muted);font-weight:450}
.m-tick{width:24px;height:24px;flex-shrink:0;margin-top:2px;border-radius:50%;
  border:1.8px solid var(--muted);display:grid;place-items:center;color:transparent;
  transition:background var(--t-hover),border-color var(--t-hover),color var(--t-hover)}
.m-tick:hover{border-color:var(--ok)}
.m-tick.is-done{background:var(--ok);border-color:var(--ok);color:#08221c}
.m-close{width:30px;height:30px;flex-shrink:0;border-radius:8px;color:var(--muted);
  font-size:20px;line-height:1;display:grid;place-items:center;
  transition:background var(--t-hover),color var(--t-hover)}
.m-close:hover{background:rgba(255,255,255,.07);color:#fff}

.m-body{padding:18px 20px;overflow-y:auto;flex:1;min-height:0;
  display:flex;flex-direction:column;gap:14px}
.m-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px}
.m-field{display:flex;flex-direction:column;gap:6px;min-width:0}
.m-field>span{font-size:10.5px;font-weight:600;letter-spacing:.1em;text-transform:uppercase;
  color:var(--muted)}
.m-input{width:100%;background:var(--surface);border:1px solid transparent;border-radius:10px;
  padding:9px 11px;font-size:13.5px;color:var(--text);font-family:inherit;
  transition:border-color var(--t-hover),box-shadow var(--t-hover)}
.m-input:focus{outline:none;border-color:var(--accent);box-shadow:0 0 0 3px rgba(var(--accent-rgb),.15)}
.m-notes{min-height:84px;resize:vertical;line-height:1.6}
/* The priority control wears the priority it sets. */
.pri-select[data-pri="urgent"]{color:var(--p-urgent-ink);border-color:rgba(240,86,110,.4)}
.pri-select[data-pri="high"]{color:var(--p-high-ink);border-color:rgba(240,145,61,.34)}
.pri-select[data-pri="medium"]{color:var(--p-medium-ink)}
.pri-select[data-pri="low"]{color:var(--p-low-ink)}
.pri-select[data-pri="someday"]{color:var(--p-someday-ink)}

.m-legacy{display:flex;align-items:center;gap:9px;background:var(--surface);
  border-radius:10px;padding:9px 12px;font-size:12.5px}
.m-legacy-label{font-size:10px;letter-spacing:.09em;text-transform:uppercase;color:var(--muted)}
.m-legacy-value{color:var(--text);font-style:italic}
.m-legacy-note{margin-left:auto;font-size:10.5px;color:var(--muted)}

.m-steps-block{display:flex;flex-direction:column;gap:8px}
.m-steps-head{display:flex;align-items:center;gap:8px;font-size:10.5px;font-weight:600;
  letter-spacing:.1em;text-transform:uppercase;color:var(--muted)}
.m-steps-count{margin-left:auto;letter-spacing:0;font-size:10px;background:var(--surface);
  border-radius:6px;padding:1px 7px}
/* The editor's step list is the SAME list Today draws: same `.ts-row` grid,
   same `.step-tick`. Only its behaviour differs — every step is tickable here,
   in any order. It used to carry its own 16.7px control and its own spacing,
   which is half of why the two lists never looked like the same thing. */
.m-steps{display:flex;flex-direction:column;gap:4px}
.m-steps .ts-row{background:var(--surface);padding:6px 8px 6px 10px;border-radius:9px}
.m-steps .ts-name{font-size:13px;color:var(--text)}
.m-steps .ts-row.is-done .ts-name{color:var(--muted)}
.m-step-add{margin-left:calc(var(--step-text) + 8px)}
/* The Add button is not decoration. Enter was the only way to commit a step,
   nothing said so, and typing one then clicking Save discarded it silently. */
/* A completed task, opened from history. It is not an ordinary edit and must
   not look like one — the bar says what happened and offers the one verb that
   makes sense next. */
/* The override confirmation. Inside the editor, not over it, so the task you
   are deciding about stays visible behind the question. */
/* FIXED, not absolute-inside-the-modal. `.modal` is `position:fixed` centred by
   a transform, and giving it `position:relative` to contain this silently
   overrode that and threw the dialog 310px off centre — a regression caught
   once already and guarded by test. A fixed overlay needs no containing block,
   and the editor still shows through behind it. */
.m-confirm{position:fixed;inset:0;z-index:210;display:grid;place-items:center;
  background:rgba(12,10,18,.72);padding:20px}
.m-confirm-card{background:var(--surface-2);border:1px solid var(--border-strong);
  border-radius:14px;box-shadow:var(--e3);padding:20px 22px;max-width:340px;
  display:flex;flex-direction:column;gap:8px}
.m-confirm-card h3{margin:0;font-size:15px;font-weight:650;color:var(--text)}
.m-confirm-card p{margin:0;font-size:12.5px;color:var(--text-2);line-height:1.5}
.m-confirm-acts{display:flex;flex-direction:column;gap:7px;margin-top:6px}
.m-confirm-acts .btn{width:100%;justify-content:center;text-align:center}

.m-done-bar{display:flex;align-items:center;justify-content:space-between;gap:12px;
  padding:9px 18px;background:rgba(0,217,163,.07);
  border-bottom:1px solid var(--hairline)}
.m-done-when{font-size:11.5px;color:var(--ok);font-weight:600}
.modal.is-completed .m-title{color:var(--muted)}

.m-step-add{display:flex;gap:8px;align-items:center}
.m-step-add .m-input{font-size:13px;flex:1;min-width:0}
#m-step-add{flex-shrink:0}

.m-soon{display:flex;align-items:center;gap:10px;font-size:11px;color:var(--muted);
  padding-top:2px}
.m-soon-tag{background:var(--surface);border-radius:6px;padding:3px 9px;font-size:10px;
  letter-spacing:.05em;text-transform:uppercase}

.m-foot{display:flex;align-items:center;gap:8px;padding:14px 20px;
  border-top:1px solid var(--hairline);flex-shrink:0}
.m-save-state{margin-left:auto;font-size:11.5px;color:var(--muted);
  font-variant-numeric:tabular-nums}
.m-danger{color:var(--danger)}
.m-danger:hover{background:rgba(255,100,110,.1)}

/* A line of explanation inside a dialog body. Not `.m-notes`, which is the
   notes TEXTAREA — one letter apart and completely different things. */
.m-note{font-size:12px;color:var(--muted);line-height:1.55;margin:0}

/* The book's OWN relationships, under its closed cover. Bounded to the cover's
   own width so it reads as belonging to the book rather than to the page. */
.bk-cover-rel{width:100%;max-width:520px;margin:0 auto;padding:0 4px 28px}
.bk-cover-rel .rel{margin-top:0}

/* Habit modal extras */
.h-stats{display:flex;gap:22px}
.h-stat{display:flex;flex-direction:column;gap:2px}
.h-stat .n{font-size:24px;font-weight:700;letter-spacing:-.5px;line-height:1;
  font-variant-numeric:tabular-nums}
.h-stat .l{font-size:11px;color:var(--muted)}
.h-days.is-hidden{display:none}
.h-day-row{display:flex;gap:5px}
.h-day{width:34px;height:34px;border-radius:9px;background:var(--surface);
  font-size:12px;color:var(--muted);
  transition:background var(--t-hover),color var(--t-hover)}
.h-day:hover{background:var(--surface-2);color:var(--text-2)}
.h-day[aria-pressed="true"]{background:var(--accent-soft);color:#fff;font-weight:600}
.h-recent{display:flex;flex-direction:column;gap:7px}
.h-recent-label{font-size:10.5px;font-weight:600;letter-spacing:.1em;text-transform:uppercase;
  color:var(--muted)}
.h-recent-row{display:flex;gap:4px}
.h-dot{width:14px;height:14px;border-radius:4px;background:var(--surface-2)}
.h-dot.is-done{background:var(--ok);opacity:.85}
.h-recent-note{font-size:10.5px;color:var(--muted)}

/* ── Habit ring ─────────────────────────────────────────────────────
   An SVG ring whose stroke carries progress, so a multi-count habit shows
   partial fill instead of an all-or-nothing tick. Green is completion, and
   completion only — it appears nowhere else in the app. */
.hb-add{margin-left:auto;width:24px;height:24px;border-radius:7px;color:var(--muted);
  font-size:17px;line-height:1;display:grid;place-items:center;
  transition:background var(--t-hover),color var(--t-hover)}
.hb-add:hover{background:rgba(255,255,255,.07);color:#fff}
.hb-count{margin-left:auto;font-size:10px;font-weight:700;color:var(--muted);
  background:var(--surface-2);border-radius:6px;padding:1px 7px;letter-spacing:0}
.hb-count+.hb-add{margin-left:6px}
.hb-list{display:flex;flex-direction:column;gap:1px}

/* ── The habits today did not ask for ────────────────────────────────────
 *
 * Folded, and quieter than the card above it — these are not on your plate,
 * and listing them beside the ones that are would make every Tuesday look like
 * a Sunday. Open it and the rows are the ordinary rows: same ring, same tick,
 * same streak, because a habit ticked here is ticked exactly as it would be on
 * its own day. */
.hb-rest{margin-top:8px;border-top:1px solid var(--hairline);padding-top:6px}
.hb-rest>summary{display:flex;align-items:center;gap:7px;cursor:pointer;
  padding:4px 6px;border-radius:8px;list-style:none;
  font-size:11.5px;color:var(--muted);transition:color var(--t-hover)}
.hb-rest>summary::-webkit-details-marker{display:none}
.hb-rest>summary:hover{color:var(--text-2)}
.hb-rest-l{flex:1;min-width:0}
.hb-rest-n{font-variant-numeric:tabular-nums;font-weight:600}
.hb-rest-chev{display:grid;place-items:center;transition:transform var(--t-hover)}
.hb-rest[open]>summary .hb-rest-chev{transform:rotate(90deg)}
/* Dimmer than a due habit, and never dimmer than legible. */
.hb-rest .hb-row{opacity:.72}
.hb-rest .hb-row:hover,.hb-rest .hb-row.is-done{opacity:1}

.hb-row{display:flex;align-items:center;gap:11px;padding:7px 6px;border-radius:10px;
  transition:background var(--t-hover)}
.hb-row:hover{background:rgba(255,255,255,.035)}
.hb-ring{position:relative;width:32px;height:32px;flex-shrink:0;display:grid;
  place-items:center;border-radius:50%}
/* Scoped with `>` and a class. The old rule was `.hb-ring svg`, which also
   matched the check icon nested in .hr-mark and forced it to 32px, absolute,
   inset:0 and rotate(-90deg) — a checkmark blown up 2.5x, stretched across the
   whole control and turned on its side. That was the "arrow tail". */
.hb-ring>svg.hr-svg{position:absolute;inset:0;width:32px;height:32px;
  transform:rotate(-90deg);pointer-events:none}
.hr-track{fill:none;stroke:rgba(255,255,255,.10);stroke-width:2.6}
/* pathLength="100" makes the dash units exact percentages. Hard-coding 2*PI*r
   was wrong: a browser renders <circle> as four Bezier arcs whose real length
   is 81.155, not 81.681, so every partial fill drifted and the seam misjoined.
   `butt` caps, not `round`: a round cap adds half the stroke width past each
   end, overshooting the seam when full and leaving a dot when empty. */
.hr-fill{fill:none;stroke:var(--ok);stroke-width:2.6;stroke-linecap:butt;
  transition:stroke-dashoffset var(--d-slow) var(--e-out),opacity var(--d-fast) linear}
.hr-fill.is-empty{opacity:0}
.hb-ring:hover .hr-track{stroke:rgba(0,217,163,.3)}

/* Centre content: a check when complete, the count while partial, nothing
   otherwise. Sized in its own right so no ancestor rule can stretch it. */
.hr-mark{position:relative;width:14px;height:14px;display:grid;place-items:center;
  color:var(--ok);opacity:0;transform:scale(.6);
  transition:opacity var(--d-fast) var(--e-out),transform var(--d-fast) var(--e-out)}
.hr-mark>svg{width:14px;height:14px;position:static;transform:none}
.hb-row.is-done .hr-mark{opacity:1;transform:scale(1)}
.hr-count{position:relative;font-size:9.5px;font-weight:700;color:var(--ok);
  font-variant-numeric:tabular-nums;letter-spacing:-.2px}
.hb-name{flex:1;min-width:0;text-align:left;font-size:var(--fs-rail);color:var(--text-2);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
  transition:color var(--t-hover)}
.hb-name:hover{color:var(--text)}
.hb-row.is-done .hb-name{color:var(--muted)}
.hb-prog{font-size:10.5px;color:var(--muted);font-variant-numeric:tabular-nums}
/* Beside the name, not pinned to the row's right edge: a badge floating at the
   far side is hard to associate with the habit it belongs to. Zero stays quiet. */
.hb-streak{font-size:10.5px;font-weight:700;color:var(--ok);flex-shrink:0;
  background:rgba(0,217,163,.12);border-radius:5px;padding:1px 5px;
  font-variant-numeric:tabular-nums}
.hb-streak.is-zero{color:var(--muted);background:transparent;padding:1px 0;
  opacity:.55;font-weight:500}
.hs-unit{font-weight:500;opacity:.75;font-size:9px;margin-left:.5px}

/* Completion acknowledgement: the tick fills, then the text softens, then the
   card collapses. Restrained on purpose — no confetti, no particles. */
.task.is-completing{background:linear-gradient(90deg,rgba(0,217,163,.10),transparent 55%),var(--task);
  box-shadow:inset 0 0 0 1px rgba(0,217,163,.30)}
.task.is-completing .t-title{color:var(--muted);text-decoration:line-through;
  text-decoration-color:rgba(0,217,163,.55);transition:color var(--d-fast) var(--e-out)}
.task.is-completing .t-meta,.task.is-completing .t-actions{opacity:.35;
  transition:opacity var(--d-fast) var(--e-out)}
.task.is-completing .t-tick{background:var(--ok);border-color:var(--ok);color:#08221c}

/* ── Task dragging ───────────────────────────────────────────────────
   The placeholder IS the proposed position. It is a real element in the real
   layout, so what the user sees during a drag is the future board rather than
   a hint about it. The lifted card keeps its exact size and follows the
   pointer; everything else slides via FLIP. */
.task-placeholder{border-radius:var(--radius);border:1.5px dashed var(--border-strong);
  background:rgba(var(--accent-rgb),.05);box-sizing:border-box;
  transition:height var(--d-fast) var(--e-out)}
.task.is-dragging{box-shadow:var(--e3);opacity:.96;cursor:grabbing;
  transform-origin:center;will-change:transform;border-color:var(--accent);
  /* Width and height are animated because the card CHANGES SIZE when it moves
     between buckets of different widths — Future spans the full row. Snapping
     between the two sizes reads as a glitch; easing reads as the card fitting
     itself to where it is going. */
  transition:width var(--d-base) var(--e-out),height var(--d-base) var(--e-out)}
body.is-dragging-task{cursor:grabbing;user-select:none;-webkit-user-select:none}
/* A card is grabbable anywhere that is not a control. */
.task{cursor:grab}
.task:active{cursor:grabbing}
body.is-dragging-task .task:not(.is-dragging){cursor:grabbing}
/* An emptied bucket must keep a target while the pointer is over it. */
.drop.is-empty{min-height:52px}
@media (prefers-reduced-motion:reduce){
  .task-placeholder{transition:none}
}

/* ══ EVENT EDITOR (Phase D3) ═════════════════════════════════════════════
   Every control is custom. The brief named the offenders to avoid — three-part
   date boxes, white checkbox squares, native select arrows, stark white
   date-picker surfaces, mismatched heights, browser-blue focus rings — so
   there is ONE input shell at ONE height, and the date/time pickers are ours. */
.modal-event{width:min(620px,calc(100vw - 40px));max-height:min(86vh,760px);
  display:flex;flex-direction:column;position:fixed}
.ev-head{display:flex;align-items:flex-start;gap:10px}
.ev-src-dot{width:9px;height:9px;border-radius:50%;flex-shrink:0;margin-top:13px;
  transition:background var(--t-hover)}
.ev-body{display:flex;flex-direction:column;gap:2px;overflow-y:auto;flex:1}
.ev-ro-note{margin:0 22px 4px;padding:9px 12px;border-radius:9px;font-size:12px;
  color:var(--text-2);background:var(--surface-2);border-left:2px solid var(--warn)}

.ev-row{display:grid;grid-template-columns:74px 1fr auto;gap:10px;align-items:center;
  padding:4px 0;min-height:44px}
.ev-row-top{align-items:start}
.ev-row-top .ev-lab{padding-top:11px}
.ev-lab{font-size:11.5px;font-weight:600;color:var(--muted);letter-spacing:.02em}
.ev-when{display:flex;flex-direction:column;gap:0;padding-bottom:6px;
  border-bottom:1px solid var(--hairline);margin-bottom:8px}

/* One shell, one height — nothing here should look like a different widget. */
.ev-ctl{height:38px;background:var(--surface-2);border:1px solid transparent;
  border-radius:10px;padding:0 12px;font-size:13px;color:var(--text);
  display:flex;align-items:center;gap:8px;width:100%;text-align:left;
  transition:border-color var(--t-hover),background var(--t-hover)}
.ev-ctl:hover{background:var(--surface-3)}
.ev-ctl:focus-visible{outline:none;border-color:var(--accent);
  box-shadow:0 0 0 3px rgba(var(--accent-rgb),.16)}
.ev-input{font:inherit}
.ev-input::placeholder{color:var(--muted)}
.ev-textarea{height:auto;min-height:76px;padding:10px 12px;resize:vertical;line-height:1.5}
.ev-time{width:88px;flex:0 0 88px;justify-content:center;
  font-variant-numeric:tabular-nums;transition:opacity var(--d-fast) var(--e-out),
    width var(--d-fast) var(--e-out),flex-basis var(--d-fast) var(--e-out)}
.ev-time.is-hidden{width:0;flex-basis:0;opacity:0;padding:0;border:none;overflow:hidden}

/* Select: our own chevron, the native arrow removed. */
.ev-select{position:relative;padding-right:34px}
.ev-select select{appearance:none;-webkit-appearance:none;background:none;border:none;
  width:100%;height:100%;font-size:13px;color:var(--text);cursor:pointer}
.ev-select select:focus{outline:none}
.ev-select option{background:var(--surface-2);color:var(--text)}
.ev-chev{position:absolute;right:13px;width:7px;height:7px;pointer-events:none;
  border-right:1.6px solid var(--muted);border-bottom:1.6px solid var(--muted);
  transform:rotate(45deg) translateY(-2px)}

/* Switch, not a checkbox square. */
.sw{display:inline-flex;align-items:center;gap:9px;height:38px;background:none;padding:0}
.sw-track{width:38px;height:21px;border-radius:11px;background:var(--surface-3);
  position:relative;transition:background var(--t-select);flex-shrink:0}
.sw-knob{position:absolute;top:2.5px;left:2.5px;width:16px;height:16px;border-radius:50%;
  background:var(--muted);transition:transform var(--t-select),background var(--t-select)}
.sw.is-on .sw-track{background:var(--accent-soft)}
.sw.is-on .sw-knob{transform:translateX(17px);background:var(--accent)}
.sw-lab{font-size:12.5px;color:var(--text-2)}

.ev-more{display:inline-flex;align-items:center;gap:9px;height:38px;margin-top:6px;
  font-size:12.5px;font-weight:600;color:var(--text-2);align-self:flex-start;
  transition:color var(--t-hover)}
.ev-more:hover{color:var(--text)}
.ev-more .ev-chev{position:static;transition:transform var(--d-base) var(--e-out)}
.ev-more.is-open .ev-chev{transform:rotate(225deg) translateY(-2px)}
/* Same trap as .hov: `display:flex` beats the UA's `[hidden]{display:none}`,
   so "More options" was permanently expanded despite the hidden attribute
   being set. Progressive disclosure that never disclosed anything. */
.ev-adv[hidden]{display:none}
.ev-adv{display:flex;flex-direction:column;gap:2px;padding-top:6px;
  border-top:1px solid var(--hairline);margin-top:6px}

.ev-guests{display:flex;flex-direction:column;gap:7px}
.ev-chips{display:flex;flex-wrap:wrap;gap:5px}
.ev-chip{display:inline-flex;align-items:center;gap:6px;font-size:11.5px;padding:3px 6px 3px 8px}
.ev-chip i{width:6px;height:6px;border-radius:50%;background:var(--muted)}
.rsvp-accepted i{background:var(--ok)}
.rsvp-declined i{background:var(--danger)}
.rsvp-tentative i{background:var(--warn)}
.ev-chip-x{font-size:14px;color:var(--muted);line-height:1;padding:0 2px;
  transition:color var(--t-hover)}
.ev-chip-x:hover{color:var(--text)}
.ev-hint{font-size:11px;color:var(--muted);line-height:1.45}

.ev-notify{display:flex;flex-wrap:wrap;gap:5px}
.ev-pill{height:28px;padding:0 11px;border-radius:8px;font-size:11.5px;font-weight:500;
  background:var(--surface-2);color:var(--muted);
  transition:background var(--t-hover),color var(--t-hover)}
.ev-pill:hover{color:var(--text-2)}
.ev-pill.is-on{background:var(--accent-soft);color:var(--text)}
.ev-seg{height:38px}
.ev-seg button{min-width:66px;font-size:12.5px}

.ev-swatches{display:flex;flex-wrap:wrap;gap:6px}
.ev-swatch{width:26px;height:26px;border-radius:8px;background:var(--sw);
  border:2px solid transparent;font-size:10px;color:var(--muted);
  display:grid;place-items:center;
  transition:transform var(--d-fast) var(--e-out),border-color var(--t-hover)}
.ev-swatch:hover{transform:scale(1.09)}
.ev-swatch.is-sel{border-color:var(--text)}

.ev-meet{display:inline-flex;align-items:center;height:38px;padding:0 14px;
  border-radius:10px;font-size:12.5px;font-weight:600;text-decoration:none;
  background:rgba(78,143,196,.16);color:var(--p-low-ink);
  transition:background var(--t-hover)}
.ev-meet:hover{background:rgba(78,143,196,.26)}

/* Life OS-only section, visually apart so it is never read as Google content. */
.ev-los{margin-top:12px;padding:12px;border-radius:10px;background:rgba(var(--accent-rgb),.05);
  border:1px dashed var(--border-strong);display:flex;flex-direction:column;gap:7px}
.ev-los-lab{font-size:9.5px;font-weight:700;letter-spacing:.14em;text-transform:uppercase;
  color:var(--label)}
.ev-los-list{display:flex;flex-wrap:wrap;gap:5px}

/* ── Custom date picker ── */
/* A floating HOST positions; it does not decorate.
   `.ev-pop` used to carry the background, border and shadow for whatever was
   dropped inside it, which is why the date picker had no skin of its own and
   turned invisible the moment it was rendered somewhere else. Now every panel
   brings its own surface and every host does one job. */
.ev-pop{position:absolute;z-index:200;
  animation:rise var(--d-fast) var(--e-out) backwards}
/* Each panel owns its own surface.
   The date picker used to have none, borrowing background, border and shadow
   from the `.ev-pop` host it was rendered into. When the host became the
   shared `.cf-pop` — which only positions — the picker kept its size and lost
   its skin: a correctly-placed, fully-interactive, completely invisible grid.
   A floating panel that depends on its container to be visible is one
   refactor away from being transparent. */
.dp{width:246px;padding:10px;border-radius:12px;
  background:var(--surface);border:1px solid var(--hairline);
  box-shadow:0 16px 38px rgba(0,0,0,.42)}
.dp-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:8px}
.dp-label{font-size:12.5px;font-weight:600;color:var(--text)}
.dp-nav{width:26px;height:26px;border-radius:7px;color:var(--text-2);font-size:15px;
  display:grid;place-items:center;transition:background var(--t-hover)}
.dp-nav:hover{background:var(--surface-3)}
.dp-dow{display:grid;grid-template-columns:repeat(7,1fr);gap:2px;margin-bottom:3px}
.dp-dow span{font-size:9.5px;font-weight:600;color:var(--muted);text-align:center}
.dp-grid{display:grid;grid-template-columns:repeat(7,1fr);gap:2px}
.dp-day{height:30px;border-radius:8px;font-size:12px;color:var(--text-2);
  display:grid;place-items:center;
  transition:background var(--t-hover),color var(--t-hover)}
.dp-day:hover{background:var(--surface-3);color:var(--text)}
.dp-day.is-out{opacity:.35}
.dp-day.is-today{color:var(--accent);font-weight:700}
.dp-day.is-sel{background:var(--accent);color:#fff;font-weight:600}

/* ── Custom time picker ── */
/* The time picker's styling now lives with the shared Calendar fields. What
   was here belonged to the 96-row list it replaced: a 120px column, and an
   option colour of --text-2 that was the reason some entries read as barely
   there. Leaving it would have half-applied to the new two-column picker. */
.tp::-webkit-scrollbar,.tp-col::-webkit-scrollbar{width:8px}
.tp::-webkit-scrollbar-thumb,.tp-col::-webkit-scrollbar-thumb{
  background:var(--border-strong);border-radius:4px}
.tp::-webkit-scrollbar-track,.tp-col::-webkit-scrollbar-track{background:transparent}

/* Save button progress — restrained, no spinner takeover. */
.btn.is-busy{opacity:.72;pointer-events:none;position:relative}

/* ── Add menu ── */
.cal-menu{min-width:236px;padding:6px}
.cal-menu button{display:flex;flex-direction:column;align-items:flex-start;gap:1px;
  width:100%;padding:9px 11px;border-radius:9px;text-align:left;
  transition:background var(--t-hover)}
.cal-menu button:hover{background:var(--surface-3)}
.cm-add-l{font-size:13px;color:var(--text);font-weight:500}
.cm-add-h{font-size:11px;color:var(--muted)}

@media (max-width:640px){
  .modal-event{left:0;right:0;bottom:0;top:auto;transform:none;width:100%;
    max-height:92vh;border-radius:20px 20px 0 0}
  .ev-row{grid-template-columns:1fr;gap:5px;padding:7px 0}
  .ev-row .ev-lab{padding-top:0}
  .ev-when{gap:4px}
  .ev-time{width:100%;flex-basis:auto}
  .ev-time.is-hidden{height:0;min-height:0}
  /* 44px minimum on touch. The desktop editor packs controls at 38px, which is
     right for a pointer and too small for a finger. */
  .ev-ctl,.sw,.ev-more,.ev-seg,.ev-meet{min-height:44px}
  .ev-ctl{height:44px}
  .ev-textarea{height:auto;min-height:92px}
  .ev-pill{height:38px;padding:0 14px}
  .ev-swatch{width:38px;height:38px}
  .m-close{width:44px;height:44px}
  /* 7 columns x 44px = 308px, which fits a 375px viewport, so there is no
     reason to compromise the date grid on touch. */
  .dp-day{height:44px}
  .tp-opt{height:44px}
  .ev-chip{min-height:30px}
  .ev-chip-x{min-width:24px;min-height:24px}

  /* On a phone there is no room to hang a panel off a control, so every
     floating surface docks to the bottom instead. `.cf-pop` was added later
     and missed this, which would have let a dropdown run off the screen. */
  .ev-pop,.cf-pop{position:fixed;left:12px!important;right:12px;top:auto!important;
    bottom:12px;width:auto}
  .dp{width:100%}
  .tp{width:100%;max-height:180px}
  .cf-menu{max-width:none;max-height:46vh}
}

/* ══ CALENDAR (Phase D3) ══════════════════════════════════════════════
   Month, Agenda, Plan. Source-calendar colour appears only INSIDE an event's
   own representation — a small edge or dot — never across the shell. Purple
   stays selection, red stays conflict, green stays completion. */
/* The header's own layout lives in the D4.5/D4.7 blocks below. This rule used
   to set `display:grid;grid-template-columns:1fr auto;align-items:start`; D4.2
   replaced the display but left align-items behind, and in the column flex
   container that replaced it `align-items` governs the HORIZONTAL axis — so
   every header row shrank to max-content and hugged the left of a full-width
   header. That is the entire "header is off-centre" bug. Do not reintroduce a
   default here: state alignment where the header layout is stated. */
.cal-head-main{display:flex;flex-direction:column;gap:8px}
.cal-period{display:flex;align-items:center;gap:6px}
.cal-period-label{font-size:15px;font-weight:600;color:var(--text-2);min-width:150px}
.cal-nav{width:30px;height:30px;border-radius:9px;color:var(--text-2);font-size:18px;
  display:grid;place-items:center;transition:background var(--t-hover),color var(--t-hover)}
.cal-nav:hover{background:var(--surface-2);color:var(--text)}
.cal-today{height:30px;font-size:12.5px}
.cal-head-side{display:flex;align-items:center;gap:10px}
.cal-modes button{min-width:72px}
.cal-layers{grid-column:1/-1;display:flex;gap:7px;flex-wrap:wrap}
.cal-layer{display:inline-flex;align-items:center;gap:6px;height:28px;padding:0 10px;
  border-radius:8px;font-size:12px;font-weight:500;color:var(--muted);
  background:var(--surface-2);border:1px solid transparent;
  transition:color var(--t-hover),border-color var(--t-hover),background var(--t-hover)}
.cal-layer:hover{color:var(--text-2)}
.cal-layer.is-on{color:var(--text);border-color:var(--border-strong)}
.cl-dot{width:7px;height:7px;border-radius:50%;background:var(--muted)}
.cal-layer.is-on .cl-events{background:var(--accent)}
.cal-layer.is-on .cl-reminders{background:var(--warn)}
.cal-layer.is-on .cl-tasks{background:var(--p-low)}
.cal-layer.is-on .cl-habits{background:var(--ok)}

/* ── Month ── */
.cal-month{display:flex;flex-direction:column;gap:8px}
.cm-dow{display:grid;grid-template-columns:repeat(7,1fr);gap:6px;padding:0 2px}
.cm-dow span{font-size:10.5px;font-weight:600;letter-spacing:.14em;text-transform:uppercase;
  color:var(--muted);text-align:left;padding-left:8px}
.cm-grid{display:grid;grid-template-columns:repeat(7,minmax(0,1fr));
  grid-auto-rows:minmax(112px,auto);gap:6px}
.cm-cell{background:var(--surface);border-radius:12px;padding:7px 8px 6px;
  display:flex;flex-direction:column;gap:5px;min-width:0;cursor:pointer;
  border:1px solid transparent;position:relative;
  transition:background var(--t-hover),border-color var(--t-hover)}
.cm-cell:hover{background:var(--surface-2);border-color:var(--border)}
.cm-cell.is-outside{opacity:.42}
.cm-cell.is-today .cm-num>span{background:var(--accent);color:#fff;border-radius:7px;
  min-width:22px;text-align:center}
.cm-cell.is-selected{border-color:var(--accent);background:var(--accent-soft)}
.cm-num{display:flex;align-items:center;gap:5px;font-size:12.5px;font-weight:600;
  color:var(--text-2)}
.cm-num>span{padding:1px 5px}
.cm-flag{font-size:11px;font-weight:800;color:var(--danger);margin-left:auto}
.cm-flag.is-warn{color:var(--warn)}
.cm-items{display:flex;flex-direction:column;gap:3px;min-width:0;flex:1}
.cm-ev{display:flex;align-items:center;gap:5px;min-width:0;font-size:11.5px;
  color:var(--text-2);border-radius:5px;padding:1px 3px;
  overflow:hidden;white-space:nowrap;text-overflow:ellipsis;
  transition:background var(--t-hover),color var(--t-hover)}
.cm-ev:hover{background:var(--surface-3);color:var(--text)}
.cm-ev b{font-weight:600;color:var(--muted);font-variant-numeric:tabular-nums;font-size:10.5px}
.cm-ev-t{overflow:hidden;text-overflow:ellipsis}
.cm-ev.is-allday{background:rgba(255,255,255,.045)}
/* Source-calendar colour: a 3px edge, nothing more. */
.cm-src{width:3px;height:11px;border-radius:2px;flex-shrink:0}
.cm-due{display:flex;align-items:center;gap:5px;font-size:11px;color:var(--p-high-ink);
  overflow:hidden;white-space:nowrap;text-overflow:ellipsis}
.cm-due i{width:5px;height:5px;border-radius:1px;background:var(--p-high);
  transform:rotate(45deg);flex-shrink:0}
.cm-more{font-size:10.5px;color:var(--muted);font-weight:600}
.cm-foot{display:flex;gap:4px;align-items:center}
.cm-chip{font-size:9.5px;font-weight:700;border-radius:4px;padding:0 4px;
  font-variant-numeric:tabular-nums}
.cm-plan{background:rgba(78,143,196,.16);color:var(--p-low-ink)}
/* Habits in a Month cell: a ratio, and quiet unless it is complete.
   A partial day is information, not a warning — nothing here goes red, because
   a missed habit is not an error and the month should not read as a scorecard. */
.cm-habit{background:rgba(0,217,163,.10);color:var(--ok);opacity:.75}
.cm-habit.is-all{background:rgba(0,217,163,.20);opacity:1}
.cm-habit.is-none{background:var(--surface-3);color:var(--muted)}
/* Workload: a restrained left edge, never a heatmap. */
.cm-cell::before{content:"";position:absolute;left:0;top:12px;bottom:12px;width:2px;
  border-radius:2px;background:transparent;transition:background var(--t-hover)}
.cm-cell.load-busy::before{background:rgba(240,145,61,.45)}
.cm-cell.load-overloaded::before{background:rgba(240,86,110,.55)}

/* ── Agenda ── */
.cal-agenda{display:flex;flex-direction:column;gap:6px;max-width:860px}
.ag-group{font-size:11px;font-weight:700;letter-spacing:.16em;text-transform:uppercase;
  color:var(--label);margin:20px 0 6px;padding-left:2px}
.ag-group:first-child{margin-top:0}
.ag-day{display:grid;grid-template-columns:52px 1fr;gap:12px;padding:6px 0}
.ag-date{display:flex;flex-direction:column;align-items:center;padding-top:6px}
.ag-dow{font-size:10px;font-weight:600;color:var(--muted);text-transform:uppercase}
.ag-num{font-size:19px;font-weight:700;color:var(--text-2);line-height:1.1}
.ag-items{display:flex;flex-direction:column;gap:4px;min-width:0}
.ag-item{display:flex;align-items:center;gap:9px;min-width:0;width:100%;text-align:left;
  background:var(--surface);border-radius:10px;padding:9px 12px;min-height:42px;
  transition:background var(--t-hover)}
.ag-item:hover{background:var(--surface-2)}
.ag-time{font-size:11.5px;color:var(--muted);font-variant-numeric:tabular-nums;
  min-width:56px;flex-shrink:0}
.ag-title{flex:1;min-width:0;font-size:var(--fs-task);color:var(--text);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.ag-meta{display:flex;gap:8px;align-items:center;font-size:11px;color:var(--muted);flex-shrink:0}
.ag-src{width:3px;height:20px;border-radius:2px;flex-shrink:0}
.ag-meet{background:rgba(78,143,196,.16);color:var(--p-low-ink);border-radius:4px;
  padding:1px 5px;font-weight:600;font-size:10px}
/* Each type reads differently — never all rendered as event bars. */
.ag-reminder{background:transparent;border:1px dashed var(--border)}
.ag-reminder.is-overdue{border-color:rgba(255,196,77,.5)}
.ag-reminder.is-done .ag-title{color:var(--muted);text-decoration:line-through}
.ag-check{width:17px;height:17px;border-radius:5px;border:1.6px solid var(--muted);
  flex-shrink:0;transition:background var(--t-hover),border-color var(--t-hover)}
.ag-check:hover{border-color:var(--ok)}
.ag-reminder.is-done .ag-check{background:var(--ok);border-color:var(--ok)}
.ag-deadline{background:transparent;border-left:2px solid var(--p-high);border-radius:0 10px 10px 0}
.ag-flag{width:7px;height:7px;background:var(--p-high);transform:rotate(45deg);flex-shrink:0}
.ag-block{background:rgba(78,143,196,.07);border-left:2px solid var(--p-low);
  border-radius:0 10px 10px 0}
.ag-plan{width:7px;height:7px;border-radius:2px;background:var(--p-low);flex-shrink:0}
/* --muted on this pill measured 3.06:1 — under the 4.5:1 that small text
   needs, and it read as barely-there grey. A label worth showing is a label
   worth reading. */
.ag-badge{font-size:10px;font-weight:600;color:var(--text-2);background:var(--surface-2);
  border-radius:4px;padding:1px 6px;flex-shrink:0}
.ag-badge.is-warn{background:rgba(255,196,77,.14);color:var(--warn)}
.ag-ro{opacity:.7}

/* ── Plan ── */
.cal-plan{overflow-x:auto}
.pl-grid{display:grid;grid-template-columns:54px repeat(7,minmax(112px,1fr));gap:5px;min-width:820px}
/* ── One band height for the whole week ─────────────────────────────────
 *
 * `--pl-ad-rows` is set on the grid from the busiest day and capped, so every
 * column's canvas begins at the same y and the hour labels beside them are
 * true. Before this the band was a property of one column: nine all-day items
 * pushed that day's canvas 184px below its neighbours', and the axis's "07:00"
 * pointed at something 230px from the 07:00 event.
 *
 * The axis reads the same number, which is the point — one variable, so the
 * labels and the lines cannot drift apart again. */
.pl-axis{display:flex;flex-direction:column;
  padding-top:calc(42px + var(--pl-ad-rows, 0) * var(--pl-ad-row))}
.pl-hour{flex:1;font-size:10px;color:var(--muted);font-variant-numeric:tabular-nums;
  text-align:right;padding-right:7px;transform:translateY(-6px)}
.pl-day{display:flex;flex-direction:column;min-width:0}
.pl-day-head{display:flex;align-items:baseline;gap:6px;padding:0 0 6px 2px;height:36px}
.pl-dow{font-size:10.5px;font-weight:600;color:var(--muted);text-transform:uppercase}
.pl-num{font-size:15px;font-weight:700;color:var(--text-2)}
.pl-day.is-today .pl-num{color:var(--accent)}
/* Every column reserves the same band, empty ones included — that is what
   keeps the canvases level. `--pl-ad-row` is one row plus its gap. */
:root{--pl-ad-row:22px}
.pl-allday{display:flex;flex-direction:column;gap:3px;overflow:hidden;
  height:calc(var(--pl-ad-rows, 0) * var(--pl-ad-row));flex-shrink:0}
.pl-ad{font-size:10.5px;background:var(--surface-2);border-radius:5px;padding:2px 6px;
  color:var(--text-2);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
  height:19px;flex-shrink:0;text-align:left}

/* ── A thing that is still going on ─────────────────────────────────────
 *
 * The arrows carry the whole meaning: it did not start here, and it does not
 * end here. A visit from Wednesday to the following Wednesday reads as one
 * continuous thing across the week instead of an event that appeared once and
 * vanished — which is what it did before the day filter was fixed. Square on
 * the sides it continues through, rounded on the ends where it really begins
 * and ends. */
.pl-ad-span{display:flex;align-items:center;gap:5px;
  background:color-mix(in srgb, var(--accent) 16%, var(--surface-2));
  border-radius:0;position:relative}
.pl-ad-span.is-open{border-radius:5px 0 0 5px}
.pl-ad-span.is-close{border-radius:0 5px 5px 0}
.pl-ad-span.is-open.is-close{border-radius:5px}
.pl-ad-span b{font-size:9.5px;color:var(--muted);font-variant-numeric:tabular-nums}
.pl-ad-in,.pl-ad-out{width:0;height:0;flex-shrink:0;
  border-top:3.5px solid transparent;border-bottom:3.5px solid transparent}
.pl-ad-in{border-right:4px solid var(--muted)}
.pl-ad-out{border-left:4px solid var(--muted);margin-left:auto}

/* More above the axis than the band can hold without pushing the day down. */
.pl-ad-more{color:var(--muted);font-weight:600;
  background:transparent;box-shadow:inset 0 0 0 1px var(--hairline);
  transition:background var(--t-hover),color var(--t-hover)}
.pl-ad-more:hover{background:var(--surface-2);color:var(--text-2)}
.pl-canvas{position:relative;flex:1;min-height:calc(var(--pl-hours)*46px);
  background:var(--surface);border-radius:10px;overflow:hidden}
.pl-line{display:block;height:calc(100%/var(--pl-hours));
  border-bottom:1px solid rgba(255,255,255,.045)}
/* ── Lanes ──────────────────────────────────────────────────────────────
 *
 * These were `left:3px;right:3px` — every timed thing the full width of the
 * column, positioned by top and height alone. Two at the same time were drawn
 * in the same rectangle, one over the other, and later-in-the-list won: the
 * reported "same colour, can't tell them apart, text running over text".
 *
 * A slot now divides the column by how many things genuinely overlap it.
 * `--lane` / `--lanes` come from `laneOut`; the gutter is decided here, once,
 * next to the radius and padding it has to agree with. The defaults mean a
 * lone event still spans the column exactly as it always did. */
.pl-ev,.pl-block{position:absolute;right:auto;
  left:calc(3px + (100% - 6px) * var(--lane, 0) / var(--lanes, 1) + var(--pl-inset, 0px));
  width:calc((100% - 6px) / var(--lanes, 1) - var(--pl-gutter, 0px) - var(--pl-inset, 0px));
  border-radius:7px;padding:3px 6px;
  font-size:10.5px;line-height:1.28;overflow:hidden;cursor:pointer;
  transition:transform var(--d-fast) var(--e-out),box-shadow var(--t-hover)}

/* Only a box WITH a neighbour separates itself. Sharing a slot is the whole
   reason to draw an edge, and a lone event that grew one would be paying for a
   problem it does not have. */
.pl-ev.is-shared,.pl-block.is-shared{--pl-gutter:3px;
  box-shadow:0 0 0 1px var(--rail)}
/* Alternate lanes step tone, so two boxes at one time read as two objects
   before you have read a word of either. Deliberately NOT a hue change: the
   left border already carries WHICH CALENDAR this came from, in the same
   colour language as the month dots, and overwriting that to say "these
   overlap" would trade real information for something position already says. */
.pl-ev.is-lane-alt{background:var(--surface-2)}
.pl-block.is-lane-alt{background:rgba(78,143,196,.20)}

/* ── Inside, rather than beside ─────────────────────────────────────────
 *
 * Halving the column is right when two things compete for the same moment. It
 * is wrong when one thing happens DURING another: an offsite from 06:00 to
 * 12:00 with a keynote at 08:00 is not two half-days, it is a day with a thing
 * in it, and splitting both to say so throws away the containment that is the
 * actual information.
 *
 * So the inner one keeps its host's slot and is drawn inset and on top. The
 * host stays visible down the left and right of it, which is what makes the
 * relationship readable without a word of explanation. `--pl-inset` rather
 * than a new left/width rule, so this composes with the lane arithmetic
 * instead of overwriting it. */
.pl-ev.is-nested,.pl-block.is-nested{--pl-inset:13px;z-index:3;
  box-shadow:0 0 0 1px var(--surface),var(--e1)}
.pl-ev.is-nested{background:var(--surface-3)}
.pl-block.is-nested{background:rgba(78,143,196,.26)}

/* More at this time than the column can honestly hold. Names them in the
   tooltip; opens the day, which has the width. */
.pl-more{position:absolute;right:4px;z-index:4;
  transform:translateY(2px);
  min-width:22px;height:16px;padding:0 4px;border-radius:8px;
  font-size:9px;font-weight:700;letter-spacing:.02em;
  background:var(--rail);color:var(--text-2);
  box-shadow:0 0 0 1px rgba(255,255,255,.10),var(--e1);
  transition:background var(--t-hover),color var(--t-hover)}
.pl-more:hover{background:var(--surface-3);color:var(--text)}
.pl-ev{background:var(--surface-3);border-left:3px solid var(--src, var(--accent));color:var(--text-2)}
.pl-ev:hover,.pl-block:hover{box-shadow:var(--e2);transform:translateY(-1px)}
.pl-ev b,.pl-block b{color:var(--muted);font-variant-numeric:tabular-nums;font-size:9.5px}
.pl-block{background:rgba(78,143,196,.14);border:1px dashed rgba(78,143,196,.45);
  color:var(--p-low-ink)}
.pl-tag{display:block;font-size:9px;color:var(--muted);font-weight:600}

/* ── Plan drag ── */
.pl-block{position:absolute}
.pl-resize{position:absolute;left:0;right:0;bottom:0;height:9px;cursor:ns-resize;
  border-radius:0 0 7px 7px}
.pl-resize::after{content:"";position:absolute;left:50%;bottom:2.5px;width:18px;height:2px;
  transform:translateX(-50%);border-radius:2px;background:rgba(255,255,255,.28);
  opacity:0;transition:opacity var(--t-hover)}
.pl-block:hover .pl-resize::after{opacity:1}
.pl-block.is-source{opacity:.30}
body.is-planning{cursor:grabbing;user-select:none}
[data-queue-task].is-dragging{opacity:.4}
/* The ghost IS the proposed block at its final dimensions — not a separate
   preview abstraction that can drift out of sync with the thing it previews. */
.pl-ghost{position:absolute;left:3px;right:3px;border-radius:7px;padding:3px 6px;
  font-size:10.5px;line-height:1.3;pointer-events:none;z-index:3;overflow:hidden;
  background:var(--accent-soft);border:1.5px solid var(--accent);color:var(--text)}
.pl-ghost b{display:block;font-variant-numeric:tabular-nums;font-size:9.5px;color:var(--text-2)}
.pl-ghost.has-clash{background:rgba(240,86,110,.14);border-color:var(--danger)}
.pl-clash{display:block;font-size:9px;color:var(--danger);font-weight:600}
/* ── Rail ── */
.cs-load{font-size:11.5px;color:var(--muted);margin-bottom:10px}
.cs-load b{color:var(--text-2);text-transform:capitalize}
.cs-sec{display:flex;flex-direction:column;gap:4px;margin-bottom:12px}
.cs-lab{font-size:9.5px;font-weight:700;letter-spacing:.14em;text-transform:uppercase;
  color:var(--muted)}
.cs-row{display:flex;align-items:center;gap:8px;width:100%;text-align:left;
  font-size:var(--fs-rail);color:var(--text-2);padding:5px 4px;border-radius:7px;min-width:0;
  transition:background var(--t-hover)}
.cs-row:hover{background:rgba(255,255,255,.035)}
.cs-row>span{font-size:10.5px;color:var(--muted);min-width:48px;flex-shrink:0;
  font-variant-numeric:tabular-nums}
.cs-row>b{font-weight:500;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.cs-row>i{width:3px;height:15px;border-radius:2px;flex-shrink:0;background:var(--accent)}
.cs-plan{background:var(--p-low)}
.cs-rem{background:var(--warn)}
.cs-due{background:var(--p-high)}
.cs-hab{background:var(--ok)}
.cs-add{width:100%;margin-top:4px}
.rl-list{display:flex;flex-direction:column;gap:3px}
.rl-row{display:flex;align-items:center;gap:8px;width:100%;text-align:left;
  font-size:var(--fs-rail);color:var(--text-2);padding:6px 4px;border-radius:7px;min-width:0}
.rl-row.is-warn .rl-t{color:var(--warn)}
.rl-t{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.rl-s{font-size:10.5px;color:var(--muted);flex-shrink:0}
.rl-dot{width:8px;height:8px;border-radius:50%;flex-shrink:0}
.pq-item{background:var(--surface-2);border-radius:8px;padding:7px 9px;font-size:12px;
  color:var(--text-2);border-left:2px solid var(--p-marker,var(--muted));
  display:flex;flex-direction:column;gap:1px;cursor:grab}
.pq-item span{font-size:10px;color:var(--muted)}
.cal-note{margin-top:10px;font-size:10.5px}

@media (max-width:1280px){
  .cm-grid{grid-auto-rows:minmax(94px,auto)}
  .cm-ev-t{font-size:11px}
}
@media (max-width:900px){
  .cal-head{grid-template-columns:1fr}
  .cal-head-side{justify-content:space-between}
  /* Labels give way to dots rather than being squeezed unreadable. */
  .cm-grid{grid-auto-rows:minmax(64px,auto);gap:4px}
  .cm-items .cm-ev-t,.cm-ev b,.cm-due{display:none}
  .cm-ev{width:6px;height:6px;border-radius:50%;padding:0;background:var(--accent)}
  .cm-items{flex-direction:row;flex-wrap:wrap;gap:3px;align-items:center}
  .cm-src{display:none}
  .cm-cell{padding:5px}
  .ag-day{grid-template-columns:40px 1fr;gap:8px}
  .ag-meta{display:none}
}

/* ══ D4.1 CALENDAR REFINEMENT ════════════════════════════════════════════ */

/* ── Month cell: calmer, with habits demoted to a hairline ── */
.cm-date{padding:1px 5px;border-radius:6px}
.cm-cell.is-today .cm-date{background:var(--accent);color:#fff}
/* Selection is a border and a faint tint, not a heavy purple block that
   overwhelms its neighbours. */
.cm-cell.is-selected{border-color:var(--accent);background:rgba(var(--accent-rgb),.07);
  box-shadow:0 0 0 1px rgba(var(--accent-rgb),.35)}
.cm-cell.is-selected .cm-date{color:var(--text);font-weight:700}
.cm-cell:focus-visible{outline:none;border-color:var(--accent);
  box-shadow:0 0 0 3px rgba(var(--accent-rgb),.20)}
.cm-flag{font-size:10px;font-weight:800;color:var(--danger);margin-left:auto;
  width:13px;height:13px;border-radius:50%;background:rgba(240,86,110,.14);
  display:grid;place-items:center}
.cm-flag.is-warn{color:var(--warn);background:rgba(255,196,77,.14)}
.cm-chip{min-width:15px;height:15px;display:inline-grid;place-items:center}
.cm-rem{background:rgba(255,196,77,.16);color:var(--warn)}

/* ── Hover preview ── */
.hov{position:fixed;z-index:300;width:264px;max-width:calc(100vw - 24px);
  background:var(--surface-2);border:1px solid var(--border-strong);
  border-radius:12px;box-shadow:var(--e3);padding:11px 13px;pointer-events:auto;
  display:flex;flex-direction:column;gap:7px}
/* MUST come after the rule above. `display:flex` on .hov beats the UA's
   `[hidden]{display:none}` — author styles outrank the user agent — so the
   preview shell rendered permanently as an empty 264px rounded bar floating
   over every Calendar mode. Any element that is both `hidden` and given a
   display value needs this. */
.hov[hidden]{display:none}
.hov-head{display:flex;align-items:baseline;justify-content:space-between;gap:9px}
.hov-head b{font-size:12.5px;font-weight:600;color:var(--text);line-height:1.35}
.hov-load{font-size:9.5px;font-weight:700;text-transform:uppercase;letter-spacing:.08em;
  color:var(--muted);flex-shrink:0}
.hov-load.load-busy{color:var(--warn)}
.hov-load.load-overloaded{color:var(--danger)}
.hov-next{display:flex;align-items:center;gap:7px;font-size:11.5px;color:var(--text-2);
  min-width:0}
.hov-next i{width:3px;height:14px;border-radius:2px;flex-shrink:0}
.hov-next span{color:var(--muted);font-variant-numeric:tabular-nums;flex-shrink:0}
.hov-next b{font-weight:500;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.hov-when{font-size:11.5px;color:var(--text-2);font-variant-numeric:tabular-nums}
.hov-counts,.hov-meta{display:flex;flex-wrap:wrap;gap:5px;font-size:10.5px;color:var(--muted)}
.hov-counts span,.hov-meta span{background:var(--surface-3);border-radius:5px;padding:1px 6px}
.hov-counts .is-due{color:var(--p-high-ink)}
.hov-counts .is-rem{color:var(--warn)}
.hov-counts .is-plan{color:var(--p-low-ink)}
.hov-counts .is-clash{color:var(--danger);background:rgba(240,86,110,.12)}
.hov-cal{display:inline-flex;align-items:center;gap:5px}
.hov-cal i{width:6px;height:6px;border-radius:50%}
.hov-meet{color:var(--p-low-ink)}
.hov-ro{color:var(--muted)}

/* ── Selected-day rail ── */
.cs-empty{display:flex;flex-direction:column;gap:3px;padding:14px 0 16px}
.cs-empty-t{font-size:13px;font-weight:600;color:var(--text-2)}
.cs-empty-s{font-size:11.5px;color:var(--muted);line-height:1.5}
.cs-load{display:flex;align-items:center;gap:7px;font-size:11.5px;color:var(--text-2);
  margin-bottom:12px}
.cs-load i{width:6px;height:6px;border-radius:50%;background:var(--muted)}
.cs-load.load-moderate i{background:var(--accent)}
.cs-load.load-busy i{background:var(--warn)}
.cs-load.load-overloaded i{background:var(--danger)}
.cs-alert{display:flex;flex-direction:column;gap:4px;padding:8px 10px;margin-bottom:12px;
  border-radius:9px;background:rgba(240,86,110,.08);border-left:2px solid var(--danger)}
.cs-alert span{font-size:11px;color:var(--text-2);line-height:1.45}
.cs-alert b{font-weight:600;color:var(--text)}
.cs-row.is-done b{color:var(--muted);text-decoration:line-through}
/* ── Habits on the selected day ──
   Rows, not a summary line: the point is that each one can be corrected. */
.cs-habits{margin-top:4px;padding-top:8px;border-top:1px solid var(--hairline)}
.cs-habit-count{font-weight:700;color:var(--ok);font-variant-numeric:tabular-nums}
.cs-habit-note{font-size:11px;color:var(--muted);margin:2px 0 0;line-height:1.45}
.cs-habit-note.is-err{color:var(--danger)}
.cs-habit-row{display:flex;align-items:center;gap:8px;width:100%;padding:5px 6px;
  margin-left:-6px;border:0;background:none;border-radius:7px;cursor:pointer;
  text-align:left;transition:background var(--t-hover)}
.cs-habit-row:hover{background:var(--surface-3)}
.cs-habit-row b{font-size:11.5px;font-weight:500;color:var(--text-2);
  overflow:hidden;white-space:nowrap;text-overflow:ellipsis}
.cs-habit-row.is-done b{color:var(--muted)}
/* Same construction as the step tick in the task editor: a real checkmark
   glyph, revealed by giving it a colour. It was two crossing CSS gradients,
   which met off-centre and read as a leaning crucifix rather than a tick. */
.cs-habit-tick{width:16px;height:16px;border-radius:5px;flex-shrink:0;
  border:1.5px solid var(--border-strong);
  display:grid;place-items:center;color:transparent;
  transition:background var(--t-hover),border-color var(--t-hover),color var(--t-hover)}
.cs-habit-row:hover .cs-habit-tick{border-color:var(--ok)}
.cs-habit-row.is-done .cs-habit-tick{background:var(--ok);border-color:var(--ok);
  color:#08221c}
.cs-habit-n{margin-left:auto;font-size:10px;color:var(--muted);
  font-variant-numeric:tabular-nums}
/* The computed Diary habit in the day sheet (D2.2 §9). The SAME row — same
   height, tick, type and completed state — with one quiet word explaining why
   pressing it opens a diary rather than ticking a box. */
.cs-habit-auto{margin-left:auto;font-size:9.5px;color:var(--muted);opacity:.6;
  letter-spacing:.04em;flex-shrink:0}
.cs-habit-diary{border-bottom:1px solid var(--border);border-radius:7px 7px 0 0;
  padding-bottom:6px;margin-bottom:2px}

/* ── Calendar sources / connection ── */
.cs-connect-copy{font-size:12.5px;color:var(--text-2);line-height:1.55;margin:0 0 10px}
.cs-connect-list{margin:0 0 14px;padding-left:16px;display:flex;flex-direction:column;gap:5px}
.cs-connect-list li{font-size:11.5px;color:var(--muted);line-height:1.45}
.cs-connect{width:100%}
.cs-account{display:flex;align-items:center;gap:9px;padding:9px 10px;border-radius:10px;
  background:var(--surface-2);margin-bottom:11px}
.cs-acct-dot{width:8px;height:8px;border-radius:50%;flex-shrink:0}
.cs-acct-dot.is-ok{background:var(--ok)}
.cs-acct-dot.is-error{background:var(--danger)}
/* Trouble is amber, not red: the server is still retrying and there is nothing
   for the user to do. Red is reserved for the one state that needs them. */
.cs-acct-dot.is-warn{background:var(--warn)}
.cs-acct-body{display:flex;flex-direction:column;gap:1px;min-width:0}
.cs-acct-body b{font-size:12px;font-weight:600;color:var(--text);overflow:hidden;
  text-overflow:ellipsis;white-space:nowrap}
.cs-acct-body span{font-size:10.5px;color:var(--muted)}
.cs-sources{display:flex;flex-direction:column;gap:2px;margin-bottom:11px}
.cs-source{display:flex;align-items:center;gap:8px;padding:6px 4px;border-radius:7px;
  cursor:pointer;transition:background var(--t-hover)}
.cs-source:hover{background:rgba(255,255,255,.035)}
/* Custom box — the native checkbox square was explicitly ruled out. */
.cs-source input{position:absolute;opacity:0;width:0;height:0}
.cs-box{width:15px;height:15px;border-radius:4px;border:1.5px solid var(--muted);
  flex-shrink:0;position:relative;transition:background var(--t-hover),border-color var(--t-hover)}
.cs-source input:checked+.cs-box{background:var(--accent);border-color:var(--accent)}
.cs-source input:checked+.cs-box::after{content:"";position:absolute;left:4.5px;top:1.5px;
  width:4px;height:8px;border:solid #fff;border-width:0 1.7px 1.7px 0;transform:rotate(45deg)}
.cs-source input:focus-visible+.cs-box{box-shadow:0 0 0 3px rgba(var(--accent-rgb),.22)}
.cs-dot{width:7px;height:7px;border-radius:50%;flex-shrink:0}
.cs-name{flex:1;min-width:0;font-size:12px;color:var(--text-2);overflow:hidden;
  text-overflow:ellipsis;white-space:nowrap}
.cs-ro{font-size:9.5px;color:var(--muted);background:var(--surface-2);border-radius:4px;
  padding:1px 5px;flex-shrink:0}
.cs-sync{display:flex;align-items:center;justify-content:space-between;gap:8px;
  font-size:11px;color:var(--muted);padding-top:9px;border-top:1px solid var(--hairline)}
.cs-sync-now{height:28px;font-size:11.5px;padding:0 10px}
/* The sync button says what it is doing, where it is. A toast would be gone
   by the time the work finished, and disabling the Calendar for background
   work is worse than being briefly behind. */
.cs-sync-now{display:inline-flex;align-items:center;gap:6px}
.cs-spin{width:11px;height:11px;border-radius:50%;flex:0 0 auto;display:none;
  border:1.5px solid color-mix(in srgb,var(--text) 30%,transparent);
  border-top-color:var(--accent);animation:cs-spin 700ms linear infinite}
.cs-sync-now[data-sync-state="busy"] .cs-spin{display:block}
.cs-sync-now[data-sync-state="busy"]{color:var(--muted)}
.cs-sync-now[data-sync-state="ok"]{color:var(--ok)}
.cs-sync-now[data-sync-state="failed"]{color:var(--danger)}
@keyframes cs-spin{to{transform:rotate(360deg)}}
@media (prefers-reduced-motion:reduce){
  .cs-spin{animation-duration:2.4s}
}

.cs-auto{margin:7px 0 0;font-size:10.5px;line-height:1.45;color:var(--muted)}
/* Calendar settings, inside Sources rather than in a settings screen of their
   own -- both answers are about the calendars listed directly above them. */
/* Habits that did not exist on the day being looked at. Shown, because you
   may well have been doing one already and want to say so -- but set apart,
   because they are not part of what that day asked of you. */
.cs-habit-later{margin-top:9px;padding-top:8px;border-top:1px dashed var(--hairline)}
.cs-habit-later-l{display:block;font-size:9.5px;letter-spacing:.08em;text-transform:uppercase;
  color:var(--muted);margin-bottom:3px}
.cs-habit-row.is-later{opacity:.82}
.cs-habit-when{margin-left:auto;font-size:10px;color:var(--muted);white-space:nowrap}
.cs-habit-add{width:20px;height:20px;margin-left:auto;border:0;border-radius:6px;cursor:pointer;
  background:var(--surface-2);color:var(--muted);font-size:14px;line-height:1;
  display:inline-flex;align-items:center;justify-content:center}
.cs-habit-add:hover{color:var(--text);background:var(--surface)}
.cs-habits .cs-lab{display:flex;align-items:center;gap:7px}
.cs-set{margin-top:11px;padding-top:10px;border-top:1px solid var(--hairline);
  display:flex;flex-direction:column;gap:5px}
.cs-set-h{margin:0;font-size:9.5px;letter-spacing:.08em;text-transform:uppercase;
  color:var(--muted)}
.cs-set-h:not(:first-child){margin-top:8px}
.cs-set-s{margin:0 0 3px;font-size:10.5px;color:var(--muted);line-height:1.4}
.cs-default{height:30px;padding:0 8px;border-radius:7px;font-size:12px;
  color:var(--text);background:var(--surface-2);border:1px solid var(--hairline)}
.cs-default:disabled{color:var(--muted)}
.cs-busy-row .cs-name{color:var(--text-2)}

.cs-disconnect{margin-top:9px;font-size:11px;color:var(--muted);
  transition:color var(--t-hover)}
.cs-disconnect:hover{color:var(--danger)}

/* ── Plan queue cards ── */
.pq-list{display:flex;flex-direction:column;gap:6px}
.pq-card{background:var(--surface-2);border-radius:10px;padding:9px 10px;
  border-left:2.5px solid var(--p-marker,var(--muted));cursor:grab;
  display:flex;flex-direction:column;gap:6px;
  transition:background var(--t-hover),transform var(--d-fast) var(--e-out)}
.pq-card:hover{background:var(--surface-3)}
.pq-card:focus-visible{outline:none;box-shadow:0 0 0 3px rgba(var(--accent-rgb),.22)}
.pq-card.pri-urgent{--p-marker:var(--p-urgent)}
.pq-card.pri-high{--p-marker:var(--p-high)}
.pq-card.pri-medium{--p-marker:var(--p-medium)}
.pq-card.pri-low{--p-marker:var(--p-low)}
.pq-card.pri-someday{--p-marker:var(--p-someday)}
.pq-top{display:flex;align-items:flex-start;gap:8px}
.pq-title{flex:1;min-width:0;font-size:12.5px;color:var(--text);line-height:1.35}
/* The Schedule button is the non-drag path. It appears on hover and focus, but
   is always reachable by keyboard and always present on touch. */
.pq-sched{flex-shrink:0;height:24px;padding:0 9px;border-radius:7px;font-size:11px;
  font-weight:600;background:var(--accent-soft);color:var(--text);
  opacity:0;transition:opacity var(--t-hover),background var(--t-hover)}
.pq-card:hover .pq-sched,.pq-card:focus-within .pq-sched,.pq-sched:focus-visible{opacity:1}
.pq-sched:hover{background:var(--accent);color:#fff}
.pq-meta{display:flex;flex-wrap:wrap;gap:5px;align-items:center}
.pq-meta>span{font-size:10px;color:var(--muted);background:var(--surface);
  border-radius:4px;padding:1px 5px}
.pq-area{color:var(--text-2)}
.pq-due.is-soon{color:var(--p-high-ink);background:rgba(240,145,61,.12)}
.pq-pri{text-transform:capitalize}
.pri-dot-urgent{color:var(--p-urgent-ink)}
.pri-dot-high{color:var(--p-high-ink)}
.pri-dot-low{color:var(--p-low-ink)}

/* ── Plan canvas: outside planning hours recedes ── */
.pl-outside{position:absolute;left:0;right:0;background:rgba(0,0,0,.16);
  pointer-events:none;z-index:1}
.pl-now{position:absolute;left:0;right:0;height:1.5px;background:var(--accent);
  z-index:2;pointer-events:none}
.pl-now::before{content:"";position:absolute;left:-3px;top:-2.75px;width:7px;height:7px;
  border-radius:50%;background:var(--accent)}
.pl-free{position:absolute;left:3px;right:3px;border-radius:6px;
  background:rgba(var(--accent-rgb),.045);border:1px dashed rgba(var(--accent-rgb),.18);
  pointer-events:none}

/* ── Reminder modal ── */
.modal-reminder{width:min(520px,calc(100vw - 40px));position:fixed}
.rm-mark{width:9px;height:9px;border-radius:2px;background:var(--warn);flex-shrink:0;
  margin-top:13px;transform:rotate(45deg)}

/* ── Event editor: wider, better hierarchy, Life OS scrollbar ── */
.modal-event{width:min(720px,calc(100vw - 48px))}
.ev-head .m-title{font-size:21px;font-weight:600;line-height:1.3}
.modal-reminder .m-title{font-size:19px;font-weight:600}
.ev-row{grid-template-columns:88px 1fr auto;gap:12px;min-height:46px;padding:5px 0}
.ev-when{padding-bottom:10px;margin-bottom:12px}
.ev-adv{padding-top:10px;margin-top:10px;gap:4px}
/* The bright native scrollbar is replaced, not merely narrowed. */
.ev-body{scrollbar-width:thin;scrollbar-color:var(--border-strong) transparent;
  padding-right:6px}
.ev-body::-webkit-scrollbar{width:9px}
.ev-body::-webkit-scrollbar-track{background:transparent}
.ev-body::-webkit-scrollbar-thumb{background:var(--border-strong);border-radius:5px;
  border:2.5px solid var(--surface)}
.ev-body::-webkit-scrollbar-thumb:hover{background:var(--muted)}
/* Colour swatches read as a set, not as loose buttons. */
.ev-swatches{gap:5px;padding:6px;background:var(--surface-2);border-radius:10px}
.ev-swatch{width:24px;height:24px;border-radius:7px}
.dp-today{width:100%;height:30px;margin-top:7px;border-radius:8px;font-size:11.5px;
  font-weight:600;color:var(--text-2);background:var(--surface-3);
  transition:color var(--t-hover)}
.dp-today:hover{color:var(--text)}
.tp-clear{color:var(--muted);font-style:italic}

/* ── Add menu with icons ── */
.cal-menu button{flex-direction:row;align-items:center;gap:10px}
.cm-add-ico{width:28px;height:28px;border-radius:8px;display:grid;place-items:center;
  flex-shrink:0;background:var(--surface-2)}
.cm-add-ico i{width:8px;height:8px;border-radius:50%}
.cm-add-body{display:flex;flex-direction:column;gap:1px;min-width:0}

@media (max-width:900px){
}
@media (max-width:640px){
  .modal-reminder{left:0;right:0;bottom:0;top:auto;transform:none;width:100%;
    max-height:92vh;border-radius:20px 20px 0 0}
  .hov{display:none}
}

/* ══ D4.2 — CONSOLIDATION AND CLARITY ═══════════════════════════════════ */

/* ── §4 Centring ────────────────────────────────────────────────────────
   Month, Agenda and Plan share ONE outer bound, so the canvas never shifts
   horizontally when you change mode. Previously each mode found its own width
   and Agenda in particular sat far left in a wide window. */
.cal-head,.cal-month,.cal-agenda,.cal-plan{width:100%;max-width:var(--cal-max);
  margin-inline:auto}
:root{--cal-max:1180px}

@media (min-width:1600px){:root{--cal-max:1340px}}
@media (min-width:2100px){:root{--cal-max:1520px}}

/* ── §3 Header as one system ────────────────────────────────────────────
   Mode control and Add share a height, a radius and a baseline. They are two
   separate controls that look like they were designed together. */
.cal-head{display:flex;flex-direction:column;gap:14px}
.cal-head-row{display:flex;align-items:flex-end;justify-content:space-between;gap:20px}
.cal-head-sub{align-items:center}
.cal-head-main{display:flex;flex-direction:column;gap:9px;min-width:0}
.cal-head-side{display:flex;align-items:center;gap:10px;flex-shrink:0}

.cal-period{display:flex;align-items:center;gap:3px}
.cal-period-label{font-size:15px;font-weight:600;color:var(--text-2);
  min-width:168px;padding:0 4px;
  transition:opacity var(--d-fast) var(--e-out)}
.cal-period-label.is-swapping{opacity:0}
.cal-nav{width:30px;height:30px;border-radius:9px;color:var(--muted);
  display:grid;place-items:center;
  transition:background var(--t-hover),color var(--t-hover)}
.cal-nav svg{width:17px;height:17px;fill:none;stroke:currentColor;stroke-width:1.7;
  stroke-linecap:round;stroke-linejoin:round}
.cal-nav:hover{background:var(--surface-2);color:var(--text)}
.cal-nav:active{transform:scale(.94)}
.cal-today{height:30px;padding:0 12px;border-radius:9px;font-size:12.5px;font-weight:600;
  color:var(--muted);transition:background var(--t-hover),color var(--t-hover)}
.cal-today:hover{background:var(--surface-2);color:var(--text)}

/* Mode control: one track, one sliding pill. */
.cal-modes{position:relative;display:flex;height:38px;padding:3px;border-radius:11px;
  background:var(--surface-2);flex-shrink:0}
.cal-mode-pill{position:absolute;top:3px;bottom:3px;left:3px;border-radius:8px;
  width:calc((100% - 6px) / var(--mode-n));
  transform:translateX(calc(var(--mode-i) * 100%));
  background:var(--accent);box-shadow:0 2px 8px rgba(var(--accent-deep-rgb),.30);
  transition:transform var(--t-select)}
.cal-modes button{position:relative;z-index:1;height:32px;padding:0 15px;border-radius:8px;
  font-size:12.5px;font-weight:600;color:var(--muted);white-space:nowrap;
  transition:color var(--t-select)}
.cal-modes button:hover{color:var(--text-2)}
.cal-modes button[aria-selected="true"]{color:#fff}
.cal-modes button:focus-visible{outline:none;box-shadow:0 0 0 2px rgba(var(--accent-rgb),.55)}

/* Add: same height and radius family as the mode track. */
.cal-add{display:inline-flex;align-items:center;gap:7px;height:38px;padding:0 16px 0 13px;
  border-radius:11px;font-size:13px;font-weight:600;color:#fff;
  background:var(--brand);box-shadow:var(--glow);
  transition:transform var(--d-fast) var(--e-out),box-shadow var(--t-hover)}
.cal-add svg{width:16px;height:16px;fill:none;stroke:currentColor;stroke-width:2;
  stroke-linecap:round}
.cal-add:hover{transform:translateY(-1px);box-shadow:0 8px 22px rgba(var(--accent-deep-rgb),.34)}
.cal-add:active{transform:translateY(0) scale(.98)}
.cal-add[aria-expanded="true"]{box-shadow:0 0 0 3px rgba(var(--accent-rgb),.25)}

/* ── §24 Legend ── */
.cal-legend-btn{display:inline-flex;align-items:center;gap:6px;height:28px;padding:0 10px;
  border-radius:8px;font-size:11.5px;font-weight:500;color:var(--muted);
  transition:background var(--t-hover),color var(--t-hover)}
.cal-legend-btn svg{width:14px;height:14px;fill:none;stroke:currentColor;stroke-width:1.6;
  stroke-linecap:round}
.cal-legend-btn:hover{background:var(--surface-2);color:var(--text-2)}
/* Contents only. Position, width, border, shadow and padding belong to the
   shared .util-surface shell — that is what makes Sources and Key the same
   object. See the D4.7 block. */
.legend{display:flex;flex-direction:column;gap:13px}
.legend h4{margin:0;font-size:12.5px;font-weight:600;color:var(--text)}
.lg-group{display:flex;flex-direction:column;gap:6px}
.lg-lab{font-size:9.5px;font-weight:700;letter-spacing:.14em;text-transform:uppercase;
  color:var(--muted)}
.lg-row{display:flex;align-items:center;gap:9px;font-size:11.5px;color:var(--text-2);
  line-height:1.4}
.lg-row b{font-weight:600;color:var(--text)}
.lg-row>i{flex-shrink:0}
.lg-src{width:3px;height:14px;border-radius:2px;background:var(--accent)}
.lg-due{width:7px;height:7px;background:var(--p-high);transform:rotate(45deg)}
.lg-rem{width:8px;height:8px;border-radius:50%;background:var(--warn)}
.lg-plan{width:8px;height:8px;border-radius:2px;background:var(--p-low)}
.lg-clash{width:13px;height:13px;border-radius:50%;background:rgba(240,86,110,.16)}
.lg-load{width:3px;height:16px;border-radius:2px;background:var(--border-strong)}
.lg-load.load-busy{background:var(--warn)}
.lg-load.load-overloaded{background:var(--danger)}
.lg-note{margin:0;font-size:11px;color:var(--muted);line-height:1.5;
  padding-top:11px;border-top:1px solid var(--hairline)}

/* ── §7 Workload: two marked states only, and never purple ──────────────
   Purple means selection everywhere else in Life OS. Using it for
   "moderately busy" meant one colour carried two unrelated meanings. */
.cm-cell.load-moderate::before{background:transparent}
.cm-cell.load-busy::before{background:var(--warn);opacity:.55}
.cm-cell.load-overloaded::before{background:var(--danger);opacity:.65}

/* ── §8 Clash indicator: an icon with a count, not a bare number ── */
.cm-flag{display:inline-flex;align-items:center;gap:2px;width:auto;min-width:14px;
  height:14px;padding:0 3px;border-radius:7px;font-size:9.5px;font-weight:700}
.cm-flag svg{width:9px;height:9px;fill:none;stroke:currentColor;stroke-width:2.2;
  stroke-linecap:round}

/* ── §11/§12 Canvas transitions ─────────────────────────────────────────
   Direction is the message: forward slides left, back slides right, mode
   change crossfades in place. Transform and opacity only. */
@keyframes cal-in-left{from{opacity:0;transform:translateX(18px)}to{opacity:1;transform:none}}
@keyframes cal-in-right{from{opacity:0;transform:translateX(-18px)}to{opacity:1;transform:none}}
@keyframes cal-in-fade{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}
.cal-canvas-next{animation:cal-in-left var(--d-slow) var(--e-out) backwards}
.cal-canvas-prev{animation:cal-in-right var(--d-slow) var(--e-out) backwards}
.cal-canvas-mode{animation:cal-in-fade var(--d-base) var(--e-out) backwards}
@media (prefers-reduced-motion:reduce){
  .cal-canvas-next,.cal-canvas-prev,.cal-canvas-mode{animation:none}
}
html[data-motion="reduced"] .cal-canvas-next,
html[data-motion="reduced"] .cal-canvas-prev,
html[data-motion="reduced"] .cal-canvas-mode{animation:none}

/* ── §5 Stable rail ─────────────────────────────────────────────────────
   Calendar is one section, so its rail keeps one shell. Only the middle
   contextual card changes with the mode. */
.rail-ctx{animation:cal-in-fade var(--d-base) var(--e-out) backwards}
.rail-head{display:flex;align-items:center;justify-content:space-between;gap:8px;
  margin-bottom:11px}
.rail-head h3{margin:0}
.rail-src-btn{display:inline-flex;align-items:center;gap:6px;height:26px;padding:0 9px;
  border-radius:7px;font-size:11px;color:var(--muted);background:var(--surface-2);
  transition:color var(--t-hover)}
.rail-src-btn:hover{color:var(--text-2)}
.rail-src-dot{width:6px;height:6px;border-radius:50%;background:var(--ok);flex-shrink:0}
.rail-src-dot.is-error{background:var(--danger)}

@media (max-width:900px){
  .cal-head-row{flex-direction:column;align-items:stretch;gap:12px}
  .cal-head-side{justify-content:space-between}
  .cal-modes{flex:1}
  /* `flex:1` on the buttons was dead once the track became a grid, and a dead
     declaration next to a live one is how the next person mis-reads this. The
     grid's own 1fr columns already make them equal. */
  .cal-modes button{padding:0 8px}
  .cal-period-label{min-width:0;flex:1}
  .legend{left:12px!important;right:12px;width:auto}
}

/* ── §17 Schedule a task ── */
.modal-schedule{width:min(560px,calc(100vw - 40px));position:fixed}
.st-mark{width:9px;height:9px;border-radius:2px;background:var(--p-low);flex-shrink:0;
  margin-top:14px}
.st-title{font-size:19px;font-weight:600;margin:0;flex:1}
/* Fill the row. As a flex item that never grew, the list collapsed to the
   width of its longest title — a narrow scrolling stack of half-visible
   cards in a 544px modal, which is what made choosing a task feel like
   rummaging. It is the subject of this dialog and should look like it. */
.st-list{display:flex;flex-direction:column;gap:5px;width:100%;flex:1 1 100%;
  max-height:246px;overflow-y:auto;
  scrollbar-width:thin;scrollbar-color:var(--border-strong) transparent}
.st-list::-webkit-scrollbar{width:8px}
.st-list::-webkit-scrollbar-thumb{background:var(--border-strong);border-radius:4px}
.st-task{display:flex;flex-direction:column;gap:4px;text-align:left;padding:9px 11px;
  border-radius:9px;background:var(--surface-2);border-left:2.5px solid var(--p-marker,var(--muted));
  transition:background var(--t-hover),box-shadow var(--t-hover)}
.st-task.pri-urgent{--p-marker:var(--p-urgent)}
.st-task.pri-high{--p-marker:var(--p-high)}
.st-task.pri-medium{--p-marker:var(--p-medium)}
.st-task.pri-low{--p-marker:var(--p-low)}
.st-task.pri-someday{--p-marker:var(--p-someday)}
.st-task:hover{background:var(--surface-3)}
.st-task.is-sel{box-shadow:inset 0 0 0 1.5px var(--accent)}
.st-task:focus-visible{outline:none;box-shadow:0 0 0 3px rgba(var(--accent-rgb),.22)}
.st-task-t{font-size:12.5px;color:var(--text);line-height:1.35}
.st-task-m{display:flex;flex-wrap:wrap;gap:5px}
.st-task-m>span{font-size:10px;color:var(--muted);background:var(--surface);
  border-radius:4px;padding:1px 5px}
.st-pri{text-transform:capitalize}
.st-due{color:var(--p-high-ink)}
.st-durations{display:flex;flex-wrap:wrap;gap:5px}
.st-check{margin-top:6px;padding:9px 11px;border-radius:9px;font-size:11.5px;
  line-height:1.45;color:var(--text-2);
  transition:background var(--t-hover),color var(--t-hover)}
.st-check b{font-weight:600;font-variant-numeric:tabular-nums}
.st-check.is-clear{background:rgba(0,217,163,.07);color:var(--text-2)}
.st-check.has-clash{background:rgba(240,145,61,.09);color:var(--p-high-ink)}
.st-empty{display:flex;flex-direction:column;gap:7px;align-items:flex-start;
  padding:20px 0 8px}
.st-empty-t{font-size:14px;font-weight:600;color:var(--text-2)}
.st-empty-s{font-size:12px;color:var(--muted);line-height:1.5}
.st-empty .btn{margin-top:8px}

/* ── §19 Read-only detail ───────────────────────────────────────────────
   Deliberately unlike the editors: no input shells, no Save. The absence of
   a form is the message that Life OS cannot change this. */
.modal-detail{width:min(560px,calc(100vw - 40px));position:fixed}
.dt-head{display:flex;align-items:flex-start;gap:11px}
.dt-accent{width:4px;align-self:stretch;min-height:30px;border-radius:2px;flex-shrink:0;
  margin-top:2px}
.dt-title{flex:1;margin:0;font-size:20px;font-weight:600;line-height:1.32;color:var(--text)}
.dt-body{padding-top:4px}
.dt-rows{margin:0;display:flex;flex-direction:column;gap:1px}
.dt-row{display:grid;grid-template-columns:96px 1fr;gap:14px;padding:9px 0;
  border-bottom:1px solid var(--hairline)}
.dt-row:last-child{border-bottom:none}
.dt-row dt{font-size:11.5px;font-weight:600;color:var(--muted)}
.dt-row dd{margin:0;font-size:12.5px;color:var(--text-2);line-height:1.5;
  overflow-wrap:anywhere}
.dt-note{margin:14px 0 0;padding:10px 12px;border-radius:9px;font-size:11.5px;
  line-height:1.5;color:var(--muted);background:var(--surface-2);
  border-left:2px solid var(--border-strong)}
.dt-foot{gap:8px}
.dt-meet{background:rgba(78,143,196,.16);color:var(--p-low-ink);text-decoration:none}
.dt-meet:hover{background:rgba(78,143,196,.26)}
.dt-foot .btn{text-decoration:none;display:inline-flex;align-items:center}

@media (max-width:640px){
  .modal-schedule,.modal-detail{left:0;right:0;bottom:0;top:auto;transform:none;
    width:100%;max-height:92vh;border-radius:20px 20px 0 0}
  .dt-row{grid-template-columns:1fr;gap:3px}
}

/* ── D4.3 §7 Stable rail ── */
.rail-context .rail-head h3{font-size:13px;line-height:1.35}
.rail-sync{font-size:10.5px;color:var(--muted)}
.rail-ctx{display:flex;flex-direction:column;gap:var(--stack-gap,20px)}
.rail-attention .rl-row{width:100%;text-align:left}
.cal-hint{margin-top:10px;font-size:10.5px}

/* ── §23 Sources popover ── */
/* Contents only — see .legend above and the shared shell in the D4.7 block. */
.sources{display:flex;flex-direction:column;gap:11px}
.sources h4{margin:0;font-size:12.5px;font-weight:600;color:var(--text)}

/* ── §9 Agenda uses the width it has ── */
.cal-agenda{max-width:var(--cal-max)}
/* ── Calendar skeleton ───────────────────────────────────────────────────
   Shown while the view being asked for is not the view on screen — changing
   month, week or mode. It reuses the REAL containers (.cal-month, .pl-grid,
   .cal-agenda), so the geometry is the geometry that is coming and nothing
   shifts when the answer lands.

   DECLARED LAST, AND SCOPED. The first version sat above the calendar's own
   rules, so `.cm-cell span { background: … }` and friends beat `.sk-line` on
   source order at equal specificity — the placeholder boxes were the right
   size and completely invisible. Every selector here is prefixed with
   `.is-skeleton`, and the block comes after the views it dresses.

   Deliberately quiet: this appears for a fraction of a second on every
   navigation, and a bright pulsing block would be more disruptive than the
   pop-in it replaces.

   Held back before it appears, too. A fast answer resolves in a few tens of
   milliseconds, and a skeleton that flashed for 25ms would be a worse flicker
   than the thing it was built to remove — so it renders immediately (holding
   the layout) and stays invisible until the wait is worth admitting to. */
.is-skeleton{pointer-events:none;user-select:none;
  opacity:0;animation:sk-appear 160ms ease-out 150ms forwards}
@keyframes sk-appear{to{opacity:1}}

.is-skeleton .sk-line,.is-skeleton .sk-block{display:block;border-radius:4px;
  background-image:linear-gradient(90deg,
    color-mix(in srgb,var(--text) 10%,transparent) 25%,
    color-mix(in srgb,var(--text) 20%,transparent) 50%,
    color-mix(in srgb,var(--text) 10%,transparent) 75%);
  background-color:transparent;
  background-size:200% 100%;
  animation:sk-sweep 1.4s ease-in-out infinite}
.is-skeleton .sk-short{width:58%}

/* Month: 42 cells, exactly as the real grid lays them out. */
.is-skeleton .cm-cell{cursor:default}
.is-skeleton .sk-cell{display:flex;flex-direction:column;gap:5px}
.is-skeleton .sk-num{width:18px;height:11px;border-radius:3px}
.is-skeleton .sk-ev{width:82%;height:9px}

/* Plan: seven columns and the same hour axis, so the axis never jumps. */
.is-skeleton .sk-day{display:flex;flex-direction:column}
.is-skeleton .sk-dow{width:26px;height:9px}
/* The REAL canvas class, so the column is the height the real column will be
   and the grid does not grow when the answer lands. */
.is-skeleton .sk-col{position:relative;flex:1}
.is-skeleton .sk-block{position:absolute;left:6%;width:88%;border-radius:6px}

/* Agenda: group headings and rows at the real rhythm. */
.is-skeleton .ag-group{height:14px}
.is-skeleton .sk-grp{width:120px;height:13px}
.is-skeleton .sk-row{display:flex;flex-direction:column;gap:7px}
.is-skeleton .sk-day-label{width:88px;height:11px}
.is-skeleton .sk-item{width:100%;height:30px;border-radius:8px}

@keyframes sk-sweep{
  0%{background-position:200% 0}
  100%{background-position:-200% 0}
}
/* A sweep that never stops is worse than no sweep for anyone who finds motion
   uncomfortable — the shapes alone already say "not yet". The DELAY is the
   useful part and survives; only the movement goes. */
@media (prefers-reduced-motion:reduce){
  .is-skeleton .sk-line,.is-skeleton .sk-block{animation:none;
    background-image:none;
    background-color:color-mix(in srgb,var(--text) 12%,transparent)}
  .is-skeleton{animation-duration:1ms}
}


.ag-day{grid-template-columns:64px 1fr;gap:16px}
.ag-item{padding:10px 14px;min-height:46px;gap:12px}
.ag-title{font-size:13px}
.ag-time{min-width:66px;font-size:12px}
@media (min-width:1200px){
  .ag-meta{max-width:44%;justify-content:flex-end}
  .ag-meta span{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:180px}
}

/* ── §10 Plan week: weekdays carry the planning, weekends recede ─────────
   Seven equal columns made every day too narrow to read at 1440px. Weekends
   keep a column so nothing is hidden, but at 0.62 the weight of a weekday —
   they hold fewer commitments and are rarely where work gets planned. */
.pl-grid{grid-template-columns:54px repeat(5,minmax(0,1fr)) repeat(2,minmax(0,0.62fr))}
.pl-day.is-weekend .pl-day-head{opacity:.6}
.pl-day.is-weekend .pl-canvas{background:rgba(33,30,44,.6)}
.pl-day.is-weekend.has-events .pl-day-head{opacity:1}
.pl-day.is-weekend.has-events .pl-canvas{background:var(--surface)}
@media (max-width:1100px){
  .pl-grid{grid-template-columns:48px repeat(7,minmax(96px,1fr))}
}

/* Daily capacity in the column head. */
.pl-load{margin-left:auto;font-size:9.5px;font-weight:700;letter-spacing:.06em;
  text-transform:uppercase;color:var(--muted)}
.pl-load.load-busy{color:var(--warn)}
.pl-load.load-overloaded{color:var(--danger)}
.pl-free-label{position:absolute;left:5px;right:5px;font-size:9.5px;color:var(--muted);
  pointer-events:none;padding-top:2px;opacity:0;transition:opacity var(--t-hover)}
.pl-canvas:hover .pl-free-label{opacity:1}

/* ── D4.4 Reminders in Month and Plan ────────────────────────────────────
   A reminder asks for attention on a date; it does not occupy the day the way
   an event does. Dotted rather than solid carries that without a legend. */
.cm-rem-row{display:flex;align-items:center;gap:5px;min-width:0;width:100%;
  font-size:11px;color:var(--text-2);border-radius:5px;padding:1px 3px;text-align:left;
  border:1px dotted rgba(255,196,77,.42);background:transparent;
  overflow:hidden;white-space:nowrap;
  transition:background var(--t-hover),border-color var(--t-hover)}
.cm-rem-row:hover{background:rgba(255,196,77,.08);border-color:rgba(255,196,77,.7)}
.cm-rem-row.is-overdue{border-color:rgba(255,196,77,.85);color:var(--warn)}
.cm-rem-row b{font-weight:600;color:var(--muted);font-size:10px;
  font-variant-numeric:tabular-nums}
.cm-rem-dot{width:5px;height:5px;border-radius:50%;background:var(--warn);flex-shrink:0}
.cm-rem-rep{font-style:normal;font-size:9.5px;color:var(--muted);flex-shrink:0;margin-left:auto}

/* Plan: date-only reminders sit ABOVE the axis so they cannot eat a free
   window that is genuinely still free. */
/* Same 19px as every other band row — the band is a shared height now, and a
   row that sizes itself would put one column's canvas below its neighbours'. */
.pl-rem-ad{display:flex;align-items:center;gap:5px;width:100%;text-align:left;
  height:19px;flex-shrink:0;
  font-size:10px;border-radius:5px;padding:2px 6px;color:var(--text-2);
  border:1px dotted rgba(255,196,77,.45);background:transparent;
  overflow:hidden;white-space:nowrap;text-overflow:ellipsis;
  transition:background var(--t-hover)}
.pl-rem-ad:hover{background:rgba(255,196,77,.10)}
.pl-rem-ad.is-overdue{border-color:var(--warn);color:var(--warn)}
.pl-rem-ad>i{width:4px;height:4px;border-radius:50%;background:var(--warn);flex-shrink:0}
.pl-rem-ad>span{overflow:hidden;text-overflow:ellipsis}
/* Timed reminders are markers on the axis, never duration blocks. */
.pl-rem{position:absolute;left:3px;right:3px;display:flex;align-items:center;gap:4px;
  font-size:9.5px;color:var(--warn);z-index:2;pointer-events:auto;
  transform:translateY(-50%);text-align:left}
.pl-rem>i{width:5px;height:5px;border-radius:50%;background:var(--warn);flex-shrink:0;
  box-shadow:0 0 0 2px var(--surface)}
.pl-rem>span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
  background:var(--surface);padding:0 3px;border-radius:3px}
.pl-rem:hover>span{color:var(--text)}

/* An INTENDED time — "I'll do that Thursday at three", usually said to the
   assistant. It holds no time, so like a timed reminder it is a mark on the
   axis and never a block: a box would make the day read as committed and
   would eat a free window that is genuinely still free. Hollow rather than
   solid, because nothing has been reserved. */
.pl-intent{position:absolute;left:3px;right:3px;display:flex;align-items:center;gap:4px;
  font-size:9.5px;color:var(--p-low-ink);z-index:2;pointer-events:auto;
  transform:translateY(-50%);text-align:left}
.pl-intent>i{width:5px;height:5px;border-radius:50%;flex-shrink:0;
  background:var(--surface);box-shadow:0 0 0 1.5px var(--p-low),0 0 0 3px var(--surface)}
.pl-intent>span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
  background:var(--surface);padding:0 3px;border-radius:3px;font-style:italic}
.pl-intent:hover>span{color:var(--text)}

/* ══ D4.5 ═══════════════════════════════════════════════════════════════ */

/* §19 Three-zone header: the mode selector is centred on the CANVAS, not
   merely placed after the date controls. Equal side columns are what make the
   centre column actually centred. */
/* minmax(0,1fr), not 1fr: a bare `1fr` refuses to shrink below its content, so
   the wider side column steals from the middle and the mode selector ends up
   off-centre — measured 40px left before this. */
.cal-head-row{display:grid;grid-template-columns:minmax(0,1fr) auto minmax(0,1fr);
  align-items:center;gap:16px}
.cal-head-main{min-width:0}
.cal-head-main h1{white-space:nowrap}
.cal-head-main{justify-self:start}
.cal-head-mid{justify-self:center}
.cal-head-side{justify-self:end}
.cal-head-sub{display:flex;align-items:center;justify-content:space-between}

/* §13 The identity row is a link now, not a menu trigger. */
.who-go{margin-left:auto;display:grid;place-items:center;width:18px;height:18px;
  color:var(--muted);opacity:0;transform:translateX(-3px);
  transition:opacity var(--t-hover),transform var(--t-hover)}
.who-go svg{width:15px;height:15px;fill:none;stroke:currentColor;stroke-width:1.7;
  stroke-linecap:round;stroke-linejoin:round}
.who:hover .who-go,.who:focus-visible .who-go{opacity:1;transform:translateX(0)}
.who-chev{display:none}

/* §14 Completed history, at the end of the board where content belongs. */
.today-history{display:inline-flex;align-items:center;gap:8px;height:36px;padding:0 14px;
  border-radius:10px;font-size:12.5px;font-weight:500;color:var(--muted);
  background:var(--surface);align-self:flex-start;
  transition:background var(--t-hover),color var(--t-hover)}
.today-history:hover{background:var(--surface-2);color:var(--text-2)}
.set-build{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11.5px;
  color:var(--muted);background:var(--surface-2);border-radius:6px;padding:3px 8px}

/* §3-§5 The workspace itself. */
/* D4.7: the workspace sits on the Calendar frame too, so leaving Reminders
   does not slide the page sideways. */
.rv{width:auto;max-width:var(--cal-max);margin-left:var(--cal-inset);margin-right:0;
  display:flex;flex-direction:column;gap:18px}
.rv-head{display:flex;align-items:center;justify-content:space-between;gap:16px;flex-wrap:wrap}
.rv-filters{display:flex;gap:5px;flex-wrap:wrap}
.rv-filter{display:inline-flex;align-items:center;gap:7px;height:32px;padding:0 13px;
  border-radius:9px;font-size:12.5px;font-weight:500;color:var(--muted);
  background:var(--surface-2);transition:color var(--t-hover),background var(--t-hover)}
.rv-filter:hover{color:var(--text-2)}
.rv-filter.is-on{background:var(--accent-soft);color:var(--text);font-weight:600}
.rv-count{font-size:10.5px;font-weight:700;color:var(--muted);
  font-variant-numeric:tabular-nums}
.rv-filter.is-on .rv-count{color:var(--accent)}

.rv-list{display:flex;flex-direction:column;gap:7px}
.rv-card{display:flex;align-items:center;gap:13px;padding:13px 16px;border-radius:12px;
  background:var(--surface);border-left:2.5px solid transparent;
  transition:background var(--t-hover),border-color var(--t-hover)}
.rv-card:hover{background:var(--surface-2)}
.rv-card.is-overdue{border-left-color:var(--warn)}
.rv-card.is-paused{opacity:.6}
.rv-card.is-done .rv-title{color:var(--muted);text-decoration:line-through}
.rv-check{width:19px;height:19px;border-radius:6px;border:1.7px solid var(--muted);
  flex-shrink:0;position:relative;
  transition:background var(--t-hover),border-color var(--t-hover)}
.rv-check:hover{border-color:var(--ok)}
.rv-card.is-done .rv-check{background:var(--ok);border-color:var(--ok)}
.rv-card.is-done .rv-check::after{content:"";position:absolute;left:6px;top:2px;
  width:5px;height:10px;border:solid #08221c;border-width:0 2px 2px 0;transform:rotate(45deg)}
.rv-body{flex:1;min-width:0;display:flex;flex-direction:column;gap:5px;text-align:left}
.rv-title{font-size:13.5px;color:var(--text);line-height:1.35}
.rv-meta{display:flex;flex-wrap:wrap;gap:6px;align-items:center}
.rv-meta>span{font-size:11px;color:var(--muted)}
/* The RULE leads. The date it next lands on is a consequence of it. */
.rv-rule{color:var(--text-2)!important;font-weight:600}
.rv-next{font-variant-numeric:tabular-nums}
.rv-area{background:var(--surface-2);border-radius:5px;padding:1px 7px}
.rv-status{margin-left:auto}
.rv-card.is-overdue .rv-status{color:var(--warn)!important;font-weight:600}
.rv-actions{display:flex;gap:5px;flex-shrink:0;opacity:0;
  transition:opacity var(--t-hover)}
.rv-card:hover .rv-actions,.rv-card:focus-within .rv-actions{opacity:1}
.rv-act{height:28px;padding:0 11px;border-radius:8px;font-size:11.5px;font-weight:500;
  color:var(--muted);background:var(--surface-2);transition:color var(--t-hover)}
.rv-act:hover{color:var(--text)}
.rv-empty{display:flex;flex-direction:column;gap:6px;padding:56px 0;align-items:center;
  text-align:center}
.rv-empty-t{font-size:15px;font-weight:600;color:var(--text-2)}
.rv-empty-s{font-size:12.5px;color:var(--muted);max-width:340px;line-height:1.55}

@media (max-width:900px){
  .cal-head-row{grid-template-columns:1fr;gap:12px}
  .cal-head-main,.cal-head-mid,.cal-head-side{justify-self:stretch}
  .rv-actions{opacity:1}
  .rv-status{margin-left:0}
}

/* ══ D4.6 ═══════════════════════════════════════════════════════════════ */

/* §4 D4.6 gave Calendar its own 38px filled trigger and Today a 34px bare one,
   with a menu class each. D4.7 replaced all four with .util-btn / .util-menu —
   see the D4.7 block. Only the count badge is still local to the menu items. */
.tm-count{margin-left:auto;font-size:11px;color:var(--muted);
  font-variant-numeric:tabular-nums}

/* §5 The Reminders workspace header — no period, no modes, no layers. */
.rv-head-row{grid-template-columns:1fr auto}
.rv-head-left{display:flex;align-items:center;gap:14px;min-width:0}
.rv-head-left h1{margin:0}
.rv-head-right{display:flex;align-items:center;gap:10px}
.rv-back{display:inline-flex;align-items:center;gap:6px;height:32px;padding:0 12px 0 8px;
  border-radius:9px;font-size:12.5px;font-weight:600;color:var(--muted);
  background:var(--surface-2);transition:color var(--t-hover)}
.rv-back svg{width:16px;height:16px;fill:none;stroke:currentColor;stroke-width:1.8;
  stroke-linecap:round;stroke-linejoin:round}
.rv-back:hover{color:var(--text)}
/* Shares the Add button's shape deliberately, but not its class — a different
   button wearing `cal-add` is how "is this the Calendar Add control?" starts. */
.rv-new-btn{display:inline-flex;align-items:center;gap:7px;height:38px;padding:0 16px 0 13px;
  border-radius:11px;font-size:13px;font-weight:600;color:#fff;
  background:var(--brand);box-shadow:var(--glow);
  transition:transform var(--d-fast) var(--e-out),box-shadow var(--t-hover)}
.rv-new-btn svg{width:16px;height:16px;fill:none;stroke:currentColor;stroke-width:2;
  stroke-linecap:round}
.rv-new-btn:hover{transform:translateY(-1px);box-shadow:0 8px 22px rgba(var(--accent-deep-rgb),.34)}
.rv-new-btn:active{transform:translateY(0) scale(.98)}
.rv-badge{background:rgba(255,196,77,.14)!important;color:var(--warn)!important;
  border-radius:5px;padding:1px 7px;font-weight:600}

/* D4.6 collapsed the PAGE rail column. D4.7 replaced that: see the frame block
   below. Collapsing the page column changed .main-col's width, so the header
   and the canvas — both centred inside it — slid sideways every time a day was
   selected. The rail now lives inside the Calendar frame instead. */

/* §15 Completed history rows. */
.hist-list{display:flex;flex-direction:column;max-width:760px}
.hist-restore{font-size:10.5px;font-weight:600;color:var(--muted);background:var(--surface-2);
  border-radius:6px;padding:3px 9px;opacity:0;transition:opacity var(--t-hover),color var(--t-hover)}
.hist-row:hover .hist-restore,.hist-restore:focus-visible{opacity:1}
.hist-restore:hover{color:var(--accent)}
.hist-more{margin-top:16px}

/* ══ D4.7 — HEADER CONSISTENCY, VISUAL CENTRING AND RAIL MOTION ═════════ */

/* ── §2/§3 One header grid, stretched across the frame ──────────────────
   `align-items` is stated HERE, next to the layout it belongs to. The bug it
   replaces: a `align-items:start` left over from the D3 grid header survived
   into D4.2's column flex, where it means "shrink horizontally". Every header
   row sat at max-content (860px measured inside a 1520px header) and hugged the
   left, so the mode selector was 330px left of the canvas centre while being
   perfectly centred INSIDE the row — which is why measuring the row always
   reported zero. Measure the composition, not the box you just centred. */
.cal-head{align-items:stretch}
.cal-head-row{width:100%}

/* ── §8-§11 ONE FIXED COMPOSITION FRAME ─────────────────────────────────
   The frame is the same width for the header and the body, and it does not
   change when the rail opens. Previously the rail was a PAGE column, so opening
   it narrowed .main-col — and since both the header and the canvas were centred
   inside .main-col, both slid sideways. Nothing that belongs to the page (the
   title, the period, the utility control, Add, the layer chips) may move
   because a day was selected. */
:root{--cal-rail-w:320px; --cal-rail-gap:24px; --cal-rail-col:0px}
body.cal-rail-open{--cal-rail-col:calc(var(--cal-rail-w) + var(--cal-rail-gap))}

/* ── The frame is centred on the WINDOW, not on the column ──────────────
   `margin-inline:auto` centres the frame inside .main-col, which begins after
   the sidebar — so the calendar sat 84px right of the middle of the screen and
   left a visibly bigger gap on the left. The eye includes the sidebar in the
   page; the layout did not.

   Derived, not nudged: reconstruct where .main-col's left edge is, work out
   where a window-centred frame would start, and inset by the difference. Every
   term is a token this layout already uses, so it holds at every width, and it
   clamps to zero once the frame no longer fits — a narrow window falls back to
   filling the column.

   `--sbw` is the scrollbar, published by measureScrollbar(): 100vw includes it,
   so without that term a scrolling page sits half a scrollbar off centre. */
:root{--sbw:0px}
body:has(.cal-head){
  --cal-win:calc(100vw - var(--sbw));
  --cal-wrap-offset:max(0px, calc((var(--cal-win) - var(--sidebar-w) - var(--content-max)) / 2));
  --cal-col-left:calc(var(--sidebar-w) + var(--cal-wrap-offset) + 44px);
  --cal-inset:max(0px, calc((var(--cal-win) - var(--cal-max)) / 2 - var(--cal-col-left)));
}

/* On Calendar the page rail is unused: the calendar owns its own, inside the
   frame, so the frame can stay still while only the canvas gives up width.
   The empty second track and the gutter are KEPT at zero width deliberately —
   dropping them widened .main-col by the gutter and slid the whole composition
   38px right, which reads as a bigger gap on the left. The column the frame
   centres in must stay exactly the width it has been since D4.2. */
body:has(.cal-head) .main-wrap{grid-template-columns:minmax(0,1fr) 0}
body:has(.cal-head) .rail{display:none}

/* width:auto, not 100%: the frame fills what is left after the inset and is
   then capped, so one rule covers both the wide case (capped at --cal-max,
   window-centred) and the narrow case (inset 0, fills the column). */
body:has(.cal-head) .page-head{display:block}
.cal-head,.cal-body{width:auto;max-width:var(--cal-max);
  margin-left:var(--cal-inset);margin-right:0}
.cal-body{display:grid;grid-template-columns:minmax(0,1fr) var(--cal-rail-col);
  align-items:start;
  transition:grid-template-columns var(--d-slow) var(--e-out)}
.cal-canvas{min-width:0}
/* The canvases fill the frame's first column; the frame owns the max-width now,
   so a second cap here would fight it. */
.cal-body .cal-month,.cal-body .cal-agenda,.cal-body .cal-plan{max-width:none;
  margin-inline:0;width:100%}

/* Agenda's day rows start at the same left edge as a Month cell.
   They used to lead with a 64px date column, so the cards began 80px further
   in than every other mode's content while the canvas edges matched — the
   modes looked like they were on different margins. The date is a label above
   its items now rather than a gutter beside them. */
.cal-body .ag-day{grid-template-columns:minmax(0,1fr);gap:5px;padding:6px 0 10px}
.cal-body .ag-date{flex-direction:row;align-items:baseline;gap:7px;
  padding:0 0 1px 2px}
.cal-body .ag-num{font-size:13.5px;font-weight:700;line-height:1.2}
.cal-body .ag-dow{font-size:10.5px}

/* The rail is clipped, not squeezed. Its content keeps a fixed width so nothing
   inside it re-wraps mid-transition — the reflow flicker §10 forbids. */
.cal-rail{min-width:0;overflow:hidden;position:sticky;top:22px}
.cal-rail-in{width:var(--cal-rail-w);margin-left:var(--cal-rail-gap);
  display:flex;flex-direction:column;gap:14px;
  opacity:0;transition:opacity var(--d-base) var(--e-out)}
.cal-body.rail-shown .cal-rail-in{opacity:1}

/* The mode selector does NOT follow the canvas centre.
   It used to translate by half the rail so it stayed centred over the grid,
   which meant selecting a day slid it 172px and Plan week's permanent rail
   left the whole header sitting off to one side of Month and Agenda. The
   header belongs to the page: the rail is content, and content does not move
   the page's controls. It stays on the frame centre in every mode. */
.cal-head-mid{translate:none}

/* One track, three equal buttons.
   The sliding pill is a third of the track and moves in exact thirds, but the
   buttons were content-width — 72 / 74 / 87px — so the highlight never lined
   up with the label inside it. Measured 7.5px off on Agenda, and 9.4px too
   narrow for "Plan week".

   Grid, not `flex:1`: a flex basis of zero makes the columns equal but lets
   them fall below their own labels, and "Plan week" then overflowed its
   padding and sat 5.4px right of its own button — the same bug one level
   down. `1fr` keeps an auto minimum, so every column is as wide as the widest
   label and the pill's arithmetic is simply true. */
.cal-modes{display:grid;grid-template-columns:repeat(var(--mode-n),1fr)}
.cal-modes button{padding:0 12px}

/* Centred between its arrows. The 168px min-width keeps the control from
   twitching as the label changes length, but the text was left-aligned inside
   it — 7px from the left arrow and 70px from the right. */
.cal-period-label{text-align:center}

@media (prefers-reduced-motion:reduce){
  .cal-body,.cal-head-mid,.cal-rail-in{transition:none}
}

/* §20 Below the point where a 320px rail would leave an unusable grid, the rail
   stops taking width and stacks under the canvas. No lateral animation on a
   layout that has no lateral room. */
@media (max-width:1180px){
  body.cal-rail-open{--cal-rail-col:0px}
  .cal-body{grid-template-columns:minmax(0,1fr);transition:none}
  .cal-rail{overflow:visible;position:static;grid-column:1}
  .cal-rail-in{width:auto;margin-left:0;margin-top:20px}
  .cal-head-mid{translate:none}
}

/* ── §4 ONE utility trigger, shared by Today and Calendar ───────────────
   Two near-identical controls in two sizes with two different backgrounds is
   how "is this the same button?" starts. One class, one geometry, one state
   set — used by both pages. */
.util-btn{width:34px;height:34px;border-radius:10px;color:var(--muted);
  background:transparent;display:grid;place-items:center;
  transition:background var(--t-hover),color var(--t-hover),transform var(--d-instant) var(--e-out)}
.util-btn svg{width:18px;height:18px;fill:currentColor}
.util-btn:hover{background:var(--surface-2);color:var(--text-2)}
.util-btn:active{transform:scale(.94)}
.util-btn[aria-expanded="true"]{background:var(--surface-2);color:var(--text)}
.util-btn:focus-visible{outline:none;box-shadow:0 0 0 2px rgba(var(--accent-rgb),.55)}

/* §4/§5 One menu, one anchor language: below the trigger, right edges aligned,
   because the trigger sits at the upper-right of the page on both surfaces. */
/* animation:none because .menu carries `rise`, and the shared entrance already
   animates every utility surface. Two stacked entrances left the menu resting
   8px below where the Sources/Key surface opened from the same button. */
.util-menu{min-width:212px;padding:6px;animation:none}
.util-menu button{display:flex;align-items:center;gap:10px;width:100%;
  padding:9px 12px;border-radius:9px;font-size:13px;color:var(--text-2);
  text-align:left;transition:background var(--t-hover),color var(--t-hover)}
.util-menu button:hover{background:var(--surface-3);color:var(--text)}

/* ── §6 ONE contextual surface for Sources and Key ──────────────────────
   Same shell, same anchor, same width, same shadow, same motion. Only the
   content differs, and switching between them replaces the content INSIDE the
   shell rather than closing one box and opening a different-looking one
   somewhere else. */
/* 12px, not the 13px the two old panels used: every popover in the app shares
   .menu's radius, and a surface that is one pixel rounder than the menu it was
   opened from is a difference nobody can name but everybody can see. */
.util-surface{position:fixed;z-index:300;width:308px;max-width:calc(100vw - 24px);
  background:var(--surface-2);border:1px solid var(--border-strong);border-radius:12px;
  box-shadow:var(--e3);padding:14px 15px;overflow:hidden}
.util-surface h4{margin:0;font-size:12.5px;font-weight:600;color:var(--text)}
.us-body{display:flex;flex-direction:column;gap:13px}
/* The surfaces keep their own internal rhythm (.legend, .sources); the SHELL —
   position, width, border, shadow, padding, motion — is shared. */

/* ══ PROJECTS (Phase E2) ════════════════════════════════════════════════
   A LIST at every width, never a card grid. A grid has to become a list on a
   phone, which means designing the same screen twice and letting the two
   disagree. A list is the same object everywhere; only its density changes. */

/* Same frame as Calendar, so switching sections does not move the page.
   .page-head must be a BLOCK here: as a flex container it sizes its child to
   its content, and the header then sits at 555px inside a 1221px frame —
   exactly the bug D4.7 spent a phase finding on Calendar. */
body:has(.pj-head) .page-head{display:block}
.pj-head,.pj-list{width:auto;max-width:var(--cal-max);
  margin-left:var(--cal-inset, auto);margin-right:0}
body:has(.pj-head){
  --cal-win:calc(100vw - var(--sbw));
  --cal-wrap-offset:max(0px, calc((var(--cal-win) - var(--sidebar-w) - var(--content-max)) / 2));
  --cal-col-left:calc(var(--sidebar-w) + var(--cal-wrap-offset) + 44px);
  --cal-inset:max(0px, calc((var(--cal-win) - var(--cal-max)) / 2 - var(--cal-col-left)));
}
body:has(.pj-head) .main-wrap{grid-template-columns:minmax(0,1fr) 0}
/* §17: no rail. Everything the rail would summarise belongs in the list,
   where it can be acted on. */
body:has(.pj-head) .rail{display:none}

.pj-head{display:flex;flex-direction:column;gap:14px}
.pj-head-row{display:flex;align-items:flex-end;justify-content:space-between;gap:20px}
.pj-head-side{display:flex;align-items:center;gap:10px;flex-shrink:0}
.pj-filters{display:flex;gap:5px;flex-wrap:wrap}
.pj-filter{display:inline-flex;align-items:center;gap:7px;height:32px;padding:0 13px;
  border-radius:9px;font-size:12.5px;font-weight:500;color:var(--muted);
  background:var(--surface-2);transition:color var(--t-hover),background var(--t-hover)}
.pj-filter:hover{color:var(--text-2)}
.pj-filter.is-on{background:var(--accent-soft);color:var(--text);font-weight:600}
.pj-fcount{font-size:10.5px;font-weight:700;color:var(--muted);font-variant-numeric:tabular-nums}
.pj-filter.is-on .pj-fcount{color:var(--accent)}

.pj-list{display:flex;flex-direction:column;gap:26px}
.pj-group{display:flex;flex-direction:column;gap:8px}
.pj-group-h{font-size:11px;font-weight:700;letter-spacing:.15em;text-transform:uppercase;
  color:var(--label);margin:0 0 2px;padding-left:11px;position:relative;
  display:flex;align-items:center;gap:9px}
.pj-group-rows{display:flex;flex-direction:column;gap:7px}

/* ── Group identity (E2.2) ──────────────────────────────────────────────
   The groups were readable but indistinguishable. Separation comes from a
   3px heading mark plus a barely-there row tint — NOT from recolouring whole
   cards, which would turn a calm list into five competing blocks.

   Every group keeps its text label. Colour is a second cue, never the meaning,
   and the semantics are the app's existing ones: purple is the primary
   identity, amber is attention, green is only ever completion. */
.pj-group-h::before{content:"";position:absolute;left:0;top:50%;translate:0 -50%;
  width:3px;height:12px;border-radius:2px;background:var(--muted)}
.pj-group[data-group="attention"] .pj-group-h{color:var(--warn)}
.pj-group[data-group="attention"] .pj-group-h::before{background:var(--warn)}
.pj-group[data-group="now"] .pj-group-h{color:var(--label)}
.pj-group[data-group="now"] .pj-group-h::before{background:var(--accent)}
/* Cool and quieter — available, but not the working set. */
.pj-group[data-group="upcoming"] .pj-group-h::before{background:var(--p-low)}
.pj-group[data-group="on_hold"] .pj-group-h{color:var(--muted)}
.pj-group[data-group="on_hold"] .pj-group-h::before{background:var(--muted)}
.pj-group[data-group="recent"] .pj-group-h{color:var(--muted)}
.pj-group[data-group="recent"] .pj-group-h::before{background:var(--ok)}

/* A tint you should not consciously notice — enough to bind a group's rows
   together, far too little to read as a coloured card. */
.pj-group[data-group="attention"] .pj-row{background:
  linear-gradient(0deg, rgba(255,196,77,.05), rgba(255,196,77,.05)), var(--surface)}
.pj-group[data-group="attention"] .pj-row:hover{background:
  linear-gradient(0deg, rgba(255,196,77,.07), rgba(255,196,77,.07)), var(--surface-2)}
.pj-group[data-group="now"] .pj-row{background:
  linear-gradient(0deg, rgba(var(--accent-rgb),.045), rgba(var(--accent-rgb),.045)), var(--surface)}
.pj-group[data-group="now"] .pj-row:hover{background:
  linear-gradient(0deg, rgba(var(--accent-rgb),.07), rgba(var(--accent-rgb),.07)), var(--surface-2)}
/* On hold and Recently completed are secondary, not disabled: full contrast
   text, just no tint pulling the eye towards them. */
.pj-group[data-group="on_hold"] .pj-row,
.pj-group[data-group="recent"] .pj-row{background:var(--surface)}

/* Section rhythm: groups breathe apart, rows inside one stay together. */
.pj-list{gap:30px}
.pj-group[data-group="on_hold"],.pj-group[data-group="recent"]{margin-top:4px}

/* ── The row (E2.1) ─────────────────────────────────────────────────────
   The overview read as a stack of near-identical dark rectangles. The fix is
   HIERARCHY, not colour: one clearly dominant title, a next action that reads
   as the actionable line, a quieter outcome, and state pushed to a footer at a
   smaller size. No hover lift, no tilt, no shadow bloom — the row is a row. */
.pj-row{display:flex;align-items:flex-start;gap:14px;padding:16px 18px 14px;
  border-radius:12px;background:var(--surface);
  border-left:2.5px solid transparent;
  transition:background var(--t-hover),border-color var(--t-hover)}
.pj-row:hover{background:var(--surface-2)}
.pj-row:focus-visible{outline:none;box-shadow:0 0 0 2px rgba(var(--accent-rgb),.55)}
.pj-row.is-attention{border-left-color:var(--warn)}
.pj-main{flex:1;min-width:0;display:flex;flex-direction:column;gap:5px}

.pj-title-row{margin:0;display:flex;align-items:center;gap:9px;min-width:0}
/* The one clearly dominant element in the row. */
.pj-title{font-size:16px;font-weight:650;letter-spacing:-.15px;color:var(--text);
  text-align:left;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0;
  transition:color var(--t-hover)}
.pj-title:hover{color:var(--accent)}

/* The actionable line — brighter than the outcome, because it is what the row
   is for. */
.pj-next{margin:0;display:flex;align-items:baseline;gap:8px;min-width:0}
.pj-next-lbl{font-size:9.5px;font-weight:700;letter-spacing:.14em;text-transform:uppercase;
  color:var(--muted);flex-shrink:0}
.pj-next-t{font-size:13.5px;color:var(--text-2);overflow:hidden;text-overflow:ellipsis;
  white-space:nowrap}
.pj-next-none{font-size:12.5px;color:var(--muted)}

/* ONE attention line: label, reason, action. Never a badge and a sentence
   saying the same thing in the same row. */
.pj-attention{margin:0;display:flex;align-items:baseline;gap:9px;flex-wrap:wrap;min-width:0}
.pj-attention-l{font-size:11px;font-weight:700;color:var(--warn);
  background:rgba(255,196,77,.14);border-radius:5px;padding:2px 7px;white-space:nowrap}
.pj-attention-w{font-size:12.5px;color:var(--text-2);min-width:0}
.pj-attention-act{font-size:12px;font-weight:600;color:var(--accent);white-space:nowrap}
.pj-attention-act:hover{text-decoration:underline}

.pj-outcome{font-size:12.5px;color:var(--muted);margin:0;line-height:1.45;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}

/* State, at the bottom and at a smaller size, so it never competes. Progress
   leads it — the number was previously at the far edge and easy to miss. */
.pj-foot{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-top:3px}
.pj-progress{font-size:11.5px;color:var(--text-2);font-variant-numeric:tabular-nums;
  white-space:nowrap}
/* A supporting bar, not the message. The words carry the meaning. */
.pj-bar{width:52px;height:3px;border-radius:2px;background:var(--surface-3);
  overflow:hidden;flex-shrink:0}
.pj-bar i{display:block;height:100%;background:var(--accent);border-radius:2px}
.pj-area,.pj-state,.pj-focus,.pj-target{font-size:10.5px;border-radius:5px;padding:2px 7px;
  background:var(--surface-2);color:var(--muted);white-space:nowrap}
.pj-row:hover .pj-area,.pj-row:hover .pj-state,
.pj-row:hover .pj-focus,.pj-row:hover .pj-target{background:var(--surface-3)}
.pj-state{color:var(--text-2)}
.pj-focus{background:var(--accent-soft);color:var(--accent)}
.pj-target.is-late{color:var(--warn)}
.pj-more{width:30px;height:30px;flex-shrink:0;margin-top:2px}
.pj-row .pj-more{opacity:0;transition:opacity var(--t-hover)}
.pj-row:hover .pj-more,.pj-row:focus-within .pj-more{opacity:1}

/* A new or restored row is briefly marked so the eye can find where it landed.
   A highlight, not a celebration. */
@keyframes pj-land{from{background:var(--accent-soft)}to{background:var(--surface)}}
.pj-row.is-new{animation:pj-land 1300ms var(--e-out)}
.pj-row.is-entering{animation:rise var(--d-slow) var(--e-out) backwards}

.pj-skel{height:78px;border-radius:12px;background:linear-gradient(90deg,
  var(--surface) 0%,var(--surface-2) 50%,var(--surface) 100%);
  background-size:320px 100%;animation:shimmer 1.4s linear infinite;margin-bottom:7px}

.pj-empty{display:flex;flex-direction:column;gap:9px;padding:64px 0;align-items:center;
  text-align:center}
.pj-empty-inline{padding:28px 0}
.pj-empty-t{font-size:15px;font-weight:600;color:var(--text-2)}
.pj-empty-s{font-size:12.5px;color:var(--muted);max-width:430px;line-height:1.6}

/* ── Detail (E2.1) ──────────────────────────────────────────────────────
   The structure was right and the composition was not: three sections floating
   in a lot of space with no relationship to each other. The fix is a tighter
   header block, a section rhythm that repeats, and a next action that is
   compact rather than a large empty rectangle. */
.pjd-head{gap:9px}
.pjd-left{display:flex;align-items:center;gap:14px;min-width:0}
.pjd-left h1{margin:0}
.pjd-outcome{font-size:14.5px;color:var(--text-2);margin:0;line-height:1.5;max-width:68ch}
/* The controls read as one band rather than scattered chips. */
.pjd-meta{display:flex;align-items:center;gap:8px;flex-wrap:wrap;padding:10px 12px;
  border-radius:11px;background:var(--surface)}
.pjd-meta .pj-area{background:var(--surface-2)}
/* Status and Focus are the shared dropdown now, not native <select>s. The
   meta row keeps them small so they read as metadata rather than as a form. */
.pjd-meta .cf-ctl{height:30px;font-size:12px;padding:0 9px}
.pjd-meta .cf-menu-btn{min-width:0}
.pjd-meta .pj-progress{margin-left:auto;font-size:12px}
.pjd-health{display:flex;align-items:baseline;gap:9px;flex-wrap:wrap}
.pjd-health-why{font-size:12.5px;color:var(--text-2)}

/* One rhythm for every section, so the page reads as one workspace. */
.pjd-body{display:flex;flex-direction:column;gap:26px;width:auto;
  max-width:var(--cal-max);margin-left:var(--cal-inset, auto);margin-right:0}
.pjd-sec{display:flex;flex-direction:column;gap:9px}
.pjd-sec-head{display:flex;align-items:baseline;justify-content:space-between;gap:12px}
.pjd-sec-actions{display:flex;align-items:center;gap:7px}
.pjd-sec-h{font-size:11px;font-weight:700;letter-spacing:.15em;text-transform:uppercase;
  color:var(--label);margin:0;display:flex;align-items:baseline;gap:9px}
.pjd-count{font-size:10.5px;font-weight:600;letter-spacing:0;text-transform:none;
  color:var(--muted)}
/* Says WHY this is the next action, without a permanent lecture. */
.pjd-next-why{font-size:11px;color:var(--muted)}

/* Compact, not a giant empty block. */
.pjd-next{display:flex;align-items:center;gap:10px;padding:4px 6px 4px 4px;
  border-radius:12px;background:var(--surface)}
.pjd-next.is-empty{padding:14px 16px;gap:12px;flex-wrap:wrap}
.pjd-next-open{flex:1;min-width:0;display:flex;align-items:center;gap:12px;
  padding:11px 12px;border-radius:10px;text-align:left;
  transition:background var(--t-hover)}
.pjd-next-open:hover{background:var(--surface-2)}
.pjd-next-t{font-size:14.5px;color:var(--text);font-weight:500;min-width:0;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.pjd-next-meta{margin-left:auto;display:flex;align-items:center;gap:7px;flex-shrink:0}
.pjd-next-due{font-size:11px;color:var(--muted);font-variant-numeric:tabular-nums}
.pjd-next-pri{font-size:10px;text-transform:capitalize;border-radius:5px;padding:2px 7px;
  background:var(--surface-2);color:var(--muted)}
.pjd-next-pri.pri-urgent{color:var(--p-urgent-ink);background:var(--p-urgent-bg)}
.pjd-next-pri.pri-high{color:var(--p-high-ink);background:var(--p-high-bg)}
.pjd-next-sec .btn-sm{flex-shrink:0;margin-right:6px}

.pjd-tasks{display:flex;flex-direction:column;gap:7px;min-height:8px}
.pjd-tasks.is-empty{min-height:0}
.pjd-done{margin-top:10px}
.pjd-done summary{font-size:12px;color:var(--muted);cursor:pointer;padding:6px 2px;
  list-style:none}
.pjd-done summary::-webkit-details-marker{display:none}
.pjd-done summary::before{content:"▸ ";color:var(--muted)}
.pjd-done[open] summary::before{content:"▾ "}
.pjd-done summary:hover{color:var(--text-2)}
.pjd-tasks-done{margin-top:6px;opacity:.72}

/* Grows with what is in it rather than reserving a large empty block. */
.pjd-notes{width:100%;background:var(--surface);border:1px solid var(--border);
  border-radius:12px;padding:14px 16px;font-size:13.5px;line-height:1.65;color:var(--text);
  resize:vertical;min-height:120px;field-sizing:content;max-height:60vh}
.pjd-notes:focus{outline:none;border-color:var(--border-strong)}
/* The save state is a WORD, never a silent colour, and never a false "Saved". */
.pjd-save{font-size:11.5px;color:var(--muted);transition:color var(--t-hover)}
.pjd-save[data-state="saved"]{color:var(--ok)}
.pjd-save[data-state="error"]{color:var(--danger);cursor:pointer;text-decoration:underline}
.pjd-save[data-state="dirty"]{color:var(--warn)}

/* ── Pick an existing task ── */
.modal-picker{max-width:520px;display:flex;flex-direction:column;max-height:76vh}
.tp-search{padding:0 20px 12px}
.tp-list{overflow-y:auto;padding:0 14px 16px;display:flex;flex-direction:column;gap:5px}
.tp-row{display:flex;flex-direction:column;gap:4px;align-items:flex-start;text-align:left;
  padding:11px 13px;border-radius:10px;transition:background var(--t-hover)}
.tp-row:hover{background:var(--surface-2)}
.tp-row:focus-visible{outline:none;box-shadow:0 0 0 2px rgba(var(--accent-rgb),.55)}
.tp-title{font-size:13.5px;color:var(--text)}
.tp-meta{display:flex;align-items:center;gap:6px;flex-wrap:wrap}
.tp-bucket,.tp-due{font-size:10.5px;color:var(--muted);background:var(--surface-2);
  border-radius:5px;padding:2px 7px;text-transform:capitalize}
.tp-none{font-size:12.5px;color:var(--muted);padding:20px 6px;text-align:center}
.tp-hint{font-size:12px;color:var(--muted);line-height:1.55;margin:0;padding:0 20px 12px}
.tp-auto{padding:0 14px 10px;border-bottom:1px solid var(--hairline);margin-bottom:10px}
.tp-row-auto .tp-title{color:var(--text-2)}
.tp-auto-why{font-size:11px;color:var(--muted)}
.tp-row.is-current{background:var(--accent-soft)}
.tp-current{font-size:10px;font-weight:600;color:var(--accent);
  background:rgba(var(--accent-rgb),.14);border-radius:5px;padding:2px 7px}

/* The project a task belongs to, named. Replaces the "arrives with Projects"
   placeholder that sat in every task editor. */
/* ── Project context on a Today card (E2.3) ─────────────────────────────
   A NAME and a link, never a colour: colour cannot say WHICH project, and a
   task that belongs to one should be able to take you there. It sits in the
   existing meta line, so adding it does not move the title. */
.tm-project{font-size:11px;color:var(--accent);max-width:220px;overflow:hidden;
  text-overflow:ellipsis;white-space:nowrap;transition:color var(--t-hover)}
.tm-project:hover{color:var(--text);text-decoration:underline}
.tm-next{font-size:10px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;
  color:var(--accent);background:var(--accent-soft);border-radius:5px;padding:1px 6px}

/* Next action carries what the task row carries. */
.pjd-next-sched,.pjd-next-steps,.pjd-next-bucket{font-size:10.5px;color:var(--muted);
  background:var(--surface-2);border-radius:5px;padding:2px 7px;text-transform:capitalize}

.m-project{display:flex;align-items:center;gap:10px;font-size:11.5px;
  padding:10px 0 2px;border-top:1px solid var(--hairline);margin-top:4px}
.m-project-lbl{font-size:10px;font-weight:700;letter-spacing:.12em;text-transform:uppercase;
  color:var(--muted)}
.m-project-name{color:var(--text-2)}

.pm-outcome{min-height:56px}

/* A decision with consequences, asked properly — never a browser confirm(),
   which cannot show the counts the decision turns on or say what each choice
   will do. */
.modal-choice{max-width:440px}
.ch-title{font-size:18px;font-weight:700;letter-spacing:-.3px;margin:0;color:var(--text)}
.ch-body{font-size:13px;color:var(--text-2);line-height:1.6;padding-top:0}
.ch-choices{display:flex;flex-direction:column;gap:8px;padding:16px 20px 20px}
.ch-choice{display:flex;flex-direction:column;gap:3px;align-items:flex-start;text-align:left;
  padding:13px 15px;border-radius:11px;background:var(--surface-2);
  transition:background var(--t-hover)}
.ch-choice:hover{background:var(--surface-3)}
.ch-choice:focus-visible{outline:none;box-shadow:0 0 0 2px rgba(var(--accent-rgb),.55)}
.ch-choice-l{font-size:13.5px;font-weight:600;color:var(--text)}
.ch-choice-d{font-size:12px;color:var(--muted);line-height:1.45}
.ch-choice.is-danger .ch-choice-l{color:var(--danger)}
.ch-choice.is-quiet{background:transparent}
.ch-choice.is-quiet:hover{background:var(--surface-2)}

/* Undo. Nothing destructive happens without one. */
.undo-bar{position:fixed;left:50%;translate:-50% 0;bottom:calc(var(--composer-h) + 28px);
  z-index:400;display:flex;align-items:center;gap:14px;padding:11px 14px 11px 18px;
  border-radius:12px;background:var(--surface-3);box-shadow:var(--e3);
  font-size:13px;color:var(--text-2)}

@media (max-width:1180px){
  body:has(.pj-head) .main-wrap{grid-template-columns:minmax(0,1fr)}
}
/* Tablet and below: two lines, and every action reachable by touch. */
@media (max-width:900px){
  .pj-head-row{flex-direction:column;align-items:stretch;gap:12px}
  .pj-row{flex-direction:column;gap:10px;padding:14px 15px}
  .pj-side{align-items:flex-start;width:100%;flex-direction:row;justify-content:space-between;
    flex-wrap:wrap;gap:8px}
  .pj-outcome{white-space:normal}
  /* No hover-only affordances on touch. */
  .pj-row .pj-more{opacity:1}
  .pj-more{width:38px;height:38px}
  .pj-filter{height:38px;padding:0 14px}
  /* The heading mark carries the group identity on a phone. No thick coloured
     card borders, and no loss of content width. */
  .pj-list{gap:24px}
  .pj-group-h{padding-left:10px}
  .tp-row{padding:13px}
  .tp-hint{padding:0 16px 12px}
  .pjd-meta{gap:7px}
  .pjd-meta .cf-ctl{min-height:38px}
}

/* ── Ambient stars ──────────────────────────────────────────────────
   Decorative only: behind everything, never interactive, never in the
   layout. Fixed so it does not repaint on scroll. */
#los-stars{position:fixed;inset:0;width:100vw;height:100vh;z-index:-1;
  pointer-events:none;user-select:none}
/* Only the shell needs lifting above the star layer. Listing the fixed
   elements here as well set `position:relative` on them, which silently
   overrode `.modal{position:fixed}` and threw the dialog off centre — every
   one of them already carries its own position and z-index. */
/* No z-index here, deliberately.
 *
 * `.shell{position:relative;z-index:1}` existed only to sit above the star
 * field, and it cost the entire mobile drawer. A positioned element with a
 * z-index creates a STACKING CONTEXT, so the sidebar's z-index:120 only ever
 * competed with its siblings inside the shell. From outside, the whole
 * application was z-index:1 — and `.drawer-scrim`, a body-level child at
 * z-index:110, therefore painted above all of it. The drawer opened, dimmed
 * itself, and swallowed every tap: Today, Calendar, Projects, Diary, Library
 * and Settings alike.
 *
 * The drawer has since been replaced by the bottom navigation, but the rule
 * outlives the surface that taught it: the star field is background, so it
 * goes behind at -1, and the shell takes no stacking context. Anything fixed
 * and above the application — a sheet, a modal, the navigation bar — is a
 * sibling of the shell and its z-index means what it says. */
.shell{position:relative}
@media (prefers-reduced-motion:reduce){#los-stars{display:none}}
html[data-motion="reduced"] #los-stars{display:none}

@media (max-width:1500px){ :root{--gutter:40px} }
@media (max-width:1360px){ :root{--rail-w:320px;--gutter:32px} }

@media (max-width:1180px){
  /* Tablet: the rail moves BELOW the content as a row of cards — Legacy's
     behaviour. It never simply disappears. */
  .main-wrap{grid-template-columns:minmax(0,1fr);gap:28px;padding:28px 28px 140px 28px}
  .rail{position:static;max-height:none;overflow:visible;
    display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:14px}
  .rail-today{grid-column:1/-1}
  .buckets{grid-template-columns:repeat(2,minmax(0,1fr))}
}
@media (max-width:1024px){
  .buckets{grid-template-columns:1fr;gap:16px}
  .bucket.future{grid-column:auto}
}
/* ── The drawer is gone ──────────────────────────────────────────────────
   The sidebar used to slide in from the left on a phone behind a hamburger,
   over a fixed scrim. Two things were wrong with it and only one was a bug.

   The bug: `.shell` carried `position:relative;z-index:1`, which made it a
   stacking context, so the scrim — a sibling at z-110 — painted above the
   entire application and swallowed every tap. Nothing in the app could be
   used while the drawer was open, including closing it.

   The design problem is the one that mattered: a drawer is a desktop
   sidebar with a hinge. Every destination sat two taps deep, off-screen,
   behind a control that says nothing about what is behind it.

   Both are answered by the bottom navigation in mobile.css, which is part
   of the page and cannot paint over it. The phone rules that were in this
   block moved there with it. */

@media (max-width:768px){
  .hist-row{grid-template-columns:18px 1fr;row-gap:4px}
  .hist-meta{grid-column:2}
  /* Bottom sheet, not a desktop dialog crushed to 375px. */
  .modal{left:0;right:0;bottom:0;top:auto;transform:none;width:100%;
    max-height:92vh;border-radius:20px 20px 0 0}
  .modal-narrow{width:100%}
  .m-body{padding-bottom:calc(18px + env(safe-area-inset-bottom))}
  .m-grid{grid-template-columns:1fr}
  .m-foot{padding-bottom:calc(14px + env(safe-area-inset-bottom));flex-wrap:wrap}
  .t-actions{opacity:1}
  .t-btn{width:44px;height:44px}
  .set-row{flex-wrap:wrap}
}

/* The Move button is the ONLY non-drag way to move a task, so it must never
   depend on hover. A tablet that reports `hover: hover` would otherwise hide it. */
@media (hover:none),(pointer:coarse),(max-width:768px){
  .grip{display:none}
  .t-actions{opacity:1}
  .icon-btn{width:44px;height:44px;font-size:16px}
  .task{padding:12px 8px 12px 16px;gap:12px}
  .hist-row{padding:14px 12px;min-height:44px}

  /* Every interactive control gets a 44px minimum. Where making the control
     itself 44px would wreck the layout — a 44px tick would dominate a task
     card, a 44px chip row would not fit — the VISIBLE size is kept and the
     hit area is extended with a transparent overlay instead. */
  .nav a,.cmdk,.who,.chip,.rail-area,.seg button,.btn{min-height:44px}
  .chip{display:inline-flex;align-items:center}
  .seg button{display:grid;place-items:center}
  .mobile-bar .m-btn{flex:0 0 44px}   /* flex was shrinking these to 32px wide */

  /* Small round controls: visible size kept, hit area grown to 44px. A 44px
     tick would dominate a 50px card and a 44px ring would break the rail's
     rhythm, so the finger target is the overlay, not the paint.
     `.tick` is the pre-C4 name and is kept so older surfaces stay covered. */
  .tick,.t-tick,.hb-ring,.hb-add,.m-tick,.m-close{position:relative}
  .tick::after,.t-tick::after,.hb-ring::after,.hb-add::after,
  .m-tick::after,.m-close::after{content:'';position:absolute;top:50%;left:50%;
    width:44px;height:44px;transform:translate(-50%,-50%)}
  .tick{width:24px;height:24px;font-size:14px}

  /* Text buttons stretch to the full row instead of hugging their glyphs. */
  .t-title,.hb-name{position:relative;padding:6px 0}
  .t-title::after,.hb-name::after{content:'';position:absolute;
    top:50%;left:0;right:0;height:44px;transform:translateY(-50%)}
}

/* ── Mobile release pass ──────────────────────────────────────────────────
   The C4 touch pass already gave the ticks, titles and habit rings a
   transparent 44px `::after` hit area, and that pattern works — measured on a
   375px viewport, every one of those controls passes. What it did not cover
   is below, found by walking every route at phone widths rather than by
   reading the stylesheet. */
@media (hover:none),(pointer:coarse),(max-width:768px){

  /* A task row was `20px 89px 160px` on a 375px screen: three 44px controls
     and a drag grip took 160px of a 291px row and left the title 89px, which
     truncates almost every task to two words. The controls are right to be
     44px — they just do not belong on the same line as the title. */
  .t-row{grid-template-columns:auto minmax(0,1fr);row-gap:9px}
  .t-actions{grid-column:2;justify-content:flex-start;gap:4px;opacity:1}
  /* Dragging is a mouse affordance. The touch path is the two Move buttons,
     which are already there. The existing rule hides `.grip`; the task row
     uses `.t-grip`, so it was never covered and kept its 22px. */
  .t-grip{display:none}

  /* The Diary header overflowed the viewport by 26px: `.page-actions` is
     `flex-shrink:0`, so at 321px of available width its 385px of date
     navigation simply hung off the right edge. It gets its own row instead. */
  .page-actions{margin-left:0;width:100%;flex-wrap:wrap;flex-shrink:1}

  /* Controls still under 44px after the C4 pass. Painted size kept where
     growing it would break the layout; the finger target is the overlay. */
  .tm-project,.lib-obj-more,.cal-nav,.util-btn{position:relative}
  .tm-project::after,.lib-obj-more::after,.cal-nav::after,.util-btn::after{
    content:'';position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);
    width:max(100%,44px);height:44px}

  /* Wide enough already, just short. These can simply grow. */
  .cal-today,.cal-layer,.pj-filter,.cal-add,.set-nav-item,
  .cal-modes [role="tab"]{min-height:44px}
  .cal-layer{padding:0 12px}
  .lib-search input,.lib-search .input{min-height:44px}

  /* A row of lifecycle filters must scroll rather than shrink into a row of
     unreadable stubs. */
  .pj-filters{display:flex;flex-wrap:nowrap;overflow-x:auto;gap:8px;
    scrollbar-width:none;-webkit-overflow-scrolling:touch}
  .pj-filters::-webkit-scrollbar{display:none}
  .pj-filter{flex:0 0 auto}
}

/* ═══════════════════════════════════════════════════════════════════════
   LIBRARY (Phase F2)
   The Book's geometry is lifted from docs/library-v2-legacy-book-audit.md,
   which was read from the Legacy source. The A4 ratio, the 6px gutter, the
   28/32/18/58 padding, the 46px margin stripe, the mirrored inset edge and
   the 30px ruled cycle are the book — they are not values to round off.
   ═══════════════════════════════════════════════════════════════════════ */
:root{
  /* Paper. Dark, warm, and lighter than the surfaces around it, so a page
     reads as paper on a desk rather than as another panel. A white page in a
     dark application is a searchlight. */
  --paper:#262233; --paper-2:#221E2E;
  --paper-line:rgba(255,255,255,.055);
  --paper-ink:#EDEAF6; --paper-ink-2:#B3AAC6;
  --paper-shadow:0 18px 44px rgba(0,0,0,.34),0 2px 6px rgba(0,0,0,.2);
  /* The six section colours from the audit. */
  --a-peach:#F0A07A; --a-sage:#8CC7A1; --a-lavender:#B69BF0;
  --a-gold:#E8C062;  --a-blue:#79AEE0; --a-rose:#EE8FA8;
}
[data-accent="peach"]{--accent-c:var(--a-peach)}
[data-accent="sage"]{--accent-c:var(--a-sage)}
[data-accent="lavender"]{--accent-c:var(--a-lavender)}
[data-accent="gold"]{--accent-c:var(--a-gold)}
[data-accent="blue"]{--accent-c:var(--a-blue)}
[data-accent="rose"]{--accent-c:var(--a-rose)}

/* Library uses its width for Library. The same mechanism Calendar and
   Projects use — a marker class in the page head, watched by the shell.
   Hiding the rail is not enough on its own: the grid track it sat in has to
   collapse too, or the column keeps the 320px and the book stays narrow. */
body:has(.lib-page) .main-wrap{grid-template-columns:minmax(0,1fr) 0}
body:has(.lib-page) .rail{display:none}

/* ── Overview ─────────────────────────────────────────────────────────── */
.lib-bar{display:flex;align-items:center;gap:16px;flex-wrap:wrap;margin:0 0 22px}
.lib-filters{display:flex;gap:8px;flex-wrap:wrap;min-width:0}
.lib-bar-right{display:flex;align-items:center;gap:10px;margin-left:auto}
.chip .chip-n{margin-left:6px;font-size:10.5px;font-weight:700;opacity:.6;
  font-variant-numeric:tabular-nums}
.lib-search{display:flex;align-items:center;gap:8px;background:var(--surface);
  border:1px solid var(--border);border-radius:10px;padding:0 12px;height:var(--btn-h,38px);
  color:var(--muted);transition:border-color var(--t-hover),background var(--t-hover)}
.lib-search:focus-within{border-color:var(--accent);background:var(--surface-2)}
.lib-search input{background:none;border:0;outline:0;color:var(--text);font-size:13px;
  width:150px;min-width:0;font-family:inherit}
.lib-search input::placeholder{color:var(--muted)}
.btn.on{background:var(--accent-soft);color:#fff}

.lib-sec{margin:0 0 30px}
.lib-sec-h{font-size:12px;font-weight:700;letter-spacing:.09em;text-transform:uppercase;
  color:var(--muted);margin:0 0 12px}
.lib-grid{display:grid;gap:14px;
  grid-template-columns:repeat(auto-fill,minmax(248px,1fr))}
.lib-skel{height:154px;border-radius:16px}
/* The waiting shelf (D2.2 §2). Card-shaped at the size the cards will be, and
   a filter bar above them, so the arrival is a fill rather than a reflow. The
   giant 60vh rectangle this replaces was the visible half of the regression. */
.lib-skel-bar{display:flex;gap:8px;margin:0 0 18px;flex-wrap:wrap}
.lib-skel-pill{height:32px;width:82px;border-radius:999px;display:block}
.lib-skel-pill:nth-child(2){width:104px}
.lib-skel-pill:nth-child(3){width:74px}
.lib-skel-pill:nth-child(4){width:96px}
/* A book, opening: the spread's own proportions, not a slab. */
.bk-skel{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:6px;
  width:100%;max-width:1320px;aspect-ratio:420/297;margin:0 auto}
.bk-skel-page{display:block;width:100%;height:100%;border-radius:14px}
@media (max-width:820px){
  .bk-skel{grid-template-columns:minmax(0,1fr);aspect-ratio:210/297;max-width:780px}
  .bk-skel-page:last-child{display:none}
}

.lib-card{position:relative;background:var(--surface);border-radius:16px;
  padding:15px 16px 13px 20px;box-shadow:var(--e2);cursor:pointer;overflow:hidden;
  display:flex;flex-direction:column;gap:7px;min-height:150px;
  border:1px solid transparent;
  transition:transform var(--t-hover),box-shadow var(--t-hover),border-color var(--t-hover)}
.lib-card:hover{transform:translateY(-2px);box-shadow:var(--e3);border-color:var(--border)}
.lib-card:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
/* The card's flat edge. Renamed from `.lib-spine` in L3: a real spine is now
   an object on a shelf with width and a gradient, and two different things
   must not answer to one selector. This is the ITEM VIEW's card only — the
   overview has no cards left. */
.lib-spine-flat{position:absolute;left:0;top:0;bottom:0;width:4px;background:var(--a-lavender);
  opacity:.75}
.lib-card[data-type="book"] .lib-spine-flat{background:var(--a-peach)}
.lib-card[data-type="document"] .lib-spine-flat{background:var(--a-blue)}
.lib-card[data-type="link"] .lib-spine-flat{background:var(--a-sage)}
.lib-card[data-type="image"] .lib-spine-flat{background:var(--a-rose)}
.lib-card[data-type="video"] .lib-spine-flat{background:var(--a-gold)}
.lib-card[data-type="file"] .lib-spine-flat{background:var(--muted)}
.lib-card.is-archived{opacity:.62}
.lib-card.is-archived .lib-spine-flat{opacity:.3}

.lib-card-top{display:flex;align-items:center;gap:8px}
.lib-card-icon{color:var(--text-2);display:flex}
.lib-card-type{font-size:10.5px;font-weight:700;letter-spacing:.07em;text-transform:uppercase;
  color:var(--muted)}
.lib-card-arch{font-size:10px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;
  color:var(--muted);background:var(--surface-2);border-radius:6px;padding:2px 7px}
.lib-card-more{margin-left:auto;background:none;border:0;color:var(--muted);cursor:pointer;
  border-radius:8px;width:26px;height:26px;display:grid;place-items:center;
  opacity:0;transition:opacity var(--t-hover),background var(--t-hover),color var(--t-hover)}
.lib-card:hover .lib-card-more,.lib-card:focus-within .lib-card-more{opacity:1}
.lib-card-more:hover{background:var(--surface-2);color:var(--text)}
.lib-card-more:focus-visible{opacity:1;outline:2px solid var(--accent);outline-offset:1px}
.lib-card-title{font-size:15px;font-weight:600;color:var(--text);margin:0;line-height:1.35;
  display:-webkit-box;-webkit-line-clamp:2;line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
.lib-card-desc{font-size:12.5px;color:var(--text-2);margin:0;line-height:1.5;
  display:-webkit-box;-webkit-line-clamp:2;line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
.lib-card-foot{margin-top:auto;display:flex;align-items:center;gap:8px;
  font-size:11.5px;color:var(--muted);padding-top:6px}
.lib-card-when{margin-left:auto;font-variant-numeric:tabular-nums}

/* ── The shelf (Phase L3) ───────────────────────────────────────────────
   A SHELF IS A SCROLLABLE COLLECTION, NOT A CAROUSEL. Everything here is an
   ordinary `overflow-x` list that would work with no JavaScript at all; the
   script adds prominence, keys and arrows on top of something already usable.

   The room is made from three things and no more: a ledge line, a shadow the
   objects sit in, and a back wall one shade darker than the page. No wood, no
   furniture, no wallpaper — in a dark UI those read as decoration, and §20 asks
   for structure instead. */
.lib-shelves{display:flex;flex-direction:column;gap:26px}

.lib-shelf{margin:0}
.lib-shelf-head{display:flex;align-items:center;gap:10px;margin:0 0 6px;min-height:26px}
.lib-shelf-h{font-size:12px;font-weight:700;letter-spacing:.09em;text-transform:uppercase;
  color:var(--label);margin:0}
.lib-shelf-n{font-size:11px;font-weight:700;color:var(--muted);
  font-variant-numeric:tabular-nums;background:var(--surface);border-radius:999px;
  padding:1px 7px;line-height:17px}
.lib-shelf-note{font-size:11.5px;color:var(--muted)}

/* The arrows are SECONDARY (§7). Hidden until the shelf actually overflows,
   never a tab stop, and disabled at each end rather than looping — a control
   that wraps a shelf around is a carousel wearing arrows. */
.lib-shelf-nav{margin-left:auto;display:none;gap:4px}
.lib-shelf-nav.is-live{display:flex}
.lib-step{width:26px;height:26px;display:grid;place-items:center;border-radius:8px;
  background:var(--surface);border:1px solid var(--border);color:var(--text-2);
  cursor:pointer;transition:background var(--t-hover),color var(--t-hover),
  border-color var(--t-hover),opacity var(--t-hover)}
.lib-step:hover:not(:disabled){background:var(--surface-2);color:var(--text);
  border-color:var(--border-strong)}
.lib-step:disabled{opacity:.3;cursor:default}

/* The rail. `scroll-padding-inline` keeps a snapped object clear of the left
   edge so it reads as sitting ON the shelf rather than jammed against the frame.
   Snap is PROXIMITY, never mandatory (§22): mandatory snap fights trackpad
   momentum and turns a flick into a fight. */
/* ---- ONE SHELF PLANE (L3.2 S10) --------------------------------------
   Measured before this change: contact gap Diary -6px, Book +36px,
   Document +36px. The Diary hung 42px lower than everything else relative to
   its own shelf, because the personal rail carried different padding and the
   ledge is drawn from the rail's bottom edge.

   There is now ONE formula, and every rail obeys it:

       ledge line   = rail bottom - var(--shelf-drop)
       object bottom = ledge line - var(--shelf-contact)

   Objects may be any height ABOVE that line. They may not sit at arbitrary
   heights below it. A Document is shorter than a Book and rests on the same
   plane, which is what a shelf means. */
:root{
  --shelf-drop:30px;      /* ledge line, measured up from the rail's bottom  */
  --shelf-contact:4px;    /* object bottom to ledge: touching, not floating  */
  --shelf-head:52px;      /* breathing room above the tallest object         */
  --shelf-label:34px;     /* the label zone below the ledge                  */
}
/* `scroll`, not `auto`.
   A horizontal scrollbar lives inside the rail's border box, so a shelf that
   overflows loses 10px of height that a shelf which fits does not -- and the
   ledge is drawn from that bottom edge. Measured: books rail offsetHeight 287
   against clientHeight 277, personal rail 242 against 242, which put the Diary
   10px off every other object's plane. `scrollbar-gutter: stable` does not fix
   it: that property reserves the INLINE gutter, not the block-end one.
   Reserving it on every rail is what makes one baseline formula true. The track
   is transparent and the thumb does not render when there is nothing to
   scroll, so a shelf that fits shows nothing. */
/* NO VISIBLE GUTTER (S1 §33).
   `overflow-x: scroll` keeps a scrollbar gutter inside the border box, and the
   ledge is painted on the rail — so the drawn contact plane sat one gutter below
   the plane the Books actually stand on. Measured: exactly 10px of daylight
   under every object, on every shelf, which is what the review saw as "the Books
   are not touching the shelf".

   Measuring the gutter and subtracting it was tried and is fragile: the measure
   has to happen after layout, and a physical alignment should not depend on when
   a script ran. Removing the gutter removes the discrepancy — the rail still
   scrolls natively, still has its arrows, and every shelf still reserves exactly
   the same nothing, so the six baselines stay identical. */
.lib-rail{overflow-x:scroll;overflow-y:hidden;scrollbar-width:none;
  scroll-snap-type:x proximity;
  scroll-padding-inline:10px;overscroll-behavior-x:contain;
  /* §34 — headroom for the lift, INSIDE the scroll box. `overflow-y` cannot be
     visible while `overflow-x` is scroll (the browser forces it to auto), so a
     pulled object cannot escape the rail; it has to have somewhere to go within
     it. 48px clears the 32px lift plus the folio sheets that stand 12px proud
     of their lid, and it is a multiple of four. */
  /* The ledge is drawn at `100% - var(--shelf-drop)`, and the object carries its
     own `margin-bottom: var(--shelf-contact)`. So the rail must reserve
     drop MINUS contact — reserving drop PLUS contact counts the contact gap
     twice and floats every object 2 x contact above its own shelf. Measured
     before: objects 8px above the ledge line at 1280. */
  padding:var(--shelf-head) 0 calc(var(--shelf-drop) - var(--shelf-contact));
  margin:0 -4px;
  /* `scrollbar-width` is declared ONCE, at the top of this block. It used to
     be set to `thin` here as well, later in the same declaration block, which
     silently won — the rail kept its 10px gutter and the ledge kept sitting
     that far below the Books. */

  /* THE SHELF ITSELF (S22). A back plane the objects stand in front of, a lit
     front edge with thickness, and the floor wash where shadow gathers. Quiet
     enough that the objects stay the focal point -- the back plane is 1.4% white,
     which reads as depth rather than as a section band. */
  background-image:
    linear-gradient(to bottom,rgba(255,255,255,.014) 0,
      rgba(255,255,255,.014) calc(100% - var(--shelf-drop)),
      transparent calc(100% - var(--shelf-drop))),
    linear-gradient(to bottom,transparent calc(100% - var(--shelf-drop)),
      rgba(255,255,255,.13) calc(100% - var(--shelf-drop)),
      rgba(255,255,255,.13) calc(100% - var(--shelf-drop) + 1px),
      rgba(255,255,255,.04) calc(100% - var(--shelf-drop) + 1px),
      rgba(255,255,255,.04) calc(100% - var(--shelf-drop) + 4px),
      transparent calc(100% - var(--shelf-drop) + 4px)),
    linear-gradient(to bottom,transparent calc(100% - var(--shelf-drop) + 4px),
      rgba(0,0,0,.22) 100%)}
.lib-rail::-webkit-scrollbar{display:none}

/* ---- DENSITY, BY COLLECTION SIZE (L3.2 S23/S24) ----------------------
   Two Books must read as two Books. A dozen should read as a shelf.

   L3.1 used one formula for both: 64px of overlap on a 150px object, so each
   book showed 86px and the NEXT book's dark spine landed in the middle of the
   previous cover. The review is right that this reads as layered cards -- a
   spine is a dark vertical band, and cover/band/cover/band is a card stack.

   The overlap is now exactly the FORE-EDGE. Books touch: each one's page block
   tucks behind its neighbour's spine, which is what books on a shelf actually
   do, and every cover stays whole. Below four objects they do not even touch --
   they sit apart with a real gap. */
.lib-row{list-style:none;margin:0;padding:0 10px;display:flex;align-items:flex-end;
  column-gap:14px;width:max-content;min-width:100%}
.lib-shelf-book .lib-row{column-gap:16px}
/* Dense means TOUCHING, not overlapping.
   A negative margin was drafted here and then removed: tucking each book's
   fore-edge behind its neighbour's spine hides the single strongest "this is a
   bound object" cue on the shelf, to buy 6px. Books that simply touch already
   read as a shelf -- each page block sits against the next spine, which is what
   a real shelf looks like -- and every cover stays whole. */
.lib-rail.is-dense .lib-row{column-gap:0}
/* Touching is a BOOK rule. Four documents are four cards, and cards that touch
   clip one another — measured on "Recently opened", the Insurance card was
   painted over the Rage 2026 spine and Trifusion invoice was cut off by Lease
   agreement. Resource shelves keep their gap however many they hold. */
.lib-shelf-res .lib-rail.is-dense .lib-row{column-gap:14px}
/* §33 — Books touch, always. The adaptive density rule was written for the old
   flat cover-cards, where three touching covers read as a deck; with spines the
   opposite is true, and a 16px gap between three Books is what the review meant
   by "not touching one another". Two Books leaning together are the start of a
   collection; two Books 16px apart are two objects on display. */
/* Touching was too tight to read as separate volumes; widely spaced read as
   objects on display. One token, one number (§4). */
.lib-shelf-book .lib-row,.lib-shelf-personal .lib-row{column-gap:var(--lib-book-gap)}
/* ── A slot has ONE transform, composed from named parts ────────────────
 *
 * There used to be two rules that each set `transform` outright — the pulled
 * neighbour nudge and nothing else — so anything wanting to move a slot for a
 * second reason had to overwrite the first. Hover needed exactly that, and
 * two writers of one property is how they end up fighting.
 *
 * Now every reason contributes a named part and the transform adds them up.
 *   --slot-shift  making room for a Book that is OPEN
 *   --slot-peek   making room for a Book somebody is LOOKING at
 */
.lib-slot{scroll-snap-align:start;flex:0 0 auto;position:relative;
  transform:translateX(calc(var(--slot-shift, 0px) + var(--slot-peek, 0px)));
  transition:transform var(--t-select)}

/* PULLED-FORWARD BREATHING ROOM (S7). The neighbours step aside rather than
   the rail reflowing -- a transform, so nothing is measured and nothing moves
   that is not adjacent. */
/* Superseded by the explicit `is-nudge-*` classes below: only the two immediate
   neighbours move, and the code that pulls a Book is what says which two. */

/* ── Where one kind ends and another begins ─────────────────────────────
 *
 * "Recently opened" holds whatever you last touched, which can be a Book, a
 * document, a link and a file in any order. Rendered as one undifferentiated
 * run they read as a single row of mismatched objects — a 34px spine wedged
 * between two 178px cards.
 *
 * So the shelf keeps ONE heading and one scroll, and puts the Books first with
 * a marked break after them. Not a second shelf: they are one answer to one
 * question, and splitting the heading would say they were two.
 */
/* The LAST Book in the run reserves its own cover.
   A Book occupies only its spine — 30-odd px — while its cover is 126 and
   simply overhangs whatever comes next. On the Books shelf that is hidden
   behind the following spine, which is the point. At the end of a run there is
   nothing to hide it, so it landed on the first card. The clearance is the
   Book's own arithmetic, cover minus spine, so a thick Book and a thin one
   both come out right. */
.lib-slot.is-kind-break-before .lib-obj{
  margin-right:calc(var(--bw, 126px) - var(--bt, 32px))}
.lib-slot.is-kind-break{margin-left:20px;position:relative}
.lib-slot.is-kind-break::before{content:'';position:absolute;
  left:-11px;bottom:0;width:1px;height:60%;
  background:linear-gradient(180deg,transparent,var(--hairline) 40%,var(--hairline))}

/* Small collections are centred rather than pinned left (S26). */
.lib-row{justify-content:flex-start}
.lib-rail:not(.is-full) .lib-row{justify-content:center}

/* Small collections are centred rather than pinned left (§26). `auto` margins
   on a flex row do nothing until there is slack, so this is inert the moment
   the shelf overflows — one rule, both cases, no measuring. */
.lib-row{justify-content:flex-start}
.lib-rail:not(.is-full) .lib-row{justify-content:center}

/* ── The object, and its five states (§23) ───────────────────────────── */
/* ---- Resting, hover, focus, pulled, opening, returned ----------------
   Six states, and NONE happens on its own. A shelf nobody has touched has no
   raised object at all. */
.lib-obj{position:relative;display:block;cursor:pointer;
  border:0;background:none;padding:0;font-family:inherit;text-align:left;
  margin-bottom:var(--shelf-contact);
  transition:transform var(--t-select)}
/* 1. RESTING -- nothing, stated explicitly so it cannot be given a look later. */
/* 2. HOVER -- deliberately SMALLER than a pull (S21): 3px, and no reveal.
      Hover says "this responds"; pulling says "this one". */
/* HOVER (L3.4 §6). 3px was too quiet to answer "which Book am I over?", and the
   authenticated review said so. 8px, plus the cloth lifting a shade and a
   deeper contact shadow -- but still no reveal and still nothing that could be
   mistaken for the pulled state. The hierarchy has to stay
   RESTING < HOVER < PULLED, and 8px against 32px keeps it. */
.lib-obj:hover{transform:translateY(calc(-1 * var(--lib-book-hover)));z-index:3}
.lib-obj:hover .lib-spine{filter:brightness(1.16)}
.lib-obj:hover .lib-folio-lid,.lib-obj:hover .lib-clip,
.lib-obj:hover .lib-jacket-body,.lib-obj:hover .lib-frame{filter:brightness(1.1)}
/* 3. FOCUS -- the app accent, :focus-visible only, and the only state with an outline. */
.lib-obj:focus{outline:none}
.lib-obj:focus-visible{outline:2px solid var(--accent);outline-offset:4px;
  border-radius:3px;z-index:4}
/* 4. PULLED FORWARD -- ONE depth cue, not three (S6).
      32px of travel, NO SCALE at all, and a deeper shadow. About a sixth of the
      object's height: unmistakably out of the row, still plainly on the shelf.

      32 rather than 30, and the reason is arithmetic. The translate must land
      on a whole DEVICE pixel at every supported ratio or the pulled object
      rasterises on a different subpixel phase from the resting one -- which is
      the blur the review saw. 30 x 1.25 = 37.5, half a device pixel out. Any
      multiple of 4 is exact at 1, 1.25, 1.5 and 2; 32 is the smallest one in
      S6's list that also reads unmistakably as pulled. */
.lib-obj.is-pulled{transform:translateY(calc(-1 * var(--lib-book-pull)));z-index:6}
/* NEIGHBOUR CLEARANCE (§14). Only the two Books either side, and by transform,
   so the rest of the shelf never reflows and no hole opens. */
/* 16, not 14. Every travel on this shelf must land on a whole DEVICE pixel at
   1, 1.25, 1.5 and 2, or the moved object rasterises on a different subpixel
   phase from its neighbours -- which is the blur L3.2 chased down. 14 x 1.25 is
   17.5, half a device pixel out; any multiple of 4 is exact, and 16 is inside
   the 10-18px §14 asks for. */
/* Both sides read the SAME token, so they can never drift apart and there is no
   second 16 hiding anywhere (§21). */
/* The left neighbour only has to become readable; the right side has to get out
   from under a 126px cover. `--lib-book-clear` is the cover's overhang past the
   Book's own spine, measured per Book when it is pulled. */
/* ── Nothing to the LEFT moves any more, and the reason is measurement ──
 *
 * The left neighbour used to step 16px left "to become readable". Measured on
 * a real shelf, it had nothing to become readable FROM: a pulled Book travels
 * straight up, its spine stays in exactly the same 34px band it occupied at
 * rest, and its cover overhangs to the RIGHT. The left neighbour was never
 * covered by anything.
 *
 * What the 16px did instead was slide ONE book into the one behind it — which
 * does not move — so it overlapped it by 16px and, being later in the DOM,
 * painted on top. That is precisely the "the first book to the left comes out
 * a lot more than the rest and comes in front of the second most left book"
 * that was reported. The nudge was solving a problem that did not exist and
 * causing one that did.
 *
 * The right side still moves, and moves as a GROUP — every following slot by
 * the same amount — so their spacing is exactly what it was. That is why the
 * right-hand side always looked correct and the left never did.
 */
.lib-slot.is-nudge-r{--slot-shift:calc(var(--lib-book-clear, 0px) + var(--lib-book-neighbour))}
/* The left side moves too, and as a GROUP — see the note in setNeighbours.
   A pulled Book grows from a 47px spine into a 126px cover, so it swallows the
   13px it was already overlapping on its left; 28px turns that into a 15px gap
   against 16px on the right. Every preceding slot by the same amount, so their
   own spacing is untouched and nothing overlaps anything. */
.lib-slot.is-nudge-l{--slot-shift:calc(-1 * var(--lib-book-left-clear))}

/* ── HOVER MAKES ROOM (reported) ────────────────────────────────────────
 *
 * Hover used to lift the Book 8px and raise its z-index, and move nothing.
 * With the Books touching, the hovered Book's 126px cover — normally hidden
 * behind the next three spines — suddenly painted on top of them, which reads
 * as the cover cutting THROUGH its neighbour rather than standing in front of
 * it. "It looks like it's just glitching through the book."
 *
 * So everything to its right steps aside a little, and you see some of the
 * front. `~` and not `+`: the whole run moves by the same amount, keeping the
 * spacing between them — moving only the next one would recreate, in
 * miniature, exactly the overlap being fixed on the left.
 *
 * Books only. A row of document cards has no cover to reveal and nothing to
 * make room for.
 */
.lib-shelf-book .lib-rail:not(.has-pulled) .lib-slot:has(> .lib-obj:hover) ~ .lib-slot,
.lib-shelf-personal .lib-rail:not(.has-pulled) .lib-slot:has(> .lib-obj:hover) ~ .lib-slot{
  --slot-peek:var(--lib-book-peek)}

/* ── While a Book is open, nothing peeks ────────────────────────────────
 *
 * Reported twice, as two symptoms of one thing. Hovering a Book that is
 * already open pushed its neighbour further away — and it is showing its front
 * cover, so it is revealing nothing and has nothing to make room for. Hovering
 * the Book to its LEFT shoved the open Book sideways for the same reason.
 *
 * The peek is a RESTING-shelf affordance: it says "there is a cover behind this
 * spine". Once a Book is pulled, the shelf is in a different mode and the pull
 * owns the spacing. One rule, no exceptions, and nothing on the shelf ever
 * moves for two reasons at once. Hover still lifts and brightens. */
/* THE PULLED BOOK DOES NOT CHANGE THE LAYOUT.
   Widening it to its cover looked right and reflowed the whole rail: measured,
   the neighbours moved 48.5px when §14 asks for 10-18, because a centred row
   redistributes every time one child grows. So the Book keeps its shelf width,
   the cover simply overhangs, and the two nudge classes are the ONLY thing that
   moves. What the cover needs instead is somewhere to be clicked -- an
   untransformed shim at the cover's own footprint, because the faces
   themselves take no pointer input. */
.lib-obj.lib-book.is-pulled::before{content:'';position:absolute;left:0;top:0;
  width:var(--bw);height:100%}
.lib-obj.lib-book.is-pulled::after{left:0;right:calc(100% - var(--bw))}
.lib-obj.is-pulled .lib-foot{opacity:1;transform:translateY(0);pointer-events:auto}
/* 5. OPENING -- there is no opening state. The Book view arrives in 18ms, so a
      320ms fly-away got one frame and read as the Book moving twice for one
      click. The committed front-facing cover simply hands over. */
/* 6. RETURNED -- a soft glow in the object's own accent, never an outline. */
.lib-obj.is-returned .lib-cover,.lib-obj.is-returned .lib-res-face,
.lib-obj.is-returned .lib-frame{
  box-shadow:0 0 18px -2px color-mix(in srgb,var(--accent-c,var(--a-peach)) 55%,transparent),
    var(--obj-edge)}

/* ---- ONE CONTACT-SHADOW GRAMMAR (L3.2 S12) ---------------------------
   Two shadows, and they mean different things:

     CONTACT  a tight dark ellipse where the object meets the shelf. It is
              narrow, close, and it is what says "this is touching".
     DEPTH    a soft cast shadow that only appears when the object LIFTS.

   Resting = contact, no depth. Pulled = contact weakens and shrinks, depth
   appears. That is how a real object leaving a surface behaves, and it is why
   this is not an elevation scale: a resting object here has no cast shadow at
   all, whereas Material elevation gives everything one. */
.lib-obj::after{content:'';position:absolute;left:6%;right:6%;bottom:-3px;height:7px;
  border-radius:50%;
  background:radial-gradient(ellipse at center,
    rgba(0,0,0,.62) 0%,rgba(0,0,0,.30) 50%,transparent 74%);
  pointer-events:none;transition:opacity var(--t-select),transform var(--t-select)}
.lib-obj:hover::after{transform:scaleX(.96) scaleY(1.1);opacity:.85}
.lib-obj.is-pulled::after{transform:scaleX(.84) scaleY(1.5) translateY(6px);opacity:.5}

/* The edge shadow every face shares -- a hairline that gives the object an
   edge, NOT a card elevation. One token, so a Document and a Book cannot drift
   apart stylistically. */
:root{--obj-edge:0 2px 4px -2px rgba(0,0,0,.5),inset 0 0 0 1px rgba(255,255,255,.05);
      --obj-lift:0 18px 30px -14px rgba(0,0,0,.68),inset 0 0 0 1px rgba(255,255,255,.10)}

.lib-obj.is-archived{opacity:.5}

/* ══ THE BOOK AS A SOLID (L3.4 §9-§13) ═════════════════════════════════
   Four faces in ONE coordinate system, so when the Book turns every face turns
   with it and every printed line turns with the face it is printed on:

       back board   x = 0        spine        z = 0
       front cover  x = t        page block   z = -126

   The dimensions are real and they do not change. There is no width
   interpolation, no scale, and no stationary coloured filler standing in for
   depth -- the page block is a face of the box with page striations on it, at
   the end of the box opposite the spine.

   `--bt` is the thickness, from the Book's content. `--bh` is its height, from
   a sixteen-rung ladder. Both are set per Book on the element. */
/* ══ RESTING BOOK TUNING ═══════════════════════════════════════════════
   The four numbers that decide how a Book stands on the shelf. They live here
   and ONLY here — nothing below restates them — so the resting look can be
   tuned without touching geometry, and without another redesign.

     --lib-book-gap        5px    air between neighbouring resting Books
     --lib-book-lean       2deg   rightward lean (rotateZ)
     --lib-book-top-tilt   4deg   how far the Book tips back, exposing its head
     --lib-book-depth      6px    how far it stands proud, revealing its side

   Safe ranges and what each one does are in
   docs/library-v2-final-visual-direction.md → "Resting Book tuning". */
:root{
  /* RESTING POSE — chosen on the real shelf with the Book Tuner (S2.4).
     Books touching, standing square, tipped slightly FORWARD so you read the
     tail rather than the head, and turned a little to the left so the front
     board catches the eye. Nothing stands proud: the yaw does that work now,
     and the depth nudge only softened it. */
  --lib-book-gap:0px;         /* air between neighbouring Books      0-14px  */
  --lib-book-lean:0deg;       /* lean, left or right               -6- 6deg  */
  --lib-book-top-tilt:-4deg;  /* tips back (head) or forward (tail) -10-10deg */
  --lib-book-yaw:-6deg;       /* turns the back edge toward you    -12-12deg */
  --lib-book-depth:0px;       /* how far it stands proud             0-16px  */
  --lib-page-grain:90deg;     /* which way the page edges run                */
  /* RETRIEVAL */
  --lib-flat-lift:14px;       /* how far Documents, Media, Links and Files sit
                                 above the ledge. Books stay ON it — they are
                                 standing on a shelf; a folio in a tray is not. */
  --lib-book-hover:8px;       /* hover lift  — must stay under pull          */
  --lib-book-pull:32px;       /* pull travel                                 */
  --lib-book-neighbour:16px;  /* how far each immediate neighbour steps aside */
  /* How far the right-hand run steps aside on HOVER. Smaller than the pull's
     clearance on purpose: hover says "this responds", and a shelf that opens
     a 92px gap every time the pointer crosses it is a shelf that never sits
     still. A multiple of 4, like every other travel here, so it lands on a
     whole device pixel at 1, 1.25, 1.5 and 2. */
  --lib-book-peek:20px;
  /* How far everything LEFT of an open Book steps back. Larger than the peek
     because it has more to undo: at rest the Books overlap by 13px, and a
     pulled Book turns its 47px spine into a 126px cover on top of that. 28px
     leaves a 15px gap, against 16px on the right — the same, to the eye. */
  --lib-book-left-clear:28px;
}

/* Declared on `:root` rather than on the shelves, so a live override can win by
   being an inline style on the same element. `--d-turn` already lives with the
   other motion tokens and is the eighth control. */

.lib-book{--bw:126px;--bh:190px;--bt:32px;
  width:var(--bt);height:var(--bh);display:block;position:relative}

/* THE RESTING POSE.
   `.lib-stand` pivots at the BOTTOM of the spine, so the Book leans while
   standing on the shelf — a centre origin would swing the bottom edge off the
   ledge and the Book would read as floating and rotating (§11/§12).

   The order matters. `rotateX` first tips the Book back about its base, which
   is what brings the head into view; `rotateZ` then leans the standing Book to
   the right. Doing it the other way round tips it back along the already-leaned
   axis and the bottom corner lifts. `translateZ` stands it slightly proud, and
   the row's perspective turns that into a sliver of its side — a real
   projection of the real box, not a drawn edge. */
/* Like the volume and the faces, the stand takes NO pointer input. It is
   transformed, so its hit box is its projected quad — which after a lean and a
   tilt overhangs the neighbours, and the first thing that cost was a click on
   one Book pulling the one beside it. `.lib-obj` stays the only target. */
.lib-stand{position:absolute;inset:0;transform-style:preserve-3d;
  pointer-events:none;transform-origin:left bottom;
  /* Three axes and a nudge forward, in this order:
       rotateX  tips it back or forward about its base — the top tilt, which is
                what brings the head or the tail into view;
       rotateZ  leans the standing Book left or right;
       rotateY  swings the back edge toward you, which is what shows the side;
       translateZ stands it proud so perspective can do the rest.
     rotateX comes first because it tips about the BASE. Applied after the lean
     it would tip along the already-leaned axis and lift the bottom corner off
     the shelf. */
  transform:rotateX(var(--lib-book-top-tilt)) rotateZ(var(--lib-book-lean))
    rotateY(var(--lib-book-yaw)) translateZ(var(--lib-book-depth));
  transition:transform var(--d-turn) var(--e-out)}
/* Pulled and arrived: the pose unwinds over the SAME duration and curve as the
   turn, so the two read as one motion and the cover still commits dead flat. */
.lib-obj.is-pulled .lib-stand,.lib-obj.is-front .lib-stand{transform:none}
/* Perspective belongs to the ROW, so every Book on a shelf is seen from the
   same eye. It is safe at both ends of the turn precisely because of the
   compensating translateZ: the spine sits at z=0 at rest and the cover sits at
   z=0 when it arrives, so neither terminal state is magnified. Only the middle
   of the turn is projected, which is where depth is supposed to be visible. */
.lib-shelf-book .lib-row,.lib-shelf-personal .lib-row,
.lib-results .lib-row,
/* A Book on a MIXED shelf is the same object it is on the Books shelf, and
   without a perspective its three faces flatten into a rectangle. "Recently
   opened" holds both kinds, so it needs one; a shelf of nothing but flat cards
   is unaffected by it. */
.lib-shelf-res .lib-row{perspective:1400px}
.lib-vol{position:absolute;inset:0;transform-style:preserve-3d;
  transform-origin:left center;pointer-events:none;
  transition:transform var(--d-turn) var(--e-out)}
/* THE HIT RULE. Nothing inside the volume takes pointer input: every face is
   transformed, and a transformed element's hit box is its PROJECTED quad, which
   is not where the Book looks like it is. `.lib-obj` is the only target, it is
   never rotated, and it covers exactly the space the Book occupies -- so a
   neighbour is always clickable, with no close-first step. */
.lib-face{position:absolute;top:0;height:100%;pointer-events:none;
  backface-visibility:hidden}

/* The spine: the face you read on a shelf. Its bands and its rule are CHILDREN
   of it, so they rotate away with it rather than floating free (§9). */
.lib-spine{left:0;width:var(--bt);border-radius:2px 0 0 2px;overflow:hidden;
  display:flex;flex-direction:column;align-items:center;justify-content:space-between;
  padding:11px 0;
  background:
    repeating-linear-gradient(90deg,rgba(255,255,255,.030) 0 1px,transparent 1px 2px),
    repeating-linear-gradient(0deg,rgba(0,0,0,.055) 0 1px,transparent 1px 3px),
    linear-gradient(90deg,var(--cl-1) 0%,var(--cl-2) 34%,var(--cl-3) 74%,var(--cl-4) 100%);
  box-shadow:inset 1.5px 0 0 rgba(255,255,255,.14),inset -2px 0 5px rgba(0,0,0,.5)}
.lib-spine-band{flex:0 0 auto;width:100%;height:4px;
  background:linear-gradient(180deg,rgba(255,255,255,.17),rgba(0,0,0,.32));
  box-shadow:inset 0 1px 0 rgba(255,255,255,.22)}
.lib-spine-rule{flex:0 0 auto;width:66%;height:1px;
  background:linear-gradient(90deg,transparent,
    color-mix(in srgb,var(--accent-c,var(--a-peach)) 72%,#F6EBD2),transparent);
  opacity:.9;margin-bottom:9px}
.lib-spine-t{flex:1 1 auto;min-height:0;writing-mode:vertical-rl;transform:rotate(180deg);
  display:flex;align-items:center;justify-content:center;white-space:nowrap;
  overflow:hidden;text-overflow:ellipsis;
  font-size:11.5px;font-weight:500;letter-spacing:.06em;color:#F3EFFA;
  text-shadow:0 1px 2px rgba(0,0,0,.55)}

/* The page block. A real face at the far end of the box, finely lined, with the
   boards overhanging it slightly the way a hardcover's squares do. */
.lib-edge{left:0;width:var(--bt);transform:translateZ(calc(-1 * var(--bw)));
  overflow:hidden;background:linear-gradient(90deg,#332C42 0%,#241E31 55%,#171320 100%)}
/* THE PAGE GRAIN.
   The fore-edge and the head are two views of the SAME stack of paper, so they
   take the same rule. Pages stack from the front board to the back board —
   across the Book's THICKNESS — which is the element's width on both faces. So
   the grain repeats across the width and each line runs the other way: down the
   height on the fore-edge, and away from you on the head.

   The fore-edge used to run its lines the other way (`0deg`, horizontal lines
   stacked vertically). That is ruled paper seen face-on, not a page block seen
   edge-on, and it was wrong on the one face you see most.

   `--lib-page-grain` flips both together, so the direction can be settled by
   looking rather than by arguing about which way a book faces. */
.lib-leaves{position:absolute;inset:2px 2px;background:
  repeating-linear-gradient(var(--lib-page-grain),
    rgba(238,233,250,.17) 0 1px,rgba(18,15,26,.55) 1px 2px)}

/* THE HEAD — the top of the book, and the face the tilt exposes (§6).
   A real face of the same box: t across, 126 deep, hinged at the top edge and
   sweeping backwards. What you see on it is the head of the page block, which
   is what the top of a closed book actually is. */
/* Both sweep from the BACK edge forward, not from the front edge back.
   The direction decides which way the painted side ends up pointing, and the
   first version had both faces inside-out: the head's normal pointed DOWN and
   the tail's pointed UP, so each was culled from the only side you can look at
   it from. Measured — head normal (0, +1, 0) with `backface-visibility: hidden`,
   which is why tilting back showed a hollow box instead of paper.

   `translateZ(-126) rotateX(...)` rotates first and then pushes the plane back,
   so the sweep still spans the full depth and the normal comes out facing the
   eye: up for the head, down for the tail. */
.lib-head{top:0;left:0;width:var(--bt);height:var(--bw);
  transform:translateZ(calc(-1 * var(--bw))) rotateX(90deg);
  transform-origin:center top;overflow:hidden;
  background:linear-gradient(0deg,#3A3247 0%,#2A2337 60%,#1B1626 100%)}
/* THE TAIL — the bottom of the book, and what a NEGATIVE tilt exposes.
   Without it, tipping the Book forward showed the inside of an open box. Same
   construction as the head, hinged at the bottom edge and sweeping back, and it
   is darker because the shelf is right underneath it. */
.lib-tail{bottom:0;top:auto;left:0;width:var(--bt);height:var(--bw);
  transform:translateZ(calc(-1 * var(--bw))) rotateX(-90deg);
  transform-origin:center bottom;overflow:hidden;
  background:linear-gradient(180deg,#241E31 0%,#1B1626 60%,#120F1A 100%)}
/* Head and tail inherit the grain and only soften it — both are further from
   the eye than the fore-edge and catch less light. */
.lib-head .lib-leaves,.lib-tail .lib-leaves{inset:1px 2px;opacity:.8}
.lib-tail .lib-leaves{opacity:.55}

/* The two boards. The back one is plain cloth; the front one carries the cover. */
.lib-board,.lib-back{left:0;width:var(--bw);transform-origin:left center;overflow:hidden}
.lib-back{transform:rotateY(90deg);
  background:linear-gradient(120deg,var(--cl-2),var(--cl-4));
  box-shadow:inset 0 0 0 1px rgba(255,255,255,.05)}
.lib-board{transform:translateX(var(--bt)) rotateY(90deg)}

/* ---- THE MATERIALS (§5) ----------------------------------------------
   Muted Book cloths, deterministic per Book. These are materials, not brand
   colours -- Life OS purple stays the interface accent and is not among them. */
.lib-book{--cl-1:#191422;--cl-2:#272134;--cl-3:#1F1A2B;--cl-4:#120E1A}
.lib-book[data-material="plum"]{--cl-1:#241A2E;--cl-2:#3A2A46;--cl-3:#2C2038;--cl-4:#170F1E}
.lib-book[data-material="navy"]{--cl-1:#151C2E;--cl-2:#243149;--cl-3:#1B2439;--cl-4:#0D1220}
.lib-book[data-material="slate"]{--cl-1:#1B2028;--cl-2:#2C3540;--cl-3:#212932;--cl-4:#11151B}
.lib-book[data-material="moss"]{--cl-1:#17231D;--cl-2:#26382D;--cl-3:#1D2B23;--cl-4:#0E1712}
.lib-book[data-material="walnut"]{--cl-1:#241A15;--cl-2:#3A2A21;--cl-3:#2B1F19;--cl-4:#160F0B}
.lib-book[data-material="claret"]{--cl-1:#26161C;--cl-2:#3D232C;--cl-3:#2D1A21;--cl-4:#170D11}
.lib-book[data-material="graphite"]{--cl-1:#17171B;--cl-2:#282830;--cl-3:#1E1E25;--cl-4:#0F0F13}

/* ---- THE TURN (§7/§8/§10) ---------------------------------------------
   One rotation about the spine's outer edge -- the point a real book pivots on
   when you pull it out of a row. The compensating `translateZ(-t)` is what
   makes the arrival exact: at -90 degrees it puts the cover back in the screen
   plane, so its projected width there is precisely its layout width. No
   inflation, no shrink, nothing to settle. */
.lib-obj.is-pulled .lib-vol{
  transform:translateZ(calc(-1 * var(--bt))) rotateY(-90deg)}

/* THE COMMIT (§12/§13). Once round, the 3D is gone: the box transform is
   dropped, the depth faces stop painting, and the cover becomes an ordinary
   untransformed element with its title on the pixel grid. */
/* THE COMMIT IS A HANDOFF, NOT A MOVEMENT (S2.11).
   Both of these carry `transition: transform`, so dropping the 3D animated it:
   the turn played over 400ms, and 520ms later the commit UNWOUND the same
   rotation over another 400ms, landing on a state that looks identical. One
   click, two turns — the second of them the Book appearing to turn all over
   again. That is the replay that has been reported since S2.5.
   Animation illustrates the transition; the DOM owns the final state, and
   arriving at the final state is not itself a transition. */
.lib-obj.is-front .lib-vol{transform:none;transform-style:flat;transition:none}
.lib-obj.is-front .lib-spine,.lib-obj.is-front .lib-edge,
.lib-obj.is-front .lib-back,.lib-obj.is-front .lib-head,
.lib-obj.is-front .lib-tail{display:none}
.lib-obj.is-front .lib-board{transform:none;left:0;transition:none}

.lib-cover{position:absolute;inset:0;display:flex;flex-direction:column;
  align-items:center;justify-content:center;gap:2px;padding:15px 13px;
  border-radius:0 3px 3px 0;
  background:linear-gradient(150deg,var(--paper) 0%,var(--paper-2) 100%);
  box-shadow:inset 3px 0 0 0 var(--accent-c,var(--a-peach)),var(--obj-edge);
  transition:box-shadow var(--t-select)}
.lib-obj.is-pulled .lib-cover{box-shadow:inset 3px 0 0 0 var(--accent-c,var(--a-peach)),
  var(--obj-lift)}
.lib-cover-mark{font-family:'Playfair Display',Georgia,serif;font-size:8px;
  letter-spacing:.16em;color:var(--paper-ink-2);opacity:.72}
.lib-cover-pre{font-size:6.5px;font-weight:700;letter-spacing:.3em;text-transform:uppercase;
  color:var(--accent-c,var(--a-peach))}
.lib-cover-title{font-family:'Playfair Display',Georgia,serif;font-weight:500;
  font-size:15px;line-height:1.14;color:var(--paper-ink);text-align:center;margin-top:6px;
  display:-webkit-box;-webkit-line-clamp:4;line-clamp:4;-webkit-box-orient:vertical;
  overflow:hidden;overflow-wrap:anywhere}
.lib-cover-sub{font-family:'Playfair Display',Georgia,serif;font-style:italic;
  font-size:8.5px;line-height:1.3;color:var(--paper-ink-2);text-align:center;
  display:-webkit-box;-webkit-line-clamp:2;line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
.lib-cover-rule{width:34px;height:1px;background:var(--paper-ink-2);opacity:.4;margin:7px 0 5px}
.lib-cover-author{font-size:6.5px;font-weight:600;letter-spacing:.16em;text-transform:uppercase;
  color:var(--paper-ink-2);opacity:.8;text-align:center}

/* The Diary: lavender cloth and JOURNAL on the cover. Material and wording, not
   a badge and not a system button (§30). */
.lib-book-system{--cl-1:#251B33;--cl-2:#3C2C52;--cl-3:#2E2140;--cl-4:#170F22}
.lib-book-system .lib-spine{box-shadow:inset 2px 0 0 rgba(182,155,240,.6),
  inset -2px 0 5px rgba(0,0,0,.55)}
.lib-book-system .lib-cover{box-shadow:inset 3px 0 0 0 var(--a-lavender),var(--obj-edge)}
.lib-book-system.is-pulled .lib-cover{box-shadow:inset 3px 0 0 0 var(--a-lavender),
  var(--obj-lift)}

/* ---- THE OBJECT FOOTER (L3.2 S8, option C) ---------------------------
   L3.1 floated a bright purple pill over the cover, in the same corner region
   as the overflow menu. The review called it a debug badge. It was: a saturated
   UI chip sitting on top of the artwork, belonging to neither the object nor
   the page.

   The title, the subtitle and the Open action now share one footer BENEATH the
   object, at the object's own width, inside the shelf's label zone. Nothing
   covers the cover, the label is attached to what it names, and the second
   activation has a target that is part of the object.

   Absolutely positioned, so revealing it cannot shift the row. */
/* §18 -- no floating purple pill, and no large CTA. Beneath the arrived Book:
   its title, optionally its subtitle, and a quiet `Open`. The second click on
   the cover still opens, so this is a label that happens to be actionable
   rather than a button the interaction depends on. */
.lib-foot.is-quiet .lib-foot-a{margin-top:0}
.lib-foot{position:absolute;left:0;right:0;top:100%;margin-top:calc(var(--shelf-drop) - 16px);
  display:flex;flex-direction:column;gap:1px;align-items:flex-start;text-align:left;
  opacity:0;transform:translateY(-5px);pointer-events:none;
  transition:opacity var(--t-hover),transform var(--t-select)}
.lib-foot-t{font-size:12px;font-weight:600;color:var(--text);line-height:1.25;
  display:-webkit-box;-webkit-line-clamp:2;line-clamp:2;-webkit-box-orient:vertical;
  overflow:hidden}
.lib-foot-s{font-size:10.5px;color:var(--muted);line-height:1.3;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%}
/* The action reads as text with an arrow, not as a button sitting on a shelf.
   Quiet by design: it is the only thing here that can be pressed, so it does
   not also have to shout. */
.lib-foot-a{display:inline-flex;align-items:center;gap:4px;margin-top:5px;
  font-size:11.5px;font-weight:600;color:var(--accent);
  letter-spacing:.01em;cursor:pointer}
.lib-obj.is-pulled .lib-foot-a:hover{text-decoration:underline}

.lib-obj-flag{position:absolute;left:calc(var(--spine-w,0px) + 7px);top:7px;
  font-size:8px;font-weight:700;letter-spacing:.07em;text-transform:uppercase;
  color:var(--muted);background:rgba(0,0,0,.5);border-radius:4px;padding:2px 5px}
/* The overflow menu lives at the TOP of the object; the Open action lives in
   the footer at the bottom (L3.2 S9). Opposite ends, so they cannot compete for
   the same press, and the menu can never cover the title. */
.lib-obj-more{position:absolute;right:4px;top:5px;width:24px;height:24px;
  display:grid;place-items:center;border:0;border-radius:6px;cursor:pointer;
  background:rgba(10,8,16,.62);color:var(--text-2);opacity:0;z-index:2;
  transition:opacity var(--t-hover),background var(--t-hover),color var(--t-hover)}
/* §19 -- management is secondary and it never paints onto a resting object.
   A Book shows it only once it is FRONT-FACING: a 24px menu cannot sit on a
   29px spine without becoming the spine, and a control that appears mid-turn is
   a control you will hit by accident. Flat objects, which have a face at rest,
   keep the hover/focus reveal. */
.lib-res:hover .lib-obj-more,.lib-res.is-pulled .lib-obj-more,
.lib-res:focus-within .lib-obj-more,
.lib-book.is-front .lib-obj-more,
.lib-book.is-front:focus-within .lib-obj-more{opacity:1}
.lib-book .lib-obj-more{pointer-events:none}
.lib-book.is-front .lib-obj-more{pointer-events:auto}
/* Clear of the Open action beneath, and clear of the turn. */
.lib-book.is-front .lib-obj-more{right:5px;top:6px}
.lib-obj-more:hover{background:rgba(10,8,16,.85);color:var(--text)}
.lib-obj-more:focus-visible{opacity:1;outline:2px solid var(--accent);outline-offset:1px}

/* ── The non-Book family (§10) ────────────────────────────────────────
   Same shelf, same states, same type scale — a different object. A folio is
   not a book with the spine removed; it is shorter, wider and made of paper
   that lies flat. */
/* ---- THE DOCUMENT AS A STORED FOLIO (L3.2 S15) -----------------------
   The review is right that it still read as a UI card: an 18px-ish radius, a
   soft ambient shadow and a glyph on a tinted panel is the visual language of
   a dashboard tile, whatever is drawn inside it.

   What it is now, and every part earns its place:

     - FLATTER than a book, and landscape, because a folio lies down;
     - a 2px radius, not a card radius. Paper corners are square-ish;
     - a visible SHEET EDGE down the right, the folio's own page block, which
       is the folio equivalent of a book's fore-edge;
     - a small accent TAB at the top left, the way a filed folio is tabbed;
     - paper stock, not surface stock -- the same `--paper` the Book cover and
       the Diary page are made of;
     - the same edge shadow, the same contact shadow, the same baseline.

   It carries its own title, so it needs no detached resting label (S19). */
/* ══ THE FLAT FAMILIES (L3.4 §22/§28/§29) ══════════════════════════════
   Chosen from the component lab. Each is a different physical object, not the
   same rectangle in three tints, and none of them borrows the Book's spine or
   the Book's turn. A folder does not turn round: they come forward. */
.lib-res{--rw:178px;display:block;width:var(--rw);position:relative;
  margin-bottom:calc(var(--shelf-contact) + var(--lib-flat-lift))}

/* DOCUMENT -- a file folio, half open. The sheets standing behind the lid are
   what make it a folio rather than a card, and they are why it needs no spine. */
.lib-folio{position:relative;display:block;width:100%;height:132px}
.lib-folio-sheet{position:absolute;left:9px;right:13px;top:-7px;bottom:26px;border-radius:1px;
  background:linear-gradient(180deg,var(--paper) 0%,var(--paper-2) 100%);
  box-shadow:0 -1px 0 rgba(0,0,0,.3),inset 0 0 0 1px rgba(0,0,0,.06)}
.lib-folio-sheet2{left:14px;right:8px;top:-12px;opacity:.72}
.lib-folio-lid{position:absolute;left:0;right:0;bottom:0;height:104px;
  display:flex;flex-direction:column;gap:5px;padding:13px 15px;
  border-radius:2px 2px 2px 6px;
  background:linear-gradient(158deg,#2E2740 0%,#211B2E 62%,#1A1526 100%);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.11),var(--obj-edge);
  transition:box-shadow var(--t-select)}
.lib-obj.is-pulled .lib-folio-lid{box-shadow:inset 0 1px 0 rgba(255,255,255,.11),
  var(--obj-lift)}

/* LINK -- a clipping card. Source mark, title, domain. Never a browser tab. */
.lib-clip{position:relative;display:flex;align-items:center;gap:11px;
  width:100%;height:76px;padding:12px 14px;border-radius:2px;
  background:linear-gradient(158deg,#262031 0%,#1A1524 100%);
  box-shadow:var(--obj-edge);transition:box-shadow var(--t-select)}
.lib-obj.is-pulled .lib-clip{box-shadow:var(--obj-lift)}
.lib-clip-mark{flex:0 0 auto;width:28px;height:28px;border-radius:3px;display:grid;
  place-items:center;font-size:13px;font-weight:700;color:#1A1624;
  background:var(--a-sage)}
.lib-clip-body{min-width:0;display:flex;flex-direction:column;gap:3px}

/* FILE -- a jacket. The clipped lower corner is the whole difference between a
   jacket and a card. */
.lib-jacket{position:relative;display:block;width:100%;height:76px}
.lib-jacket-body{position:absolute;inset:0;display:flex;flex-direction:column;gap:3px;
  padding:12px 14px;border-radius:2px;
  background:linear-gradient(158deg,#2B2437 0%,#1C1727 100%);
  box-shadow:var(--obj-edge);transition:box-shadow var(--t-select);
  clip-path:polygon(0 0,100% 0,100% 100%,12px 100%,0 calc(100% - 12px))}
.lib-obj.is-pulled .lib-jacket-body{box-shadow:var(--obj-lift)}
.lib-jacket-corner{position:absolute;left:0;bottom:0;width:12px;height:12px;
  background:linear-gradient(45deg,rgba(255,255,255,.08) 50%,transparent 50%)}

.lib-res-kind{font-size:8.5px;font-weight:700;letter-spacing:.13em;
  text-transform:uppercase;color:var(--accent-c,var(--a-blue))}
.lib-res-name{font-size:12.5px;font-weight:600;line-height:1.28;color:#EFEAF8;
  display:-webkit-box;-webkit-line-clamp:2;line-clamp:2;-webkit-box-orient:vertical;
  overflow:hidden;overflow-wrap:anywhere}
.lib-res-excerpt{font-size:9.5px;line-height:1.4;color:rgba(214,203,240,.62);
  display:-webkit-box;-webkit-line-clamp:2;line-clamp:2;
  -webkit-box-orient:vertical;overflow:hidden}
.lib-res-domain{margin-top:auto;font-size:10px;font-weight:600;color:var(--muted)}
.lib-res-link .lib-res-name{-webkit-line-clamp:2;line-clamp:2}
.lib-res-file .lib-res-kind{color:var(--accent-c,var(--a-peach))}

/* Images and video: a FRAME with a drawn floor. The floor is the default and
   the picture sits on top, so a dead URL leaves a frame rather than a hole
   (§28). `--ratio` is only set when the row actually knows the dimensions. */
/* Images and video: the same physical rules, a different object. A frame with
   a drawn floor, the same edge shadow, the same height as a folio so the whole
   Media shelf sits on one line. */
.lib-frame{position:relative;display:block;width:100%;height:118px;
  border-radius:2px;overflow:hidden;
  background:linear-gradient(158deg,var(--surface-2) 0%,var(--surface) 100%);
  box-shadow:var(--obj-edge);transition:box-shadow var(--t-select)}
.lib-obj.is-pulled .lib-frame{box-shadow:var(--obj-lift)}
/* §26 -- the content dominates. The caption is plain text beneath the picture,
   always visible, with no bar behind it; only the Open action is revealed. */
.lib-cap{display:block;margin-top:8px;min-width:0}
.lib-cap-t{display:block;font-size:11.5px;font-weight:600;color:var(--text-2);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.lib-cap-m{display:block;margin-top:2px;font-size:9.5px;color:var(--muted)}
.lib-foot.is-quiet{gap:0}
.lib-foot.is-quiet .lib-foot-a{margin-top:0}
.lib-frame-fallback{position:absolute;inset:0;display:grid;place-items:center;
  color:var(--muted);opacity:.55}
.lib-frame-img{position:relative;width:100%;height:100%;object-fit:cover;display:block}
.lib-frame-badge{position:absolute;right:6px;bottom:6px;font-size:9.5px;font-weight:700;
  color:#fff;background:rgba(0,0,0,.66);border-radius:3px;padding:1px 5px;
  font-variant-numeric:tabular-nums}
.lib-frame-play{position:absolute;left:6px;bottom:6px;width:20px;height:20px;
  display:grid;place-items:center;border-radius:999px;background:rgba(0,0,0,.6);color:#fff}

/* Every resource pulls forward with the SAME grammar as a Book -- travel, no
   scale, and the footer. Nothing on the shelf is inert, and nothing gets a
   different physics. */
.lib-res.is-pulled{transform:translateY(calc(-1 * var(--lib-book-pull)))}

/* The search result surface (§13). One wrapped set, not four rails: a result
   is an answer, and an answer should be visible rather than somewhere to
   scroll to. Objects keep their own type's visual, so a Book still arrives as
   a cover. */
.lib-results{margin:0 0 26px}
.lib-results .lib-shelf-h{margin:0 0 14px}
.lib-found{display:flex;flex-wrap:wrap;align-items:flex-end;gap:20px 18px;
  padding:8px 4px 26px;
  background-image:linear-gradient(to bottom,transparent calc(100% - 22px),
    rgba(255,255,255,.045) calc(100% - 22px),rgba(255,255,255,.045) calc(100% - 21px),
    transparent calc(100% - 21px))}

/* The personal ledge (§30 B). Shorter than a Books shelf and set apart by a
   hairline above it, so a system-owned Book is visibly on its own shelf rather
   than first in somebody's collection. */
/* The personal ledge keeps its own HEADING treatment and nothing else.
   It used to set its own rail padding, and because the ledge line is drawn from
   the rail's bottom edge, that put the Diary on a different plane from every
   other object: measured contact gap Diary -6px against Book +36px, a 42px
   discrepancy that read exactly as the review described it -- objects floating
   at arbitrary heights. One plane, one padding, no exceptions (L3.2 S13). */
.lib-shelf-personal .lib-row{justify-content:flex-start}
.lib-shelf-personal .lib-shelf-note{font-style:italic}

/* An empty Library keeps ONE faint ledge, so the room is still a room without
   showing you the size of the gap (§25). */
/* The empty-Library placeholder keeps the shared plane. It carries no objects,
   so its padding could not misalign anything -- but a shelf that opts out of
   the formula is a shelf someone later puts an object on. */
.lib-shelf-blank .lib-rail{overflow:hidden}
.lib-blank{height:96px;opacity:.5}

/* `.lib-shelf-cap` is gone. It was a persistent label at the far left of every
   shelf naming whichever object the scroll happened to be near -- detached
   from the object, and describing a state (prominence) that no longer exists.
   The title now lives in the object's own footer: see `.lib-foot`. */

/* Shelf-shaped waiting (§32). Book-shaped, on a ledge — the shape that is
   coming, not a grey slab. */
.lib-skel-shelf{display:flex;column-gap:18px;padding:18px 4px 30px;overflow:hidden}
.lib-skel-book{flex:0 0 auto;width:141px;height:181px;border-radius:2px 5px 5px 2px}
.lib-skel-res{flex:0 0 auto;width:172px;height:132px;border-radius:9px}

/* Reduced motion (§31): no spatial movement anywhere. The states stay
   distinguishable because none of them was ONLY movement — prominence still
   brightens its shadow, focus still outlines, hover still lifts its surface
   colour. What goes is the travel. */
@media (prefers-reduced-motion: reduce){
  /* Travel goes; information stays. Every state is still distinguishable
     because none of them was ever ONLY movement -- pulled forward keeps its
     shadow, its Open control and its label; hover keeps its shadow; focus
     keeps its outline; the return glow simply does not play (S25). */
  .lib-obj,.lib-obj:hover,.lib-obj.is-pulled,.lib-res.is-pulled{transform:none}
  .lib-obj::after,.lib-obj:hover::after,.lib-obj.is-pulled::after{transform:none}
  .lib-obj.is-pulled .lib-foot{transform:none}
  /* The turn is not shortened, it is skipped: `scheduleCommit` commits
     synchronously under reduced motion, so the Book arrives front-facing
     without ever rotating. Every state semantic is identical. */
  .lib-vol{transition:none}
  .lib-obj.is-pulled .lib-cover{box-shadow:0 0 0 2px var(--accent-c,var(--a-peach)),
    0 26px 40px -16px rgba(0,0,0,.72)}
  .lib-rail{scroll-behavior:auto}
}

/* Page matches — the half of a Library search that lives inside a book. */
.lib-hits{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:6px}
.lib-hit{display:flex;flex-direction:column;gap:4px;width:100%;text-align:left;
  background:var(--surface);border:1px solid transparent;border-radius:12px;padding:11px 14px;
  cursor:pointer;font-family:inherit;
  transition:background var(--t-hover),border-color var(--t-hover),transform var(--t-hover)}
.lib-hit:hover{background:var(--surface-2);border-color:var(--border);transform:translateY(-1px)}
.lib-hit:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
.lib-hit-where{font-size:10.5px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;
  color:var(--muted)}
.lib-hit-text{font-size:13px;color:var(--text-2);line-height:1.55;
  display:-webkit-box;-webkit-line-clamp:2;line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}

.lib-empty{max-width:560px;margin:40px auto}
.lib-empty-acts{display:flex;gap:10px;flex-wrap:wrap;justify-content:center;margin-top:20px}

/* Menus — the app's surface language, not a native context menu. */
.lib-menu{background:var(--surface-2);border:1px solid var(--border);border-radius:12px;
  box-shadow:var(--e3);padding:6px;min-width:230px;display:flex;flex-direction:column;gap:2px}
.lib-menu button{display:flex;align-items:center;gap:11px;background:none;border:0;
  border-radius:9px;padding:9px 11px;text-align:left;color:var(--text);font-size:13px;
  cursor:pointer;font-family:inherit;width:100%;transition:background var(--t-hover)}
.lib-menu button:hover{background:var(--surface-3)}
.lib-menu button:focus-visible{outline:2px solid var(--accent);outline-offset:-2px}
.lib-menu-i{color:var(--text-2);display:flex}
.lib-menu b{display:block;font-weight:600}
.lib-menu small{display:block;font-size:11.5px;color:var(--muted);margin-top:1px}
.lib-menu-sm{min-width:172px}

/* One item — Documents, Links and the record of an upload that does not
   have its bytes yet. Restrained on purpose: there is no viewer to build
   until upload storage exists, and a fake one would be worse than none. */
/* ---- The open view (L3.1 S19) ----------------------------------------
   Two columns: the OBJECT, drawn at the size it deserves, and what is known
   about it. The old version was a 720px card in the corner of an empty page,
   which is what "feels too empty" meant -- not too little content, too little
   composition. Nothing here invents functionality; the note at the bottom says
   plainly what the object currently stores. */
.lib-open{display:grid;grid-template-columns:auto minmax(0,1fr);gap:34px;
  align-items:start;max-width:860px}
.lib-open-object{padding:8px 4px 26px;position:relative}
/* The object is a specimen here, not a control: it is the same component the
   shelf draws, at rest, with nothing to press. */
.lib-open-object .lib-obj{cursor:default;pointer-events:none}
.lib-open-object .lib-obj-more,.lib-open-object .lib-foot{display:none}
.lib-open-object .lib-book{--bw:150px}
.lib-open-object .lib-res{--rw:200px}
.lib-open-body{display:flex;flex-direction:column;gap:16px;min-width:0}
.lib-open-kind{display:flex;align-items:center;gap:9px;font-size:11px;font-weight:700;
  letter-spacing:.08em;text-transform:uppercase;color:var(--muted)}
.lib-open-kind-i{color:var(--text-2);display:flex}
.lib-open-desc{margin:0;font-size:15.5px;line-height:1.62;color:var(--text-2)}
.lib-open-desc.is-empty{color:var(--muted);font-style:italic}
.lib-open-url{margin:0;font-size:13px;word-break:break-all}
.lib-open-url a{color:var(--accent)}
.lib-open-facts{margin:0;display:grid;gap:12px 26px;
  grid-template-columns:repeat(auto-fit,minmax(150px,1fr));
  padding:16px 18px;background:var(--surface);border-radius:14px;
  border:1px solid var(--border)}
.lib-open-facts div{display:flex;flex-direction:column;gap:2px;min-width:0}
.lib-open-facts dt{font-size:10.5px;font-weight:700;letter-spacing:.07em;
  text-transform:uppercase;color:var(--muted)}
.lib-open-facts dd{margin:0;font-size:13px;color:var(--text);
  overflow:hidden;text-overflow:ellipsis}
.lib-open-note{font-size:13px;line-height:1.6;color:var(--text-2);
  padding:14px 16px;border-radius:12px;background:var(--surface);
  border-left:3px solid var(--a-blue)}
.lib-open-note b{display:block;color:var(--text);margin-bottom:3px;font-weight:600}

.lib-item{max-width:720px;background:var(--surface);border-radius:18px;padding:24px 26px;
  box-shadow:var(--e2);display:flex;flex-direction:column;gap:14px}
.lib-item-head{display:flex;align-items:center;gap:10px}
.lib-item-icon{color:var(--text-2);display:flex}
.lib-item-type{font-size:11px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;
  color:var(--muted)}
.lib-item-desc{margin:0;font-size:14.5px;line-height:1.65;color:var(--text-2)}
.lib-item-desc.is-empty{color:var(--muted);font-style:italic}
.lib-item-url{margin:0;font-size:13px;word-break:break-all}
.lib-item-url a{color:var(--accent);text-decoration:underline;text-underline-offset:2px}
.lib-item-facts{display:flex;flex-wrap:wrap;gap:26px;margin:4px 0 0;
  padding-top:14px;border-top:1px solid var(--border)}
.lib-item-facts dt{font-size:10.5px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;
  color:var(--muted);margin:0 0 3px}
.lib-item-facts dd{margin:0;font-size:13px;color:var(--text-2)}
.lib-item-note{margin:0;font-size:12.5px;color:var(--muted);line-height:1.6;
  background:var(--surface-2);border-radius:10px;padding:11px 13px}

/* The create/rename form. */
.modal-libform .lf-title{font-size:17px;font-weight:600;color:var(--text);margin:0}
.modal-libform .lf-body{display:flex;flex-direction:column;gap:14px}
.lf-hint{margin:0;font-size:12.5px;color:var(--muted);line-height:1.55}
.lf-area{resize:vertical;min-height:64px;font-family:inherit;line-height:1.55}
.lf-err{margin:0;font-size:12.5px;color:var(--danger)}

/* ── The Book ─────────────────────────────────────────────────────────── */
.bk{display:flex;flex-direction:column;gap:14px;align-items:center;min-width:0}
.bk-stage{display:flex;align-items:center;justify-content:center;gap:12px;width:100%}

/* Book and Project Rail. Two columns that are two RENDER OWNERS — the rail
   repaints when a task changes and the book does not, which is what keeps a
   cursor alive while the panel updates beside it (§33). */
.bk-layout{display:grid;grid-template-columns:minmax(0,1fr);gap:20px;align-items:start}
.bk-layout.has-rail{grid-template-columns:minmax(0,1fr) 300px}

.bk-rail{position:sticky;top:12px;background:var(--surface-2,rgba(255,255,255,.03));
  border:1px solid var(--line,rgba(255,255,255,.08));border-radius:16px;padding:16px;
  max-height:calc(100vh - 140px);overflow:auto;scrollbar-width:thin}
.bk-rail-eyebrow{display:block;font-size:11px;letter-spacing:.08em;text-transform:uppercase;
  opacity:.6;margin-bottom:4px}
.bk-rail-title{display:block;width:100%;text-align:left;background:none;border:0;padding:0;
  color:inherit;font:inherit;font-weight:650;font-size:16px;cursor:pointer;line-height:1.3}
.bk-rail-title:hover{text-decoration:underline}
.bk-rail-meta{display:flex;flex-wrap:wrap;gap:6px;margin-top:8px}
.bk-rail-prog{display:flex;flex-direction:column;gap:6px;margin-top:10px;font-size:12px;opacity:.8}
.bk-rail-next{margin-top:14px;padding:10px 12px;border-radius:10px;
  background:var(--surface-3,rgba(255,255,255,.04))}
.bk-rail-lbl{display:block;font-size:11px;letter-spacing:.06em;text-transform:uppercase;
  opacity:.55;margin-bottom:6px}
.bk-rail-nt{font-size:13px;line-height:1.4}
.bk-rail-tasks{margin-top:16px;display:flex;flex-direction:column;gap:6px}
.bk-rail-empty,.bk-rail-hint{font-size:12px;opacity:.6;line-height:1.45;margin:8px 0 0}

/* A task in the rail. `grab` is a promise the drop targets keep. */
.bk-rt{display:grid;grid-template-columns:auto minmax(0,1fr);gap:8px;align-items:start;
  padding:8px 10px;border-radius:10px;cursor:grab;
  background:var(--surface-3,rgba(255,255,255,.03));border:1px solid transparent}
.bk-rt:hover{border-color:var(--line,rgba(255,255,255,.12))}
.bk-rt.is-dragging{opacity:.5;cursor:grabbing}
.bk-rt-tick{width:14px;height:14px;border-radius:5px;margin-top:2px;
  border:1.5px solid var(--line,rgba(255,255,255,.28))}
.bk-rt.is-done .bk-rt-tick{background:var(--a-sage,#7aa88a);border-color:transparent}
.bk-rt.is-done .bk-rt-t{text-decoration:line-through;opacity:.55}
.bk-rt-t{font-size:13px;line-height:1.35}
.bk-rt-m{grid-column:2;display:flex;flex-wrap:wrap;gap:6px;font-size:11px;opacity:.65}
.bk-rt-link{opacity:.85}

/* ── Page layouts ─────────────────────────────────────────────────────
   Eleven templates, one page model. Most differ only in their ruling and
   their starter blocks, which is why moving between them can never lose a
   block — see the layout route. */
/* `.bk-page-body{display:flex}` is declared LATER in this stylesheet at equal
   specificity, so a single-class rule here loses and the regions become flex
   items with `min-width:0` and no grow — which measured 0px wide and made the
   two-column, four-section and comparison pages impossible to type into. Two
   classes, so the grid wins wherever this block happens to sit. */
.bk-page-body.bk-regions{display:grid;flex:1;min-height:0;column-gap:26px;row-gap:0}
.bk-regions-2{grid-template-columns:repeat(2,minmax(0,1fr))}
.bk-regions-4{grid-template-columns:repeat(2,minmax(0,1fr));grid-template-rows:repeat(2,minmax(0,1fr))}
/* A RULE between columns, not a box around each. A dashed outline per region
   reads as a wireframe; a printed page divides its columns with a hairline and
   lets the writing sit on the same ruling it would use anywhere else. */
/* The divider is deliberately STRONGER than `--paper-line`. A horizontal
   hairline the same colour as the ruling is invisible by construction — the
   page is already full of horizontal lines that exact colour, which is why
   the four-section split read as two columns and no rows. The vertical one
   looked fine only because nothing competes with it. Same weight both ways
   now, so the quarters read as quarters. */
.bk-regions{--bk-divider:color-mix(in srgb,var(--paper-ink) 22%,transparent)}
.bk-regions .bk-region + .bk-region{border-left:1px solid var(--bk-divider);
  padding-left:26px;margin-left:-13px}
.bk-regions-4 .bk-region:nth-child(3),.bk-regions-4 .bk-region:nth-child(4){
  border-top:1px solid var(--bk-divider);padding-top:15px;margin-top:15px}
.bk-regions-4 .bk-region:nth-child(3){border-left:0;padding-left:0;margin-left:0}
/* `display:flex` on the region rather than a second `.bk-editor` rule: the
   30px ruling and the whole-rows height are stated ONCE, on .bk-editor below,
   and a rule here that also matched `.bk-editor{` would shadow them. */
.bk-region{min-width:0;min-height:0;display:flex;flex-direction:column}
/* A blank page is blank. The editor draws its ruling from `--paper-line`, so
   that is the variable to clear — `--bk-rule` was a name I invented and
   nothing read it, which is why Blank still arrived ruled.
   Set on the PAGE and inherited, rather than on `.bk-editor`: the whole page is
   unruled, and a selector containing the exact text `.bk-editor{` earlier in
   this file shadows the real one for anything reading the sheet by substring. */
.bk-l-blank{--paper-line:transparent}

/* ── On-paper components ──────────────────────────────────────────────
   Everything below sits on the PAGE, not on the app shell, so it is drawn
   from the paper token family (`--paper`, `--paper-ink`, `--paper-line`).
   Shell tokens and hard-coded rgba() do not know which theme the paper is:
   the first version used `rgba(0,0,0,.28)` and `#22201f`, which on the dark
   paper (`#262233`) was black on near-black — visible in neither. */

/* Checklist items. The box is not editable, the words are, and the row keeps
   the 30px writing rhythm so a checklist still sits on its ruling. */
.bk-check{display:grid;grid-template-columns:auto minmax(0,1fr);gap:10px;
  align-items:start;margin:0;line-height:30px}
.bk-check-box{width:16px;height:16px;margin-top:7px;border-radius:5px;cursor:pointer;
  border:1.5px solid var(--paper-ink-2);flex:none;
  transition:background var(--t-hover),border-color var(--t-hover)}
.bk-check-box:hover{border-color:var(--accent-c,var(--a-peach))}
.bk-check-t{min-width:0}
.bk-check.is-on .bk-check-box{background:var(--accent-c,var(--a-peach));
  border-color:transparent}
.bk-check.is-on .bk-check-t{text-decoration:line-through;color:var(--paper-ink-2)}

/* ── References ───────────────────────────────────────────────────────
   A card showing a LIVE entity. It stores an id; everything it displays is
   read from the canonical row, so ticking a task in Projects changes what
   this says. */
.bk-ref{display:grid;grid-template-columns:auto minmax(0,1fr) auto;gap:8px;
  align-items:center;margin:0;height:60px;padding:0 10px;border-radius:10px;cursor:pointer;
  color:var(--paper-ink);
  background:color-mix(in srgb,var(--paper-ink) 5%,transparent);
  border:1px solid color-mix(in srgb,var(--paper-ink) 14%,transparent)}
.bk-ref:hover{border-color:var(--accent-c,var(--a-peach))}
.bk-ref-k{font-size:10px;letter-spacing:.07em;text-transform:uppercase;
  color:var(--paper-ink-2);line-height:1.2}
.bk-ref-t{font-size:13px;line-height:1.3;min-width:0;overflow:hidden;
  text-overflow:ellipsis;white-space:nowrap}
.bk-ref-m{grid-column:2;font-size:11px;color:var(--paper-ink-2);line-height:1.2}
.bk-ref.is-done .bk-ref-t{text-decoration:line-through;color:var(--paper-ink-2)}
.bk-ref-gone{font-style:italic;color:var(--paper-ink-2)}
.bk-ref-x{background:none;border:0;cursor:pointer;color:var(--paper-ink-2);
  font-size:17px;line-height:1;padding:2px 4px;border-radius:6px}
.bk-ref-x:hover{color:var(--paper-ink);background:color-mix(in srgb,var(--paper-ink) 10%,transparent)}
/* Where a drop would land. */
.bk-editor.is-drop{outline:2px dashed var(--accent-c,var(--a-peach));outline-offset:3px}

/* A cited block, after following a backlink. Long enough to find, short
   enough not to become decoration. */
.is-cited{animation:bkCite 2.4s ease-out}
@keyframes bkCite{
  0%,60%{background:color-mix(in srgb,var(--a-gold,#d9b464) 30%,transparent)}
  100%{background:transparent}}
@media (prefers-reduced-motion:reduce){.is-cited{animation:none;
  background:color-mix(in srgb,var(--a-gold,#d9b464) 22%,transparent)}}

/* ── Bookmarks ────────────────────────────────────────────────────────
   Shortcuts, never structure. They cannot move a page. */
.bk-marks{display:flex;flex-wrap:wrap;gap:6px;width:100%;max-width:1320px}
.bk-mark{font-size:12px;padding:4px 10px;border-radius:999px;cursor:pointer;color:inherit;
  background:var(--surface-3,rgba(255,255,255,.04));
  border:1px solid var(--line,rgba(255,255,255,.10))}
.bk-mark:hover{border-color:var(--accent-c,var(--a-gold))}

/* ── Pinboard ─────────────────────────────────────────────────────────
   One page across the whole spread. Positions are percentages, so a board
   arranged on a wide screen is the same board on a narrow one. */
/* Three classes, not two. `.bk-book.bk-spread` sets two columns and is declared
   LATER in this stylesheet at equal specificity, so a two-class selector here
   loses and the board draws at half width with an empty column beside it —
   which is precisely what a spread-spanning page must not do. */
.bk-book.bk-spread.bk-spread-full{grid-template-columns:minmax(0,1fr)}
.bk-page-full{padding:22px 26px 18px}
.bk-page-full::before{display:none}
.bk-board{position:relative;flex:1;min-height:420px;border-radius:10px;
  background-image:radial-gradient(circle,var(--paper-line) 1.5px,transparent 1.5px);
  background-size:24px 24px}
.bk-board.is-drop{outline:2px dashed var(--accent-c,var(--a-peach));outline-offset:2px}
.bk-board-empty{position:absolute;inset:0;display:flex;align-items:center;
  justify-content:center;text-align:center;padding:0 18%;color:var(--paper-ink-2);
  font-size:13px;line-height:1.5;margin:0}

/* A pin is a card ON the paper — drawn from the paper ink, so it is legible on
   whichever paper the theme gives it. The first version pinned near-black text
   to a near-black card and the pin, its words and its delete button were all
   invisible, which is why a pin could not be removed once made. */
.bk-pin{position:absolute;display:flex;flex-direction:column;gap:3px;
  padding:18px 26px 10px 12px;border-radius:10px;font-size:12px;
  color:var(--paper-ink);
  background:color-mix(in srgb,var(--paper-ink) 8%,var(--paper));
  border:1px solid color-mix(in srgb,var(--paper-ink) 16%,transparent);
  box-shadow:0 6px 18px rgba(0,0,0,.22)}
.bk-pin:hover{border-color:color-mix(in srgb,var(--paper-ink) 30%,transparent)}
.bk-pin.is-dragging{box-shadow:0 14px 30px rgba(0,0,0,.34);z-index:5}
/* The grab strip. It stops short of the delete button so the two never fight
   over the same pointer. */
.bk-pin-grip{position:absolute;top:0;left:0;right:26px;height:16px;cursor:grab;
  border-radius:10px 0 0 0}
.bk-pin-grip::before{content:'';position:absolute;left:10px;top:6px;width:18px;height:3px;
  border-radius:2px;background:var(--paper-ink-2);opacity:.5}
.bk-pin:hover .bk-pin-grip::before{opacity:.9}
.bk-pin-grip:active{cursor:grabbing}
.bk-pin-k{font-size:9px;letter-spacing:.08em;text-transform:uppercase;
  color:var(--paper-ink-2)}
.bk-pin-t{line-height:1.4;word-break:break-word;min-height:18px;outline:0}
.bk-pin-t:empty::before{content:attr(data-placeholder);color:var(--paper-ink-2)}
.bk-pin-t.is-done{text-decoration:line-through;color:var(--paper-ink-2)}
.bk-pin-m{font-size:10px;color:var(--paper-ink-2)}
.bk-pin-go{font-size:10.5px;color:var(--accent-c,var(--a-peach));text-decoration:underline;
  text-underline-offset:2px;align-self:flex-start}
.bk-pin-img{max-width:100%;border-radius:6px;display:block}
/* Always visible, never a hover secret — a control you cannot find is a
   control you do not have. */
.bk-pin-x{position:absolute;top:3px;right:3px;width:20px;height:20px;background:none;
  border:0;cursor:pointer;color:var(--paper-ink-2);font-size:16px;line-height:1;
  border-radius:6px;display:flex;align-items:center;justify-content:center}
.bk-pin-x:hover{color:var(--paper-ink);
  background:color-mix(in srgb,var(--paper-ink) 14%,transparent)}
.bk-pin-tools{display:flex;gap:6px;align-items:center}
/* Delete sits apart from the reversible actions and only colours on hover —
   destructive, but not shouting at someone who came to read. */
.lib-del{color:var(--muted)}
/* Separated from the reversible actions above it. The ellipsis is the promise
   that something will ask before anything is destroyed. */
.lib-menu-del{margin-top:4px;padding-top:6px;border-top:1px solid var(--hairline);
  color:var(--muted)}
.lib-menu-del:hover{color:var(--danger)}

/* -- Event composer and confirmation ------------------------------------
   The form never writes. It produces a proposal; the confirmation executes it.
   So the two surfaces are visually related but not identical: filling in is
   busy and editable, confirming is quiet and read-only. */
.ev-modal{width:min(520px,calc(100vw - 32px))}
.ev-body{display:flex;flex-direction:column;gap:11px;max-height:min(66vh,560px);overflow:auto}
.ev-f{display:flex;flex-direction:column;gap:4px;font-size:12px;color:var(--text-2)}
.ev-f > span{display:flex;gap:6px;align-items:baseline}
.ev-f i{font-style:normal;font-size:10.5px;color:var(--muted)}
.ev-f input,.ev-f select,.ev-f textarea{height:36px;padding:0 10px;border-radius:8px;
  font:inherit;font-size:13px;color:var(--text);background:var(--surface-2);
  border:1px solid var(--hairline)}
.ev-f textarea{height:auto;padding:8px 10px;resize:vertical}
.ev-f input:focus,.ev-f select:focus,.ev-f textarea:focus{outline:0;border-color:var(--accent)}
.ev-row{display:grid;grid-template-columns:1.2fr 1fr 1fr;gap:8px}
.ev-check{display:flex;align-items:center;gap:8px;font-size:12.5px;color:var(--text-2)}
.ev-sub,.ev-zone{margin:0;font-size:11px;color:var(--muted);line-height:1.45}
.ev-more summary{font-size:12px;color:var(--text-2);cursor:pointer;padding:4px 0}
.ev-more[open] summary{margin-bottom:6px}
.ev-more{display:flex;flex-direction:column;gap:9px}
.ev-foot{display:flex;justify-content:flex-end;gap:8px}

/* -- Composer, arranged by decision rather than by API field ------------
   What -> When -> Where -> Details, everything else behind More. A form that
   shows nine fields at once makes the two-field case feel like work. */
.ev-what input{width:100%;height:44px;padding:0 12px;border-radius:10px;
  font:inherit;font-size:17px;font-weight:600;color:var(--text);
  background:var(--surface-2);border:1px solid var(--hairline)}
.ev-what input::placeholder{font-weight:500;color:var(--muted)}
.ev-what input:focus{outline:0;border-color:var(--accent)}

.ev-group{display:flex;flex-direction:column;gap:8px;padding:11px 12px;border-radius:11px;
  background:color-mix(in srgb,var(--text) 3%,transparent);
  border:1px solid var(--hairline)}
.ev-when-row{display:grid;grid-template-columns:1fr 1fr;gap:9px;align-items:end}
.ev-time-f{display:flex;flex-direction:column;gap:4px;position:relative}
.ev-time-f label{font-size:11px;color:var(--muted)}
.ev-time-f input{height:34px;padding:0 10px;border-radius:8px;font:inherit;font-size:13px;
  color:var(--text);background:var(--surface-2);border:1px solid var(--hairline)}
.ev-time-f input:focus{outline:0;border-color:var(--accent)}

/* The time list. A native time input is a different control in every browser
   and none of them match the app; this is a text field that understands "930"
   with quarter hours one keypress away. */
.ev-time-list{position:absolute;top:100%;left:0;right:0;z-index:70;margin-top:4px;
  max-height:190px;overflow-y:auto;padding:4px;border-radius:9px;
  background:var(--surface);border:1px solid var(--hairline);
  box-shadow:0 14px 32px rgba(0,0,0,.34)}
.ev-time-list button{display:block;width:100%;text-align:left;height:28px;padding:0 9px;
  border:0;border-radius:6px;background:none;color:var(--text);font-size:12.5px;cursor:pointer;
  font-variant-numeric:tabular-nums}
.ev-time-list button:hover{background:var(--surface-2)}
.ev-time-list button.is-on{color:var(--accent);font-weight:600}

/* Duration as presets, with the consequence stated. "For 1 hr" means nothing
   without "Ends 15:30" next to it. */
.ev-durs{display:flex;flex-wrap:wrap;gap:5px}
.ev-dur{height:28px;padding:0 10px;border-radius:7px;cursor:pointer;font-size:12px;
  color:var(--text-2);background:var(--surface-2);border:1px solid var(--hairline);
  white-space:nowrap;transition:border-color var(--t-hover),color var(--t-hover)}
.ev-dur:hover{border-color:var(--accent)}
.ev-dur.is-on{border-color:var(--accent);color:var(--accent);font-weight:600}
.ev-ends{margin:1px 0 0;font-size:11.5px;color:var(--muted);font-variant-numeric:tabular-nums}

.ev-row-2{display:grid;grid-template-columns:1fr 1fr;gap:9px}
.ev-more summary{list-style:none;font-size:12px;color:var(--text-2);cursor:pointer;
  padding:7px 0;border-top:1px solid var(--hairline)}
.ev-more summary::-webkit-details-marker{display:none}
.ev-more summary::before{content:"+ ";color:var(--muted)}
.ev-more[open] summary::before{content:"- "}

/* The quick composer: a name and a time, because most events are exactly that. */
.qc-title{width:100%;height:42px;padding:0 12px;border-radius:10px;font:inherit;
  font-size:16px;font-weight:600;color:var(--text);
  background:var(--surface-2);border:1px solid var(--hairline)}
.qc-title:focus{outline:0;border-color:var(--accent)}
.qc-when{margin:9px 0 0;font-size:12px;color:var(--muted)}
/* ══ Shared Calendar fields ═══════════════════════════════════════════════
   One vocabulary for Event, Reminder, Schedule Task and Birthday. Every rule
   here is used by all four; nothing below is flow-specific, on purpose. The
   four modals used to have four date fields, four time controls and four
   disclosures, which is how they stopped looking like one product. */

.modal.ev-modal,.modal-reminder,.modal-schedule{width:min(544px,calc(100vw - 32px))}
.cf-body,.ev-body{display:flex;flex-direction:column;gap:10px;
  max-height:min(68vh,600px);overflow:auto;padding-right:2px}

/* The one prominent field each flow leads with. */
.cf-title input{width:100%;height:44px;padding:0 12px;border-radius:10px;
  font:inherit;font-size:17px;font-weight:600;color:var(--text);
  background:var(--surface-2);border:1px solid var(--hairline)}
.cf-title input::placeholder{font-weight:500;color:var(--muted)}
.cf-title input:focus{outline:0;border-color:var(--accent)}

/* Rows: fixed label column, so every modal lines up with every other. */
.cf-row{display:grid;grid-template-columns:74px 1fr;gap:10px;align-items:center}
.cf-row-top{align-items:start}
.cf-row-full{grid-template-columns:1fr}
.cf-lab{font-size:11.5px;color:var(--muted);padding-top:1px}
.cf-ctl-wrap{display:flex;flex-wrap:wrap;gap:6px;align-items:center;min-width:0}
.cf-group{display:flex;flex-direction:column;gap:9px;padding:11px 12px;border-radius:11px;
  background:color-mix(in srgb,var(--text) 3%,transparent);border:1px solid var(--hairline)}
.cf-group.is-quiet{background:none;border-color:transparent;padding:0}

/* One control height, one control look — buttons, inputs and selects alike. */
.cf-ctl{display:inline-flex;align-items:center;gap:7px;height:34px;padding:0 11px;
  border-radius:8px;font:inherit;font-size:13px;color:var(--text);cursor:pointer;
  background:var(--surface-2);border:1px solid var(--hairline);
  transition:border-color var(--t-hover)}
.cf-ctl:hover{border-color:color-mix(in srgb,var(--accent) 55%,var(--hairline))}
.cf-ctl:focus-visible{outline:0;border-color:var(--accent);
  box-shadow:0 0 0 2px color-mix(in srgb,var(--accent) 30%,transparent)}
.cf-input{flex:1 1 180px;cursor:text}
.cf-textarea{height:auto;min-height:56px;padding:9px 11px;resize:vertical;line-height:1.5}

/* ══ The shared Calendar dropdown ═════════════════════════════════════════
   A native <select> draws its option list with the OPERATING SYSTEM. No CSS
   reaches it — `color-scheme: dark` restyles the closed control and nothing
   else — so Calendar, Repeat, Notify and Area opened as bright white sheets
   in a dark app while the time picker beside them was correctly dark.

   This is a button and a panel, drawn into the same popover host as the date
   and time pickers, which is how it inherits their placement and their
   immunity to the modal's own overflow. */

.cf-menu-btn{justify-content:space-between;min-width:120px;max-width:100%;text-align:left}
.cf-menu-btn .cf-chev{margin-left:auto;flex:0 0 auto}
.cf-menu-text{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
  /* EXPLICIT, not inherited. The trigger sits on a group background and a
     muted token here is what made some values almost unreadable. */
  color:var(--text)}
.cf-menu-btn[aria-expanded="true"]{border-color:var(--accent)}
.cf-menu-mark{width:8px;height:8px;border-radius:50%;flex:0 0 auto}

.cf-menu{min-width:180px;max-width:min(320px,90vw);max-height:264px;overflow-y:auto;
  padding:4px;border-radius:10px;
  background:var(--surface);
  border:1px solid var(--hairline);
  box-shadow:0 16px 38px rgba(0,0,0,.42);
  scrollbar-width:thin}
.cf-menu:focus{outline:0}

/* Every ordinary option is plainly readable. This is the rule that was
   broken: a muted token on a dark panel is legible in a mock-up and not on a
   screen at arm's length. */
.cf-menu-opt{display:flex;align-items:center;gap:8px;width:100%;text-align:left;
  min-height:32px;padding:5px 9px;border:0;border-radius:7px;cursor:pointer;
  background:none;font:inherit;font-size:12.5px;
  color:var(--text)}
.cf-menu-opt:hover{background:var(--surface-2)}
.cf-menu-opt:focus-visible,.cf-menu-opt:focus{outline:0;background:var(--surface-2);
  box-shadow:inset 0 0 0 1px var(--accent)}
.cf-menu-opt.is-on{background:color-mix(in srgb,var(--accent) 20%,transparent);
  color:var(--accent);font-weight:600}
.cf-menu-opt[disabled]{cursor:default;color:var(--muted);opacity:.75}
.cf-menu-opt[disabled]:hover{background:none}
.cf-menu-l{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* Secondary text is quieter but never invisible. */
.cf-menu-h{flex:0 0 auto;font-size:10.5px;color:var(--text-2);opacity:.85}
.cf-menu-tick{width:9px;height:5px;flex:0 0 auto;margin-left:2px;
  border-left:1.8px solid currentColor;border-bottom:1.8px solid currentColor;
  transform:rotate(-45deg) translate(1px,-2px)}

/* The reminder menu is the same panel, in the same shared host as every other
   dropdown — it used to be absolutely positioned inside the modal body, where
   overflow:auto cut it in half. */

/* The one floating surface per dialog. Appended to the DIALOG rather than the
   scrolling body, so no panel is ever clipped by the modal's overflow, and
   above everything inside it. */
.cf-pop{position:absolute;z-index:200}
.modal .cf-pop{z-index:200}


.cf-ico{width:13px;height:13px;flex:0 0 auto;opacity:.65;background:currentColor;
  -webkit-mask-size:contain;mask-size:contain;
  -webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;
  -webkit-mask-position:center;mask-position:center}
.cf-ico-date{-webkit-mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath fill='none' stroke='%23000' stroke-width='1.6' d='M3.5 5.5h13v11h-13zM3.5 8.5h13M7 3.5v3M13 3.5v3'/%3E%3C/svg%3E");
  mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath fill='none' stroke='%23000' stroke-width='1.6' d='M3.5 5.5h13v11h-13zM3.5 8.5h13M7 3.5v3M13 3.5v3'/%3E%3C/svg%3E")}
.cf-ico-time{-webkit-mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cg fill='none' stroke='%23000' stroke-width='1.6'%3E%3Ccircle cx='10' cy='10' r='6.6'/%3E%3Cpath d='M10 6.4V10l2.6 1.7'/%3E%3C/g%3E%3C/svg%3E");
  mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cg fill='none' stroke='%23000' stroke-width='1.6'%3E%3Ccircle cx='10' cy='10' r='6.6'/%3E%3Cpath d='M10 6.4V10l2.6 1.7'/%3E%3C/g%3E%3C/svg%3E")}

.cf-check{display:flex;align-items:center;gap:8px;font-size:12.5px;color:var(--text-2)}
.cf-check-sub{padding-left:84px;margin-top:-3px}
.cf-note{margin:0;font-size:11px;color:var(--muted);line-height:1.45}

/* Pills: duration, and anything else that is a small set of choices. */
.cf-durs{display:flex;flex-wrap:wrap;gap:5px;align-items:center}
.cf-pill{height:28px;padding:0 10px;border-radius:7px;cursor:pointer;font-size:12px;
  white-space:nowrap;color:var(--text-2);background:var(--surface-2);
  border:1px solid var(--hairline);transition:border-color var(--t-hover),color var(--t-hover)}
.cf-pill:hover{border-color:var(--accent)}
.cf-pill.is-on{border-color:var(--accent);color:var(--accent);font-weight:600}
.cf-dur-in{width:66px;height:28px;padding:0 8px;border-radius:7px;font:inherit;font-size:12px;
  color:var(--text);background:var(--surface-2);border:1px solid var(--accent)}
.cf-ends{margin:2px 0 0;font-size:11.5px;color:var(--muted);font-variant-numeric:tabular-nums}

/* One disclosure. Full width, chevron hard right, same in every modal —
   it used to sit above the label in one and inside a control in another. */
/* `hidden` must actually hide.
   The attribute only carries `display:none` from the browser's own stylesheet,
   which ANY author `display` beats. Several controls here are flex, so setting
   `hidden` left them fully laid out and still focusable: More options never
   collapsed, and its six fields stayed tabbable while the section read as
   closed. This is the standard idiom, scoped to the Calendar forms. */
.modal [hidden],.cf-pop[hidden]{display:none!important}

.cf-more{margin-top:2px}
.cf-more-btn{display:flex;width:100%;align-items:center;justify-content:space-between;
  height:36px;padding:0 2px;border:0;background:none;cursor:pointer;
  font:inherit;font-size:12.5px;color:var(--text-2);border-top:1px solid var(--hairline)}
.cf-more-btn:hover{color:var(--text)}
.cf-chev{width:8px;height:8px;border-right:1.6px solid currentColor;
  border-bottom:1.6px solid currentColor;transform:rotate(45deg);
  transition:transform 160ms ease;opacity:.7}
.cf-more-btn.is-open .cf-chev{transform:rotate(-135deg)}
.cf-more-body{display:flex;flex-direction:column;gap:10px;padding:9px 0 2px}

/* Google reminders — several per event, phrased the way people say them. */
.cf-rems{position:relative;display:flex;flex-wrap:wrap;gap:6px;align-items:center}
.cf-rem-list{display:flex;flex-wrap:wrap;gap:5px}
.cf-rem{display:inline-flex;align-items:center;gap:5px;height:26px;padding:0 6px 0 9px;
  border-radius:7px;font-size:11.5px;color:var(--text-2);
  background:color-mix(in srgb,var(--accent) 12%,transparent);
  border:1px solid color-mix(in srgb,var(--accent) 34%,transparent)}
.cf-rem-x{border:0;background:none;color:inherit;cursor:pointer;font-size:14px;line-height:1;
  padding:0 2px;opacity:.75}
.cf-rem-x:hover{opacity:1;color:var(--danger)}
.cf-rem-custom{display:flex;align-items:center;gap:6px;padding:6px}
.cf-rem-custom input{width:70px;height:28px;padding:0 8px;border-radius:6px;font:inherit;
  font-size:12px;color:var(--text);background:var(--surface-2);border:1px solid var(--hairline)}
.cf-rem-custom span{font-size:11.5px;color:var(--muted)}
.cf-rem-note{width:100%;margin:2px 0 0;font-size:11px;color:var(--warn)}

/* Recurrence builder — real-world patterns without anyone meeting an RRULE. */
.cf-rec-wrap{padding-left:84px}
.cf-rec{display:flex;flex-direction:column;gap:8px;padding:10px 11px;border-radius:10px;
  background:color-mix(in srgb,var(--text) 4%,transparent);border:1px solid var(--hairline)}
.cf-rec-row{display:flex;align-items:center;gap:7px;flex-wrap:wrap;font-size:12.5px;
  color:var(--text-2)}
.cf-rec input[type=number]{width:58px;height:30px;padding:0 8px;border-radius:7px;font:inherit;
  font-size:12.5px;color:var(--text);background:var(--surface-2);border:1px solid var(--hairline)}
.cf-rec input[type=date],.cf-rec select{height:30px;padding:0 8px;border-radius:7px;font:inherit;
  font-size:12.5px;color:var(--text);background:var(--surface-2);border:1px solid var(--hairline);
  color-scheme:dark}
.cf-rec-days{display:flex;flex-wrap:wrap;gap:4px}
.cf-day{width:38px;height:28px;border-radius:7px;cursor:pointer;font-size:11.5px;
  color:var(--text-2);background:var(--surface-2);border:1px solid var(--hairline)}
.cf-day[aria-pressed="true"]{border-color:var(--accent);color:var(--accent);font-weight:600}
.cf-rec-month{display:flex;flex-direction:column;gap:6px;font-size:12.5px;color:var(--text-2)}
.cf-rec-month label{display:flex;align-items:center;gap:7px;flex-wrap:wrap}
.cf-rec-say{margin:2px 0 0;font-size:11.5px;color:var(--accent)}


/* The placement preview. Snapping is only useful if you can see where it
   snapped BEFORE committing — otherwise the first sign that 10:08 became
   10:00 is a confirmation dialog. */
.pl-ghost{position:absolute;left:3%;right:3%;z-index:2;pointer-events:none;
  display:flex;align-items:flex-start;justify-content:center;padding-top:3px;
  border-radius:7px;font-size:10.5px;font-variant-numeric:tabular-nums;
  color:var(--accent);
  background:color-mix(in srgb,var(--accent) 14%,transparent);
  border:1px dashed color-mix(in srgb,var(--accent) 55%,transparent)}
@media (prefers-reduced-motion:no-preference){
  .pl-ghost{transition:top 90ms ease-out}
}

/* The time picker: two short columns, not ninety-six rows. */
.tp{width:212px;padding:8px;border-radius:11px;background:var(--surface);
  border:1px solid var(--hairline);box-shadow:0 16px 38px rgba(0,0,0,.4)}
.tp-typed{margin-bottom:7px}
.tp-in{width:100%;height:32px;padding:0 10px;border-radius:8px;font:inherit;font-size:13px;
  color:var(--text);background:var(--surface-2);border:1px solid var(--hairline);
  font-variant-numeric:tabular-nums}
.tp-in:focus{outline:0;border-color:var(--accent)}
.tp-in.is-bad{border-color:var(--danger)}
.tp-cols{display:flex;gap:6px}
.tp-col{flex:1;max-height:168px;overflow-y:auto;display:flex;flex-direction:column;gap:2px;
  scrollbar-width:thin}
.tp-mer{flex:0 0 46px}
.tp-opt{height:28px;border:0;border-radius:6px;background:none;cursor:pointer;
  color:var(--text);font-size:12.5px;font-variant-numeric:tabular-nums}
.tp-opt:hover{background:var(--surface-2)}
.tp-opt.is-sel{background:color-mix(in srgb,var(--accent) 20%,transparent);
  color:var(--accent);font-weight:600}
.tp-foot{display:flex;align-items:center;justify-content:space-between;gap:6px;
  margin-top:7px;padding-top:7px;border-top:1px solid var(--hairline)}
.tp-clear,.tp-done{height:28px;padding:0 10px;border-radius:7px;cursor:pointer;font-size:12px;
  border:1px solid var(--hairline);background:var(--surface-2);color:var(--text-2)}
.tp-done{border-color:var(--accent);color:var(--accent)}


/* ══ Calendar form spacing ════════════════════════════════════════════════
   One set of numbers for all four flows, so spacing is composed rather than
   nudged control by control. Change a token here and every modal moves
   together; that is the whole point of having them. */
:root{
  --cf-pad:20px;          /* modal horizontal padding */
  --cf-section-gap:14px;  /* between top-level sections */
  --cf-field-gap:11px;    /* between rows inside a section */
  --cf-label-gap:12px;    /* label column to control */
  --cf-inline-gap:7px;    /* between controls on one row */
  --cf-label-w:78px;      /* the label column itself */
}

.modal.ev-modal .m-head,.modal-reminder .m-head,.modal-schedule .m-head,
.modal.ev-modal .m-body,.modal-reminder .m-body,.modal-schedule .m-body,
.modal.ev-modal .m-foot,.modal-reminder .m-foot,.modal-schedule .m-foot{
  padding-left:var(--cf-pad);padding-right:var(--cf-pad)}

.cf-body,.ev-body{gap:var(--cf-section-gap);padding-top:16px;padding-bottom:16px}
/* The group was a rounded card inset by its own padding, which put every row
   inside it 15px right of every row outside it — two label columns in one
   form. As a full-width band its contents sit on the body's inset, so all
   rows share one column and the grouping still reads. */
.cf-group{gap:var(--cf-field-gap);
  margin-inline:calc(-1 * var(--cf-pad));padding:13px var(--cf-pad);
  border-radius:0;border-left:0;border-right:0}
.cf-group.is-quiet{margin-inline:0;padding:0;border-top:0;border-bottom:0}
.cf-row{grid-template-columns:var(--cf-label-w) 1fr;gap:var(--cf-label-gap)}
.cf-ctl-wrap{gap:var(--cf-inline-gap)}
.cf-more-body{gap:var(--cf-field-gap);padding:12px 0 4px}
.cf-check-sub,.cf-rec-wrap{padding-left:calc(var(--cf-label-w) + var(--cf-label-gap))}

/* Headers and footers, identical across the four. */
.modal.ev-modal .m-head,.modal-reminder .m-head,.modal-schedule .m-head{
  padding-top:16px;padding-bottom:14px;border-bottom:1px solid var(--hairline)}
.modal.ev-modal .m-foot,.modal-reminder .m-foot,.modal-schedule .m-foot{
  padding-top:14px;padding-bottom:16px;gap:9px;
  border-top:1px solid var(--hairline)}
.ev-foot{gap:9px}

/* Validation: next to the thing that is wrong, gone as soon as it is fixed. */
.cf-err{width:100%;margin:5px 0 0;font-size:11.5px;color:var(--danger);line-height:1.4}
.is-invalid{border-color:var(--danger)!important}
.cf-title input.is-invalid,.cf-title textarea.is-invalid{border-color:var(--danger)}

/* The leading field is an <input> in three flows and an auto-growing
   <textarea> in Reminder. Same box either way. */
.cf-title textarea{width:100%;min-height:44px;padding:11px 12px;border-radius:10px;
  font:inherit;font-size:17px;font-weight:600;color:var(--text);resize:none;overflow:hidden;
  background:var(--surface-2);border:1px solid var(--hairline)}
.cf-title textarea::placeholder{font-weight:500;color:var(--muted)}
.cf-title textarea:focus{outline:0;border-color:var(--accent)}

/* Heading, close button and state slot are identical in all four. */
.modal.ev-modal .m-head,.modal-reminder .m-head,.modal-schedule .m-head{
  display:flex;align-items:center;gap:10px}
.modal.ev-modal .m-title,.modal-reminder .m-title,.modal-schedule .m-title{
  flex:1;min-width:0;font-size:17px;font-weight:600;line-height:1.3}
.modal.ev-modal .m-close{width:30px;height:30px;flex-shrink:0;margin-left:auto}

@media (max-width:560px){
  .cf-row{grid-template-columns:1fr;gap:5px}
  .cf-check-sub,.cf-rec-wrap{padding-left:0}
}

/* Reminder and Schedule Task share the composer's grouping, so the three
   creation surfaces read as one family rather than three eras of the app. */
.rm-kind{margin:7px 0 0;font-size:10.5px;color:var(--muted);line-height:1.45}
.ev-group .ev-row{margin:0}
.ev-group .ev-row + .ev-row{margin-top:7px}

/* Availability reads as part of the time decision, not a status bar bolted
   underneath it. */
.st-check{margin-top:9px;padding-top:8px;border-top:1px solid var(--hairline);
  font-size:11.5px;color:var(--muted);min-height:16px}
.st-check:empty{display:none}
.st-check.is-free{color:var(--ok)}
.st-check.is-busy{color:var(--warn)}

/* The chosen task should be unmistakable — it is the subject of the sentence. */
.st-task[aria-checked="true"],.st-task.is-on{border-color:var(--accent);
  background:color-mix(in srgb,var(--accent) 10%,transparent)}
.st-task[aria-checked="true"] .st-task-t,.st-task.is-on .st-task-t{font-weight:650;color:var(--text)}


@media (max-width:520px){
  .ev-when-row,.ev-row-2{grid-template-columns:1fr}
}


/* The confirmation. What it shows is what the SERVER prepared -- if this
   screen built its own description, the thing agreed to and the thing
   executed could differ, which is the whole failure a confirmation prevents. */
.ev-name{margin:0 0 10px;font-size:16px;font-weight:650;color:var(--text)}
.ev-rows{display:grid;grid-template-columns:auto 1fr;gap:5px 14px;margin:0;font-size:12.5px}
.ev-rows dt{color:var(--muted)}
.ev-rows dd{margin:0;color:var(--text)}
.ev-changes{display:flex;flex-direction:column;gap:6px;margin-bottom:11px}
.ev-change{display:grid;grid-template-columns:64px 1fr auto 1fr;gap:8px;align-items:baseline;
  font-size:12.5px}
.ev-change-f{font-size:10.5px;text-transform:uppercase;letter-spacing:.06em;color:var(--muted)}
.ev-change-o{color:var(--muted);text-decoration:line-through}
.ev-change-a{color:var(--muted)}
.ev-change-n{color:var(--text);font-weight:600}
.ev-warn{margin:10px 0 0;font-size:11.5px;color:var(--warn);line-height:1.45}
.ev-note{margin:0 0 8px;font-size:13px;color:var(--text);line-height:1.5}

/* A conflict is a WARNING. Google allows double-booking, people do it on
   purpose, and an app that refuses is an app that gets worked around. */
.ev-clash{margin-top:12px;padding:10px 12px;border-radius:9px;display:flex;
  flex-direction:column;gap:3px;font-size:12px;
  background:color-mix(in srgb,var(--warn) 12%,transparent);
  border:1px solid color-mix(in srgb,var(--warn) 38%,transparent)}
.ev-clash b{color:var(--text);font-size:12.5px}
.ev-clash span{color:var(--text-2)}
.ev-clash i{font-style:normal;color:var(--muted);font-size:11px;margin-top:3px}

@media (max-width:520px){
  .ev-row{grid-template-columns:1fr 1fr}
}

.lib-del:hover{color:var(--danger);border-color:var(--danger)}
/* Bookmark and Archive are page ACTIONS, not page settings, so a rule keeps
   them off the end of the settings list rather than merely a gap. */
.lib-menu-foot{margin-top:10px;padding-top:9px;border-top:1px solid var(--hairline);
  display:flex;flex-direction:column;gap:2px}

/* ── Pinboard: selection, groups, wires and the contextual bar ───────────
   Everything below is drawn from the PAPER tokens, never a fixed colour. The
   board sits on whichever paper the theme gives it, and the first version of
   the pins hard-coded light-theme values onto a dark page — which is how a pin
   ended up invisible, along with the button that deleted it. */
.bk-pin{transition:box-shadow var(--t-hover),border-color var(--t-hover),opacity 160ms ease}
.bk-pin.is-selected{border-color:var(--accent-c,var(--a-peach));
  box-shadow:0 0 0 2px color-mix(in srgb,var(--accent-c,var(--a-peach)) 45%,transparent),
    0 8px 22px rgba(0,0,0,.26)}
.bk-pin.is-connect-target{border-color:var(--accent-c,var(--a-peach));
  box-shadow:0 0 0 3px color-mix(in srgb,var(--accent-c,var(--a-peach)) 30%,transparent)}
/* Focus mode dims, never hides: what is not the subject stays legible enough
   to see it is still there. */
.bk-pin.is-dimmed,.bk-group.is-dimmed,.bk-wire.is-dimmed{opacity:.22}
.bk-board.is-focused .bk-pin:not(.is-dimmed){box-shadow:0 10px 30px rgba(0,0,0,.3)}
.bk-board.is-moving{cursor:grabbing}
.bk-board.is-connecting{cursor:crosshair}

/* The two handles a selected pin exposes. Hidden until selected so a resting
   board stays a board and not a control panel. */
.bk-pin-size,.bk-pin-link{position:absolute;opacity:0;pointer-events:none;
  transition:opacity var(--t-hover)}
.bk-pin.is-selected .bk-pin-size,.bk-pin.is-selected .bk-pin-link{opacity:1;pointer-events:auto}
.bk-pin-size{right:-4px;bottom:-4px;width:14px;height:14px;cursor:nwse-resize;
  border-radius:3px;background:var(--accent-c,var(--a-peach));
  border:2px solid var(--paper)}
.bk-pin-link{right:-9px;top:50%;margin-top:-7px;width:14px;height:14px;cursor:crosshair;
  border-radius:50%;background:var(--paper);
  border:2px solid var(--accent-c,var(--a-peach))}
.bk-pin-link:hover{background:var(--accent-c,var(--a-peach))}

/* Note styles. Restrained on purpose: enough to group by eye, not enough to
   turn a board into confetti. Each is a wash of one hue over the paper, so it
   still reads as paper in both themes rather than as a sticker. */
.bk-pin.bk-s-sun{background:color-mix(in srgb,#F3C969 22%,var(--paper));
  border-color:color-mix(in srgb,#F3C969 42%,transparent)}
.bk-pin.bk-s-rose{background:color-mix(in srgb,#E88EA1 20%,var(--paper));
  border-color:color-mix(in srgb,#E88EA1 40%,transparent)}
.bk-pin.bk-s-sky{background:color-mix(in srgb,#7FB6E8 20%,var(--paper));
  border-color:color-mix(in srgb,#7FB6E8 40%,transparent)}
.bk-pin.bk-s-sage{background:color-mix(in srgb,#8FC7A2 20%,var(--paper));
  border-color:color-mix(in srgb,#8FC7A2 40%,transparent)}
.bk-pin.bk-s-ink{background:color-mix(in srgb,var(--paper-ink) 22%,var(--paper));
  border-color:color-mix(in srgb,var(--paper-ink) 38%,transparent)}
.bk-pin.bk-s-quote{background:transparent;border-color:transparent;box-shadow:none;
  border-left:3px solid var(--accent-c,var(--a-peach));border-radius:0;
  font-style:italic;padding-left:14px}
.bk-pin.bk-s-quote:hover{border-color:transparent;
  border-left-color:var(--accent-c,var(--a-peach))}

/* Image frames. */
.bk-pin.bk-f-frame .bk-pin-img{border:1px solid color-mix(in srgb,var(--paper-ink) 26%,transparent);
  padding:3px;background:color-mix(in srgb,var(--paper-ink) 6%,var(--paper))}
.bk-pin.bk-f-polaroid{background:#F6F3EC;color:#2A2724;padding-bottom:22px}
.bk-pin.bk-f-polaroid .bk-pin-img{border-radius:2px}
.bk-pin.bk-f-polaroid .bk-pin-cap{color:#4A443C}
.bk-pin.bk-f-polaroid .bk-pin-grip::before{background:#8A8177}
.bk-pin.bk-f-polaroid .bk-pin-x{color:#6A6259}
.bk-pin-cap{font-size:11px;line-height:1.35;color:var(--paper-ink-2);outline:0;
  min-height:14px;word-break:break-word}
.bk-pin-cap:empty::before{content:attr(data-placeholder);color:var(--paper-ink-2);opacity:.65}

/* Groups. Drawn FROM membership every paint — never stored as a rectangle,
   because then moving one member would leave the frame behind and the picture
   would start lying about what belongs together. */
.bk-groups{position:absolute;inset:0;pointer-events:none}
.bk-group{position:absolute;border-radius:14px;pointer-events:none;
  border:1.5px dashed color-mix(in srgb,var(--accent-c,var(--a-peach)) 55%,transparent);
  background:color-mix(in srgb,var(--accent-c,var(--a-peach)) 7%,transparent);
  transition:opacity 160ms ease}
.bk-group-t{position:absolute;top:-9px;left:12px;pointer-events:auto;
  font-size:10px;letter-spacing:.06em;text-transform:uppercase;
  padding:1px 7px;border-radius:5px;outline:0;cursor:text;
  color:var(--paper-ink-2);background:var(--paper);
  border:1px solid color-mix(in srgb,var(--accent-c,var(--a-peach)) 40%,transparent)}
.bk-group-t:empty::before{content:attr(data-placeholder);opacity:.6}

/* Connections. The wire is a hairline; the invisible fat path beside it is
   what you can actually hit with a pointer. */
.bk-wires{position:absolute;inset:0;width:100%;height:100%;pointer-events:none;overflow:visible}
.bk-wire{pointer-events:auto}
.bk-wire-hit{stroke:transparent;stroke-width:14;cursor:pointer}
.bk-wire-line{stroke:color-mix(in srgb,var(--paper-ink) 45%,transparent);stroke-width:1.5;
  pointer-events:none}
.bk-wire:hover .bk-wire-line{stroke:var(--accent-c,var(--a-peach))}
.bk-wire.is-selected .bk-wire-line{stroke:var(--accent-c,var(--a-peach));stroke-width:2.5}
.bk-wire-lb{fill:var(--paper-ink-2);font-size:10px;pointer-events:none;
  paint-order:stroke;stroke:var(--paper);stroke-width:3px;stroke-linejoin:round}
.bk-wire-ghost{stroke:var(--accent-c,var(--a-peach));stroke-width:2;stroke-dasharray:4 4}

/* The contextual bar. Restrained: it names what you can do with THIS, and
   nothing else. Positioned above the selection and translated onto it. */
.bk-pin-bar{position:absolute;transform:translate(-50%,-100%);z-index:60;
  display:flex;gap:2px;padding:3px;border-radius:9px;
  background:var(--surface);border:1px solid var(--hairline);
  box-shadow:0 10px 26px rgba(0,0,0,.3)}
.bk-pin-bar button{height:26px;padding:0 9px;border:0;border-radius:6px;cursor:pointer;
  background:none;color:var(--text);font-size:11.5px;white-space:nowrap;
  transition:background var(--t-hover)}
.bk-pin-bar button:hover{background:var(--surface-2)}
.bk-bar-in input{height:26px;width:130px;padding:0 8px;border-radius:6px;font-size:11.5px;
  background:var(--surface-2);border:1px solid var(--hairline);color:var(--text)}

/* The More sheet: style, frame and arrangement, on demand rather than in the
   bar. Everything here is a second-order choice. */
.bk-pin-sheet{position:absolute;z-index:61;width:210px;padding:9px 10px 10px;
  border-radius:11px;background:var(--surface);border:1px solid var(--hairline);
  box-shadow:0 14px 34px rgba(0,0,0,.34)}
.bk-sheet-h{margin:0 0 5px;font-size:9.5px;letter-spacing:.08em;text-transform:uppercase;
  color:var(--muted)}
.bk-sheet-r{display:flex;flex-wrap:wrap;gap:4px;margin-bottom:9px}
.bk-sheet-r:last-child{margin-bottom:0}
.bk-sheet-r button{height:25px;padding:0 8px;border-radius:6px;cursor:pointer;font-size:11px;
  background:var(--surface-2);border:1px solid var(--hairline);color:var(--text)}
.bk-sheet-r button:hover{border-color:var(--accent)}
.bk-sheet-r button.is-on{border-color:var(--accent);color:var(--accent)}
.bk-sheet-sw .bk-sw{width:26px;height:26px;padding:0;border-radius:50%}
.bk-sw.bk-s-plain{background:var(--surface-2)}
.bk-sw.bk-s-sun{background:#F3C969}
.bk-sw.bk-s-rose{background:#E88EA1}
.bk-sw.bk-s-sky{background:#7FB6E8}
.bk-sw.bk-s-sage{background:#8FC7A2}
.bk-sw.bk-s-ink{background:var(--text)}
.bk-sw.bk-s-quote{background:linear-gradient(90deg,var(--accent) 0 30%,var(--surface-2) 30%)}
.bk-sw.is-on{outline:2px solid var(--accent);outline-offset:2px}

/* Marquee and alignment guides. */
.bk-marquee{position:absolute;z-index:55;pointer-events:none;border-radius:4px;
  border:1px solid var(--accent-c,var(--a-peach));
  background:color-mix(in srgb,var(--accent-c,var(--a-peach)) 12%,transparent)}
.bk-guides{position:absolute;inset:0;z-index:54;pointer-events:none}
.bk-guide{position:absolute;background:var(--accent-c,var(--a-peach));opacity:.6}
.bk-guide-v{top:0;bottom:0;width:1px}
.bk-guide-h{left:0;right:0;height:1px}

@media (prefers-reduced-motion:reduce){
  .bk-pin,.bk-group{transition:none}
}

/* A folded shelf keeps its heading and its count — nothing is hidden, only
   the rack of covers is put away. Click the heading to open it. */
.lib-shelf.is-folded .lib-rail{display:none}
.lib-shelf.is-folded .lib-shelf-head{opacity:.7;cursor:pointer}
.lib-shelf.is-folded .lib-shelf-head::after{content:'Show';font-size:11px;opacity:.7;
  letter-spacing:.04em}

/* The rail drops below the Book rather than beside it when there is not
   genuinely room for both (§34).

   1360px, not 1100px: the sidebar already takes ~210px and the rail takes 300,
   so a 1100px window leaves the spread about 570px — measured, that is two
   224px pages, which is narrower than the Book is readable at. The threshold is
   the width at which the spread still gets ~800px, not the width at which two
   columns technically fit. */
@media (max-width:1360px){
  .bk-layout.has-rail{grid-template-columns:minmax(0,1fr)}
  .bk-rail{position:static;max-height:none;order:2;width:100%;max-width:720px;
    justify-self:center}
}

/* GEOMETRY — audit §2. Do not adjust these to taste. */
.bk-book{position:relative;display:grid;gap:6px;width:100%;
  aspect-ratio:210/297;max-width:780px;perspective:1600px}
.bk-book.bk-spread{aspect-ratio:420/297;max-width:1320px;
  grid-template-columns:repeat(2,minmax(0,1fr))}

.bk-page{position:relative;background:var(--paper);border-radius:14px;
  padding:28px 32px 18px 58px;overflow:hidden;display:flex;flex-direction:column;
  box-shadow:var(--paper-shadow),inset 3px 0 0 0 var(--accent-c,var(--a-peach))}
.bk-page-right{padding:28px 58px 18px 32px;
  box-shadow:var(--paper-shadow),inset -3px 0 0 0 var(--accent-c,var(--a-peach))}
/* Margin stripe — 46px in from the bound edge, mirrored on the right page. */
.bk-page::before{content:'';position:absolute;top:0;bottom:0;left:46px;width:1px;
  background:var(--accent-c,var(--a-peach));opacity:.35}
.bk-page-right::before{left:auto;right:46px}

/* The page title band. An explicit height, so the body grid begins at a stated
   place rather than wherever a font happened to leave it. The editor's rules
   are drawn relative to the editor, so this band cannot shift them — but it
   can shift where the first ruled line SITS on the page, and that should be a
   decision. 60px = two rows of the body grid. */
.bk-page-hdr{flex:0 0 auto;height:60px;margin:0;display:flex;align-items:center;gap:6px}
.bk-page-more,.bk-tab-more{flex:0 0 auto;background:none;border:0;cursor:pointer;
  border-radius:7px;width:24px;height:24px;display:grid;place-items:center;
  color:var(--paper-ink-2);opacity:0;
  transition:opacity var(--t-hover),background var(--t-hover),color var(--t-hover)}
.bk-page:hover .bk-page-more,.bk-page:focus-within .bk-page-more{opacity:.8}
.bk-page-more:hover{background:rgba(255,255,255,.07);color:var(--paper-ink)}
.bk-page-more:focus-visible,.bk-tab-more:focus-visible{opacity:1;
  outline:2px solid var(--accent);outline-offset:1px}
/* The section menu hangs off the ACTIVE tab only — one section is being
   worked on, and a control on every tab is six controls competing. */
.bk-tab-wrap{position:relative;display:inline-flex;align-items:center;flex:0 0 auto}
.bk-tab-more{position:absolute;right:4px;top:50%;transform:translateY(-50%);
  color:#1A1624;opacity:.55;width:20px;height:20px}
.bk-tab-wrap:hover .bk-tab-more{opacity:.9}
.bk-tab-wrap:has(.bk-tab.on) .bk-tab{padding-right:28px}
.lib-menu-note{margin:0;padding:9px 11px;font-size:12px;color:var(--muted);line-height:1.5}

/* ── The layout picker ────────────────────────────────────────────────
   The label and its description STACK rather than sitting side by side in
   the flex row, and each option carries the shape of the page it makes. */

/* ONE panel, not two.
   `.util-surface` already is a bordered, shadowed, padded box; putting a
   `.lib-menu` inside it drew a second identical box within the first, which
   is the "list inside a list" this looked like. Inside a surface the menu is
   just its contents. */
.util-surface .lib-menu{background:none;border:0;box-shadow:none;padding:0;
  min-width:0;border-radius:0}
.util-surface:has(.lib-menu){padding:6px}
.util-surface:has(.lib-menu-layouts){width:302px}

.lib-menu-layouts{max-height:min(68vh,520px);overflow-y:auto;overscroll-behavior:contain;
  /* A quiet scrollbar. The default one is a system slab across the corner of
     a small panel and reads as another edge. */
  scrollbar-width:thin;scrollbar-color:var(--border-strong) transparent;
  padding-right:2px}
.lib-menu-layouts::-webkit-scrollbar{width:8px}
.lib-menu-layouts::-webkit-scrollbar-track{background:transparent}
.lib-menu-layouts::-webkit-scrollbar-thumb{background:var(--border-strong);
  border-radius:99px;border:2px solid transparent;background-clip:content-box}
.lib-menu-layouts::-webkit-scrollbar-thumb:hover{background:var(--text-2);
  border:2px solid transparent;background-clip:content-box}
.lib-menu-hd{margin:8px 0 2px;padding:0 11px;font-size:10.5px;font-weight:650;
  letter-spacing:.07em;text-transform:uppercase;color:var(--muted)}
.lib-menu-hd:first-child{margin-top:2px}
/* The text cell. `align-items:flex-start` because a two-line description must
   sit under its label, not straddle the icon. */
.lib-menu-layouts button{align-items:flex-start}
.lib-menu-tx{display:block;min-width:0}
.lib-menu-tx b{display:block;font-weight:600;line-height:1.35}
.lib-menu-tx small{display:block;font-size:11.5px;line-height:1.4;color:var(--muted);
  margin-top:1px;white-space:normal}
.lib-menu-layout .lib-menu-i{margin-top:1px;flex:none}
/* The layout this page already uses. Marked, not disabled — re-picking it is
   harmless and being told which one you are on is the useful part. */
.lib-menu-layout.is-on{background:var(--surface-3)}
.lib-menu-layout.is-on .lib-menu-i{color:var(--accent)}
.lib-menu-layout.is-on .lib-menu-tx b::after{content:'Current';float:right;
  font-size:10px;font-weight:600;letter-spacing:.05em;text-transform:uppercase;
  color:var(--accent);margin-left:10px}

/* ── What a page is FOR ───────────────────────────────────────────────
   Chips, not a second stacked list. These are all the SAME page — six
   entries with a sentence each said the opposite, that they were six
   different kinds of thing. A row of peers says what is true, and takes a
   fifth of the height, which is most of the reason the menu had to scroll. */
.lib-menu-sub{margin:0 0 8px;padding:0 11px;font-size:11.5px;line-height:1.45;
  color:var(--muted)}
.lib-purposes{display:flex;flex-wrap:wrap;gap:6px;padding:0 11px 4px}
/* `.lib-menu .lib-purpose`, not `.lib-purpose`: `.lib-menu button` is (0,1,1)
   and sets `width:100%`, which beat a single class and turned every chip into a
   full-width bar — the stacked list this was meant to replace. */
.lib-menu .lib-purpose{width:auto;flex:0 0 auto;display:inline-flex;align-items:center;
  font-size:12px;padding:5px 11px;border-radius:999px;cursor:pointer;
  font-family:inherit;color:var(--text-2);background:var(--surface-3);
  border:1px solid transparent;transition:background var(--t-hover),color var(--t-hover)}
.lib-menu .lib-purpose:hover{color:var(--text);border-color:var(--border-strong)}
.lib-menu .lib-purpose.is-on{background:var(--accent);color:var(--on-accent,#1b1726);
  font-weight:600}

/* The label on the page itself. Small, quiet, and the only visible difference
   between a research page and a page of notes — which is the honest amount of
   difference there is. */
.bk-page-purpose{font-size:10px;font-weight:600;letter-spacing:.09em;
  text-transform:uppercase;color:var(--paper-ink-2);white-space:nowrap;
  padding:2px 8px;border-radius:999px;
  border:1px solid color-mix(in srgb,var(--paper-ink) 18%,transparent)}

.bk-page-title{flex:1;min-width:0;background:none;border:0;outline:0;
  font-family:'Playfair Display',Georgia,serif;font-size:19px;color:var(--paper-ink);
  padding:0 0 6px;border-bottom:1px solid transparent;
  transition:border-color var(--t-hover)}
.bk-page-title{width:100%}
.bk-page-title::placeholder{color:var(--paper-ink-2);opacity:.5}
.bk-page-title:focus{border-bottom-color:var(--accent-c,var(--a-peach))}
.bk-page-body{flex:1;min-height:0;display:flex}

/* RULED PAPER — audit §3, carried verbatim. The repeat cycle equals the
   line-height exactly, and `local` scrolls the rules with the text. It lives
   on the scrolling text element, never on the page: on the page the lines
   drift the moment a heading sits above them. */
/* The ruled area is a whole number of rows, so the page ends ON a rule rather
   than part-way through one, and scrolling moves in whole lines.
   `round()` does the floor in CSS — no JS measuring the layout, no resize
   observer, and it degrades to the plain flex height where it is unsupported. */
.bk-editor{flex:1;min-height:0;height:round(down,100%,30px);
  overflow-y:auto;outline:0;
  color:var(--paper-ink);font-size:15px;line-height:30px;
  background-image:repeating-linear-gradient(to bottom,
    transparent 0,transparent 29px,var(--paper-line) 29px,var(--paper-line) 30px);
  background-attachment:local;
  -webkit-overflow-scrolling:touch}
.bk-editor::-webkit-scrollbar{width:6px}
.bk-editor::-webkit-scrollbar-thumb{background:rgba(255,255,255,.1);border-radius:3px}

/* ── THE BLOCK GRID (F2.1) ─────────────────────────────────────────────
   ONE formula, applied to every block. Not a set of per-element nudges.

       height = (lead + lines) x 30px

   `lines` is however many ruled lines the text wraps onto — it follows from
   line-height being 30px everywhere, so it needs no declaration.
   `lead` is the number of rows a block claims for its own typography:

       paragraph 0   list 0   quote 0   heading 1   subheading 1

   Two rules make that work, and both matter:

   1. LEAD IS PADDING, NEVER MARGIN. A margin belongs to no element. The
      caret cannot be placed in it, so a 30px margin above a heading drew a
      ruled line nobody could click into — which is exactly the defect this
      section fixes. Padding belongs to the block, so clicking it puts the
      caret in the heading, where it should be.

   2. A LEAD ROW IS UNRULED. `::before` paints paper over the lead, so the
      space a heading owns has no rule in it. A writable blank line always
      has a rule; typography-owned space never does. That is the whole
      visual grammar, and it is why the lead cannot be mistaken for a line
      you should be able to type on.

   Browser defaults are zeroed rather than overridden, so nothing can collapse
   or leak in from the user agent stylesheet. */
.bk-editor > *{margin:0;padding:0;line-height:30px}
.bk-editor p,.bk-editor li,.bk-editor blockquote p{margin:0;line-height:30px}

/* lead: 1 — see the formula above. */
.bk-editor h2,.bk-editor h3{
  position:relative;margin:0;padding:30px 0 0;font-weight:500;
  font-family:'Playfair Display',Georgia,serif;color:var(--paper-ink)}
.bk-editor h2{font-size:23px}
.bk-editor h3{font-size:18px}
.bk-editor h2::before,.bk-editor h3::before{
  content:'';position:absolute;left:0;right:0;top:0;height:30px;
  background:var(--paper);pointer-events:none}

/* lead: 0 — a list is exactly its items, a quote exactly its paragraphs. */
.bk-editor ul,.bk-editor ol{margin:0;padding:0 0 0 24px}
.bk-editor li{margin:0;padding:0}
.bk-editor li::marker{color:var(--paper-ink-2)}
.bk-editor blockquote{margin:0;padding:0 0 0 15px;
  border-left:2px solid var(--accent-c,var(--a-peach));color:var(--paper-ink-2);
  font-style:italic}
.bk-editor a{color:var(--accent-c,var(--a-peach));text-decoration:underline;
  text-underline-offset:2px}
.bk-editor:focus-visible{outline:0}
/* §13 — content from a newer build. Visible, labelled, preserved on save. */
.bk-unknown{border:1px dashed var(--border-strong);border-radius:8px;
  padding:0 12px;margin:0;line-height:30px;color:var(--paper-ink-2);font-size:12.5px;
  background:rgba(255,255,255,.03);user-select:none}

/* The blank companion of an odd final page. A rendering decision, not a row. */
.bk-page-blank{align-items:center;justify-content:center;
  box-shadow:var(--paper-shadow),inset -3px 0 0 0 var(--accent-c,var(--a-peach));
  background:var(--paper-2)}
.bk-page-blank::before{display:none}
.bk-blank{text-align:center;color:var(--paper-ink-2);font-size:13px;
  display:flex;flex-direction:column;align-items:center;gap:14px}
.bk-blank p{margin:0;font-family:'Playfair Display',Georgia,serif;font-size:15px;opacity:.75}

/* Arrows — 44px, 48px from 900px up (audit §7). */
.bk-arrow{flex:0 0 auto;width:44px;height:44px;border-radius:50%;border:1px solid var(--border);
  background:var(--surface);color:var(--text-2);display:grid;place-items:center;cursor:pointer;
  transition:background var(--t-hover),color var(--t-hover),transform var(--t-hover),
    opacity var(--t-hover)}
.bk-arrow svg{width:20px;height:20px}
.bk-arrow:hover:not(:disabled){background:var(--surface-2);color:var(--text);transform:scale(1.06)}
.bk-arrow:disabled{opacity:.3;cursor:default}
.bk-arrow:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
@media (min-width:900px){.bk-arrow{width:48px;height:48px}}

/* Tabs — audit §4. Round on top, square at the bottom; never wraps. */
.bk-tabs{display:flex;flex-wrap:nowrap;overflow-x:auto;gap:5px;width:100%;max-width:1320px;
  padding:0 52px 0;border-bottom:1px dashed var(--border);scrollbar-width:thin}
.bk-tabs::-webkit-scrollbar{height:4px}
.bk-tabs::-webkit-scrollbar-thumb{background:var(--border);border-radius:2px}
.bk-tab{flex:0 0 auto;display:flex;align-items:center;gap:7px;
  border-radius:20px 20px 4px 4px;border:0;cursor:pointer;
  font-family:'Playfair Display',Georgia,serif;font-size:12px;font-weight:600;
  padding:8px 15px 7px;background:var(--surface-2);color:var(--text-2);
  transition:background var(--t-hover),color var(--t-hover),transform var(--t-hover)}
.bk-tab:hover{transform:translateY(-1px);color:var(--text)}
.bk-tab.on{background:var(--accent-c,var(--a-peach));color:#1A1624}
.bk-tab:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
.bk-tab-n{font-family:Inter,sans-serif;font-size:9.5px;font-weight:700;opacity:.65;
  font-variant-numeric:tabular-nums}
.bk-tab-add{background:none;border:1px dashed var(--border-strong);color:var(--muted);
  font-size:15px;padding:6px 13px 5px}

/* Cover — audit §5.
   The closed cover is ONE PAGE of the open book: the frame takes the spread's
   own proportions and max-width, and the cover fills its height. That makes
   the two states the same object at the same size instead of a card and a
   book that happen to share a title. */
.bk-arrow-ghost{visibility:hidden;pointer-events:none;flex:0 0 auto}
.bk-cover-frame{flex:1;min-width:0;max-width:1320px;aspect-ratio:420/297;
  display:flex;align-items:center;justify-content:center}
.bk-stage-cover .bk-book{max-width:none;width:auto;height:100%;aspect-ratio:210/297}
.bk-cover-page{padding:0;box-shadow:var(--paper-shadow),inset 3px 0 0 0 var(--a-peach)}
.bk-cover-page::before{left:22px;opacity:.22}
.bk-cover{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;
  text-align:center;padding:46px 40px 34px;gap:0}
.bk-cover-mark{font-family:'Playfair Display',Georgia,serif;font-size:13px;
  color:var(--a-peach);opacity:.8;margin-bottom:auto}
.bk-cover-pre{font-size:11px;font-weight:600;letter-spacing:.32em;text-transform:uppercase;
  color:var(--paper-ink-2);margin-bottom:16px}
.bk-cover-title{font-family:'Playfair Display',Georgia,serif;font-weight:500;font-size:64px;
  line-height:1.06;color:var(--paper-ink);margin:0}
.bk-cover-sub{font-family:'Playfair Display',Georgia,serif;font-style:italic;font-size:17px;
  color:var(--paper-ink-2);margin:12px 0 0}
.bk-cover-rule{width:140px;height:1px;background:var(--paper-ink-2);opacity:.4;margin:32px 0}
.bk-cover-author{font-size:11px;font-weight:600;letter-spacing:.18em;text-transform:uppercase;
  color:var(--paper-ink-2);margin:0}
.bk-open{margin-top:auto}

/* Foot: where you are, and whether it is saved. */
.bk-foot{display:flex;align-items:center;gap:14px;width:100%;max-width:1320px;
  padding:0 52px;font-size:11.5px;color:var(--muted)}
.bk-context{font-variant-numeric:tabular-nums}
.bk-save{margin-left:auto;display:flex;align-items:center;gap:8px;
  /* Fixed width so a status change never nudges the layout while typing. */
  min-width:112px;justify-content:flex-end;font-variant-numeric:tabular-nums}
.bk-save[data-state="saved"]{color:var(--ok)}
.bk-save[data-state="saving"]{color:var(--text-2)}
.bk-save[data-state="unsaved"]{color:var(--warn)}
.bk-save[data-state="failed"],.bk-save[data-state="conflict"]{color:var(--danger)}
.bk-retry{background:var(--surface-2);border:0;border-radius:6px;color:var(--text);
  font-size:11px;padding:3px 8px;cursor:pointer;font-family:inherit}
.bk-retry:hover{background:var(--surface-3)}

/* Toolbar — restrained. A ribbon in a book is a word processor in disguise. */
.bk-toolbar{display:flex;align-items:center;gap:3px;flex-wrap:wrap;
  background:var(--surface);border:1px solid var(--border);border-radius:12px;
  padding:5px 7px;box-shadow:var(--e1)}
.bk-tb{width:30px;height:30px;border-radius:8px;border:0;background:none;cursor:pointer;
  color:var(--text-2);font-size:13px;display:grid;place-items:center;font-family:inherit;
  transition:background var(--t-hover),color var(--t-hover)}
.bk-tb:hover{background:var(--surface-2);color:var(--text)}
.bk-tb.on{background:var(--accent-soft);color:#fff}
.bk-tb:focus-visible{outline:2px solid var(--accent);outline-offset:-1px}
.bk-tb-sep{width:1px;height:18px;background:var(--border);margin:0 4px}
.bk-tb-style{background:var(--surface-2);border:0;border-radius:8px;color:var(--text);
  font-size:12px;font-family:inherit;padding:6px 8px;cursor:pointer;outline:0}
.bk-tb-search{margin-left:4px;font-size:16px}

.bk-head{display:flex;align-items:center;gap:12px;flex-wrap:wrap;width:100%;
  max-width:1320px;padding:0 52px}
.bk-head-title{font-family:'Playfair Display',Georgia,serif;font-size:19px;color:var(--text);
  margin:0;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.bk-head-acts{margin-left:auto;display:flex;align-items:center;gap:8px}

/* Link prompt — never window.prompt (§24). */
.bk-linkbox{position:fixed;left:50%;top:24%;transform:translateX(-50%);z-index:120;
  background:var(--surface-2);border:1px solid var(--border);border-radius:14px;
  box-shadow:var(--e3);padding:16px;width:min(360px,calc(100vw - 40px));
  display:flex;flex-direction:column;gap:12px}
.bk-linkbox label{font-size:12px;color:var(--text-2);display:flex;flex-direction:column;gap:6px}
.bk-linkbox input{background:var(--surface);border:1px solid var(--border);border-radius:9px;
  padding:9px 11px;color:var(--text);font-size:13px;outline:0;font-family:inherit}
.bk-linkbox input:focus{border-color:var(--accent)}
.bk-linkbox-acts{display:flex;gap:8px;justify-content:flex-end}

/* Within-book search. */
.bk-search{position:absolute;top:0;right:0;width:min(370px,100%);z-index:8;
  background:var(--surface);border:1px solid var(--border);border-radius:14px;
  box-shadow:var(--e3);padding:12px;display:flex;flex-direction:column;gap:10px;
  max-height:min(460px,70vh)}
.bk-search-top{display:flex;gap:8px;align-items:center}
.bk-search-input{flex:1;min-width:0;background:var(--surface-2);border:1px solid var(--border);
  border-radius:9px;padding:8px 11px;color:var(--text);font-size:13px;outline:0;font-family:inherit}
.bk-search-input:focus{border-color:var(--accent)}
.bk-search-hint{font-size:12.5px;color:var(--muted);margin:0;padding:6px 2px}
.bk-search-list{list-style:none;margin:0;padding:0;overflow-y:auto;display:flex;
  flex-direction:column;gap:3px}
.bk-search-hit{display:flex;flex-direction:column;gap:3px;width:100%;text-align:left;
  background:none;border:0;border-radius:9px;padding:8px 10px;cursor:pointer;font-family:inherit;
  transition:background var(--t-hover)}
.bk-search-hit:hover{background:var(--surface-2)}
.bk-search-hit:focus-visible{outline:2px solid var(--accent);outline-offset:-2px}
.bk-hit-where{font-size:10.5px;font-weight:700;letter-spacing:.05em;text-transform:uppercase;
  color:var(--muted)}
.bk-hit-text{font-size:12.5px;color:var(--text-2);line-height:1.5;
  display:-webkit-box;-webkit-line-clamp:2;line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}

/* Page turn — audit §6. Both pages move together so it reads as one motion.
   No 3D curl: `perspective` is declared and deliberately unused. */
@keyframes bkLeaveLeft{to{transform:translateX(-14%);opacity:0}}
@keyframes bkLeaveRight{to{transform:translateX(14%);opacity:0}}
@keyframes bkEnterLeft{from{transform:translateX(14%);opacity:0}}
@keyframes bkEnterRight{from{transform:translateX(-14%);opacity:0}}
.bk-book.leave-next{animation:bkLeaveLeft .26s cubic-bezier(.4,0,.2,1) forwards}
.bk-book.leave-prev{animation:bkLeaveRight .26s cubic-bezier(.4,0,.2,1) forwards}
.bk-book.enter-next{animation:bkEnterLeft .26s cubic-bezier(.4,0,.2,1)}
.bk-book.enter-prev{animation:bkEnterRight .26s cubic-bezier(.4,0,.2,1)}

/* The computed Diary habit. Quietly marked as a system row: it is not one of
   your habits, it is Life OS keeping count of one for you. */
/* ── The computed Diary habit (D2.2 §7) ─────────────────────────────────
   VISUAL PARITY. It is `.hb-row` and nothing else: the same row height, the
   same 32px ring drawn by the same SVG, the same type, the same spacing, the
   same hover, the same completed appearance. There is no rule here that
   changes any of those, deliberately — the previous version had its own 22px
   ring, its own weight and a `SYSTEM` badge, and read as a different component
   that happened to live in the habits card.

   What remains is only what BEHAVES differently:
     a quiet divider below it, because it is first and always first;
     a small diary mark where an ordinary row has nothing;
     "Automatic" as the title on both controls.
   The distinction explains the behaviour without inventing a component. */
.hb-diary{position:relative}
.hb-diary::after{content:'';position:absolute;left:6px;right:6px;bottom:-1px;
  height:1px;background:var(--border);opacity:.5}
.hb-diary .hb-ring{cursor:pointer}
.hb-diary .hb-name{cursor:pointer;text-align:left}
.hb-auto{flex:0 0 auto;display:grid;place-items:center;color:var(--muted);opacity:.55;
  transition:opacity var(--t-hover)}
.hb-diary:hover .hb-auto{opacity:.9}

/* ═══════════════════════════════════════════════════════════════════════
   DIARY (Phase D2)
   A two-page spread, in the same book language as Library.

   The `.bk-*` geometry is reused wholesale — the 420/297 spread, the 6px
   gutter, A4 pages, the audited padding, the margin stripe, the mirrored
   coloured edge, the ruled paper and the 30px block grid. That geometry is
   not Library's property: it is the house style for a page you write on, and
   two surfaces that both hold writing should not invent two of them.

   What is Diary's own is what the two pages CONTAIN. Left is reflective
   writing; right is a quick check-in. Two kinds of thinking, one object.
   ═══════════════════════════════════════════════════════════════════════ */
body:has(.dia-page) .main-wrap{grid-template-columns:minmax(0,1fr) 0}
body:has(.dia-page) .rail{display:none}

.dia{display:flex;flex-direction:column;gap:14px;align-items:center;width:100%}
.dia-book{--accent-c:var(--a-lavender)}

/* Top controls: previous day, Today, next day, a month jump, History.
   D1 had a second chevron pair labelled "Entry" — it meant "the previous day
   I actually wrote on", which is a real thing to want and not a thing anyone
   reads off a chevron. That behaviour lives in History now, where the month
   grid shows exactly where those days are. */
.dia-actions{display:flex;align-items:center;gap:10px;flex-wrap:wrap}
.dia-daynav{display:flex;align-items:center;gap:2px;background:var(--surface);
  border:1px solid var(--border);border-radius:10px;padding:3px}
.dia-step{width:30px;height:30px;border-radius:7px;border:0;background:none;cursor:pointer;
  color:var(--text-2);display:grid;place-items:center;font-family:inherit;
  transition:background var(--t-hover),color var(--t-hover)}
.dia-step:hover{background:var(--surface-2);color:var(--text)}
.dia-step:focus-visible{outline:2px solid var(--accent);outline-offset:1px}
.dia-today{border:0;background:none;cursor:pointer;font-family:inherit;font-size:12px;
  font-weight:600;color:var(--text-2);padding:0 12px;height:30px;border-radius:7px;
  transition:background var(--t-hover),color var(--t-hover)}
.dia-today:hover{background:var(--surface-2);color:var(--text)}
.dia-today.is-here{background:var(--accent-soft);color:#fff}
.dia-today:focus-visible{outline:2px solid var(--accent);outline-offset:1px}

/* The date jump — the app's own month grid, never <input type="date">. */
.dia-jump{width:270px}
.dia-jump-head{display:flex;align-items:center;justify-content:space-between;gap:8px;
  margin-bottom:10px}
.dia-jump-title{font-family:'Playfair Display',Georgia,serif;font-size:15px;color:var(--text)}
.dia-jump-grid{display:flex;flex-direction:column;gap:2px}
.dia-jump-row{display:grid;grid-template-columns:repeat(7,minmax(0,1fr));gap:2px}
.dia-jump-week span{text-align:center;font-size:9.5px;font-weight:700;letter-spacing:.05em;
  text-transform:uppercase;color:var(--muted);padding-bottom:3px}
.dia-jump-cell{position:relative;aspect-ratio:1;border:0;border-radius:8px;background:none;
  cursor:pointer;color:var(--text-2);font-family:inherit;font-size:12px;
  display:grid;place-items:center;transition:background var(--t-hover),color var(--t-hover)}
.dia-jump-cell:hover{background:var(--surface-3);color:var(--text)}
.dia-jump-cell:focus-visible{outline:2px solid var(--accent);outline-offset:-2px}
.dia-jump-cell.is-outside{opacity:.4}
.dia-jump-cell.has-entry{font-weight:700;color:var(--text)}
.dia-jump-cell.is-today{box-shadow:inset 0 0 0 1px var(--border-strong)}
.dia-jump-cell.is-open{background:var(--accent-soft);color:#fff}
.dia-jump-cell .dia-dot{position:absolute;bottom:3px;left:50%;transform:translateX(-50%)}

/* Both pages scroll their own content: a spread is a fixed frame, and a long
   day must not stretch it. */
/* THE SPREAD'S HEIGHT — D2.2 §3, §4.

   height = max(approvedBaseHeight, leftRequired, rightRequired)

   and all three terms are LAYOUT, not script. No element is measured, no
   inline height is written, and nothing animates the result — so there is no
   stale height to clear, nothing to settle under a throttled timeline, and the
   shrink when writing is deleted is simply the grid re-solving.

     approvedBaseHeight  `aspect-ratio` as a MINIMUM. The same 420:297 the
                         Library Book rests at, computed from the spread's own
                         width — identical to the open Book at the same size.
     leftRequired,
     rightRequired       the natural height of each page's content. `stretch`
                         gives both pages the taller of the two, so the gutter,
                         the edges and the margin stripes extend together.

   D2 used `min-height:calc((100vw - 460px) * 297/420)`, which read the WINDOW
   to size an element inside a column — a viewport minimum of exactly the kind
   §3 forbids, and wrong at every width where the rail or the drawer changes
   the column. `aspect-ratio` as a min-height needs no arithmetic at all. */
.dia-book.bk-spread{aspect-ratio:auto;height:auto;align-items:stretch}
/* The base, expressed as a shape rather than a number. It spans both columns,
   so its width IS the spread's width and its height is exactly the open Book's
   at the same size. Zero-width, behind the pages, invisible in every way
   except that the grid row cannot be shorter than it. */
.dia-book.bk-spread::before{content:'';grid-row:1;grid-column:1/-1;
  aspect-ratio:420/297;align-self:start;pointer-events:none;z-index:-1}
.dia-book.bk-spread > .dia-left{grid-row:1;grid-column:1}
.dia-book.bk-spread > .dia-right{grid-row:1;grid-column:2}
.dia-left,.dia-right{display:flex;flex-direction:column;min-height:0}
.dia-scroll{flex:1;min-height:0;overflow:visible;display:flex;flex-direction:column}
/* The left page's spare room sits BELOW the prompts, not inside the editor —
   so the writing region stays the size §1 asks for and the prompts stay where
   the eye can find them. */
.dia-left .dia-scroll::after{content:'';flex:1 1 auto;min-height:0}
/* THE INITIAL WRITING REGION (D2.3 §1).
   `flex:1 0 auto` let the ruled area swallow every spare pixel, so a blank day
   opened with roughly half a page of empty paper before the prompts appeared —
   "enough space to begin writing" had become "nothing else is visible".
   `flex:0 0 auto` makes the editor exactly as tall as its content, floored at
   the initial region below. The prompts then sit immediately beneath the
   writing and move down with it, one 30px ruled line at a time. */
.dia-editor{flex:0 0 auto}

.dia-sheet-head{flex:0 0 auto;padding-bottom:12px;margin-bottom:6px;
  border-bottom:1px solid rgba(255,255,255,.06)}
.dia-day{margin:0;font-size:10.5px;font-weight:600;letter-spacing:.22em;text-transform:uppercase;
  color:var(--paper-ink-2)}
.dia-date{margin:5px 0 0;font-family:'Playfair Display',Georgia,serif;font-weight:500;
  font-size:26px;line-height:1.15;color:var(--paper-ink)}
.dia-title{width:100%;margin-top:8px;background:none;border:0;outline:0;font-family:inherit;
  font-size:14px;color:var(--paper-ink);padding:3px 0;
  border-bottom:1px solid transparent;transition:border-color var(--t-hover)}
.dia-title::placeholder{color:var(--paper-ink-2);opacity:.55}
.dia-title:focus{border-bottom-color:var(--accent-c)}

/* RULED PAPER and the F2.1 BLOCK GRID, carried over exactly.
   height = (lead + lines) x 30px. Lead is padding, and a lead row is unruled. */
/* 180px = SIX ruled lines, on the 30px block grid, the floor of the 6–8 range
   D2.3 §1 asks for. D2.4 §7 put all five prompts back on the page, and six
   lines is what pays for the fifth: the writing region is still the first
   thing on the paper and still large enough to start in. It is a FLOOR — past
   six lines the editor grows and the prompts move down with it. */
.dia-editor{outline:0;min-height:180px;position:relative;
  color:var(--paper-ink);font-size:15px;line-height:30px;
  background-image:repeating-linear-gradient(to bottom,
    transparent 0,transparent 29px,var(--paper-line) 29px,var(--paper-line) 30px);
  background-attachment:local}
.dia-editor > *{margin:0;padding:0;line-height:30px}
.dia-editor p,.dia-editor li,.dia-editor blockquote p{margin:0;line-height:30px}
.dia-editor h2,.dia-editor h3{position:relative;margin:0;padding:30px 0 0;font-weight:500;
  font-family:'Playfair Display',Georgia,serif;color:var(--paper-ink)}
.dia-editor h2{font-size:23px}
.dia-editor h3{font-size:18px}
.dia-editor h2::before,.dia-editor h3::before{content:'';position:absolute;left:0;right:0;
  top:0;height:30px;background:var(--paper);pointer-events:none}
.dia-editor ul,.dia-editor ol{margin:0;padding:0 0 0 24px}
.dia-editor li{margin:0;padding:0}
.dia-editor li::marker{color:var(--paper-ink-2)}
.dia-editor blockquote{margin:0;padding:0 0 0 15px;border-left:2px solid var(--accent-c);
  color:var(--paper-ink-2);font-style:italic}
.dia-editor a{color:var(--accent-c);text-decoration:underline;text-underline-offset:2px}
.dia-editor .bk-unknown{border:1px dashed var(--border-strong);border-radius:8px;
  padding:0 12px;line-height:30px;color:var(--paper-ink-2);font-size:12.5px;
  background:rgba(255,255,255,.03);user-select:none}
/* A contenteditable is never :empty — it holds <p><br></p>. */
.dia-editor.is-empty::before{content:attr(data-placeholder);color:var(--paper-ink-2);
  opacity:.45;pointer-events:none;position:absolute;line-height:30px}
.dia-editor.is-found{animation:diaFound 1.4s ease-out}
@keyframes diaFound{0%,60%{background-color:rgba(var(--accent-rgb),.10)}100%{background-color:transparent}}

.dia-toolbar{flex:0 0 auto;display:flex;align-items:center;gap:3px;flex-wrap:wrap;
  margin:0 0 10px;padding:4px 5px;border-radius:9px;background:rgba(255,255,255,.03);
  border:1px solid rgba(255,255,255,.05)}
.dia-tb{width:27px;height:27px;border-radius:7px;border:0;background:none;cursor:pointer;
  color:var(--paper-ink-2);font-size:12.5px;display:grid;place-items:center;font-family:inherit;
  transition:background var(--t-hover),color var(--t-hover)}
.dia-tb:hover{background:rgba(255,255,255,.07);color:var(--paper-ink)}
.dia-tb.on{background:var(--accent-soft);color:#fff}
.dia-tb:focus-visible{outline:2px solid var(--accent);outline-offset:-1px}
.dia-tb-sep{width:1px;height:16px;background:rgba(255,255,255,.09);margin:0 3px}
.dia-tb-style{background:rgba(255,255,255,.06);border:0;border-radius:7px;color:var(--paper-ink);
  font-size:11.5px;font-family:inherit;padding:5px 7px;cursor:pointer;outline:0}

/* The guided prompts, BELOW the free writing. A page that opens with five
   questions is a questionnaire; these are for the days when the blank page is
   too big, and out of the way on the days it is not. */
/* D2.2 §5: three rest open, and each empty field rests at 40px rather than 46.
   Five empty prompts cost 411px — more than the free writing above them — and
   that, not the frame, is what put the spread far below the fold. */
.dia-prompts{margin-top:10px;padding-top:12px;border-top:1px dashed rgba(255,255,255,.09);
  flex:0 0 auto}
.dia-prompts-head{display:flex;align-items:baseline;gap:8px;margin-bottom:8px}
/* `.dia-prompts-more` went with the disclosure control in D2.4 §7. */
.dia-prompts-h{margin:0;font-size:11px;font-weight:700;letter-spacing:.1em;
  text-transform:uppercase;color:var(--paper-ink-2)}
.dia-prompts-n{margin-left:auto;font-size:10.5px;color:var(--paper-ink-2);opacity:.75;
  font-variant-numeric:tabular-nums}
/* A response AREA, not an underline pretending to be one.
   The old field was a thin rounded line: empty it read as decoration, and
   filled it became ordinary page prose with no boundary — you could not tell
   an answer from the paragraph above it. A modest inset surface says "this is
   somewhere to write" while empty and "this is an answer" once filled, and it
   grows with the text rather than scrolling inside two lines. */
.dia-prompt{display:block;margin-bottom:6px}
.dia-prompt-q{display:block;font-size:12px;color:var(--paper-ink-2);margin-bottom:4px;
  transition:color var(--t-hover)}
.dia-prompt.is-filled .dia-prompt-q{color:var(--accent-c)}
.dia-prompt-a{width:100%;display:block;min-height:36px;
  background:rgba(255,255,255,.035);
  border:1px solid rgba(255,255,255,.07);border-radius:8px;
  box-shadow:inset 0 1px 2px rgba(0,0,0,.12);
  outline:0;resize:none;overflow:hidden;
  font-family:inherit;font-size:14px;line-height:22px;color:var(--paper-ink);
  padding:8px 12px;
  transition:border-color var(--t-hover),background var(--t-hover),height var(--d-base) var(--e-out)}
.dia-prompt-a::placeholder{color:var(--paper-ink-2);opacity:.4}
.dia-prompt-a:hover{background:rgba(255,255,255,.05)}
.dia-prompt-a:focus{border-color:var(--accent-c);background:rgba(255,255,255,.055)}
.dia-prompt.is-filled .dia-prompt-a{background:rgba(255,255,255,.05);
  border-color:rgba(255,255,255,.10)}

/* The moment fields use the SAME grammar, one column, lighter than the main
   writing surface — the right page's answers and the left page's answers should
   not look like two different kinds of thing. */
/* `.dia-note-i` removed with the Moment tiles — see §3 above. */

/* ══ THE RIGHT PAGE: THE CHECK-IN ═══════════════════════════════════════
   D2.4 rebuilt this block. Three things it establishes, in order:
     1. semantic Diary colour tokens, one family per group (§2, §23)
     2. ONE control grid, shared by every row (§5)
     3. three explicit interaction states, never colour alone (§11)
   ═══════════════════════════════════════════════════════════════════════ */

/* ── 1. COLOUR (§2, §23) ────────────────────────────────────────────────
   Semantic tokens, not inline colours. Each group gets a family; the page
   itself stays dark paper, and the Book is not recoloured.

   Every one is a TINT of an existing Life OS accent, so nothing here invents
   a hue: lavender is `--a-lavender`, the Diary book's own accent; the energy
   blue-violet and the social teal-lavender are its cool neighbours. Selected
   text is always `#1A1624` on a light fill or `--paper-ink` on a dark one —
   never a mid-grey on a mid-tint. */
.dia-checkin{
  --ci-lav:#B69BF0;   /* Overall feeling — the Diary accent itself */
  --ci-blu:#8FA6F5;   /* Energy — blue-violet */
  --ci-tea:#7FC6D9;   /* Social battery — teal-lavender */
  --ci-neu:#A79ECB;   /* Day rhythm — neutral, differentiated only when chosen */
  /* The group's own colour. Every rule below reads this, so a group changes
     family by setting one variable rather than by having its own rules. */
  --ci:var(--ci-neu);
}
.dia-ci-group[data-group-id="feeling"]{--ci:var(--ci-lav)}
.dia-ci-group[data-group-id="energy"]{--ci:var(--ci-blu)}
.dia-ci-group[data-group-id="social"]{--ci:var(--ci-tea)}
.dia-ci-group[data-group-id="rhythm"]{--ci:var(--ci-neu)}

.dia-checkin{display:flex;flex-direction:column;gap:10px}
.dia-ci-head{border-bottom:1px solid rgba(255,255,255,.06);padding-bottom:8px}
.dia-ci-title{margin:0;font-family:'Playfair Display',Georgia,serif;font-weight:500;
  font-size:20px;line-height:1.2;color:var(--paper-ink)}
.dia-ci-sub{margin:3px 0 0;font-size:11.5px;color:var(--paper-ink-2)}
.dia-ci-label{font-size:10.5px;font-weight:700;letter-spacing:.1em;text-transform:uppercase;
  color:var(--paper-ink-2)}
/* The group's label takes its family once anything is chosen, so the page
   gains colour as it is filled in rather than arriving pre-decorated. */
.dia-ci-group:has(.dia-chip.on) .dia-ci-label{color:var(--ci)}

.dia-ci-group{padding:10px 12px 11px;border-radius:11px;
  background:rgba(255,255,255,.022);border:1px solid rgba(255,255,255,.05);
  display:flex;flex-direction:column;gap:8px;
  transition:background var(--t-hover),border-color var(--t-hover)}
/* A group holding an answer carries a hairline of its family down the side.
   Group identity (§2) without a coloured panel. */
.dia-ci-group{position:relative;overflow:hidden}
.dia-ci-group::before{content:'';position:absolute;left:0;top:0;bottom:0;width:2px;
  background:var(--ci);opacity:0;transition:opacity var(--t-hover)}
.dia-ci-group:has(.dia-chip.on)::before{opacity:.55}
.dia-ci-group:focus-within{background:rgba(255,255,255,.04);
  border-color:rgba(255,255,255,.10)}
.dia-ci-grouphead{display:flex;align-items:center;gap:8px;min-height:16px}
/* The reading, in words. Never removed: colour is never the only signal. */
.dia-ci-read{margin-left:auto;font-size:11.5px;color:var(--paper-ink-2);
  font-variant-numeric:tabular-nums}
.dia-ci-group:has(.dia-chip.on) .dia-ci-read{color:var(--paper-ink)}

/* ── 2. ONE CONTROL GRID (§5) ───────────────────────────────────────────
   `repeat(var(--opts), minmax(0,1fr))` — every option in a row is exactly the
   same width, and every row starts and ends at the same two x positions,
   because they are all the same grid over the same container.

   Before D2.4 the Social Battery's chips were flex-wrapped at intrinsic
   widths, so its five words came out 53 / 84 / 60 / 38px while Energy's were
   a uniform 53.9. §5 forbids fixing that with per-row margins, and this is
   why: one declaration is the fix for all of them. */
/* `auto-fit` with a stated minimum, not a fixed column count.
   At a 310px page all five fit on one line. At 1024px the right page is only
   220px wide, and five columns there gave 44px chips whose labels ALL
   ellipsised — §25 forbids squeezing options until the words stop being
   readable. `minmax(52px, 1fr)` fits as many as will hold their word and
   wraps the rest, still equal-width and still aligned, because every core row
   has the same option count and therefore the same column count. */
.dia-chips{display:grid;grid-template-columns:repeat(auto-fit,minmax(52px,1fr));
  gap:var(--ci-gap,4px);align-items:stretch}
/* 42px, not 44. At a 310px page the rhythm's option column is 183.4px wide;
   4 x 44 + 3 gaps is 188, so a 44px minimum fitted only three and wrapped the
   fourth — which cost the right page 130px and pushed the spread past the
   composer. 4 x 42 + 12 = 180 fits, and 42px still holds `Rested` without
   ellipsis. Below 264px of page width the container query stacks the label
   instead, so the options never get squeezed further than this. */
.dia-chips-rhythm{grid-template-columns:repeat(auto-fit,minmax(42px,1fr))}
/* The precise feelings are a TAG LIST, not a scale — they wrap, they are
   multi-select, and forcing them onto the scale grid would imply an order
   they do not have. */
.dia-chips-detail{display:flex;flex-wrap:wrap;gap:5px}

/* ── 3. THE CHIP, AND ITS THREE STATES (§11) ────────────────────────────
     DEFAULT   neutral surface, hairline border
     HOVER     lighter surface + brighter border + 1px lift. NO fill.
     SELECTED  the group's family as a fill, dark ink, a raised indicator dot
     FOCUS     a 2px accent ring, distinct from both

   A selected chip under hover must still read as selected, so `.on` sets its
   own background and the hover rule only lightens what is already there. */
.dia-chip{border:1px solid rgba(255,255,255,.10);background:rgba(255,255,255,.035);
  border-radius:10px;padding:5px 4px;font-family:inherit;font-size:10px;
  color:var(--paper-ink-2);cursor:pointer;min-width:0;
  display:flex;flex-direction:column;align-items:center;justify-content:center;gap:3px;
  transition:background var(--t-hover),color var(--t-hover),
    border-color var(--t-hover),transform var(--d-fast) var(--e-out),
    box-shadow var(--t-hover)}
.dia-chip-t{display:block;max-width:100%;overflow:hidden;text-overflow:ellipsis;
  white-space:nowrap;line-height:1.25}
/* The preview box is STATED, so every core chip is the same height whatever
   its indicator draws — a face, five bars or a battery. */
.dia-chip-i{display:flex;align-items:center;justify-content:center;height:17px;
  opacity:.5;transition:opacity var(--t-hover),transform var(--d-fast) var(--e-out)}

.dia-chip:hover{background:rgba(255,255,255,.085);color:var(--paper-ink);
  border-color:rgba(255,255,255,.2);transform:translateY(-1px)}
.dia-chip:hover .dia-chip-i{opacity:.8}
.dia-chip:active{transform:translateY(0) scale(.97)}
.dia-chip:focus-visible{outline:2px solid var(--accent);outline-offset:2px;
  border-color:transparent}

.dia-chip.on{background:var(--ci);border-color:var(--ci);color:#1A1624;
  font-weight:600;transform:none;
  box-shadow:0 2px 10px -4px var(--ci)}
.dia-chip.on .dia-chip-i{opacity:1}
/* Selected AND hovered: brighter, still unmistakably filled. */
.dia-chip.on:hover{background:var(--ci);filter:brightness(1.1);
  transform:translateY(-1px)}
/* The shape carries the state too, so selection survives greyscale. */
.dia-chip.on::after{content:'';position:absolute;left:50%;bottom:3px;width:12px;height:2px;
  border-radius:2px;background:#1A1624;opacity:.45;transform:translateX(-50%)}
.dia-chip{position:relative}
.dia-chips-detail .dia-chip{flex-direction:row;padding:5px 10px;border-radius:99px;
  font-size:11px}
.dia-chips-detail .dia-chip.on::after{display:none}

/* ── THE ICONS ──────────────────────────────────────────────────────────
   Every one is `currentColor`, so a selected chip's dark ink flows straight
   into its face, bars and battery — one rule instead of a selected variant
   per icon. */
.dia-face{display:block;flex:0 0 auto;color:currentColor}

/* THE ENERGY SIGNAL (§13). h(i) = MIN + (MAX-MIN) * i/(bars-1) — linear, so
   every step is the same number of pixels. The old `6px + i*2px` gave a 33%
   jump from bar 1 to 2 and 17% from 4 to 5, which read as a mistake at
   History's size. Equal width, equal gap, fixed box. */
.dia-meter{--m-min:5px; --m-max:15px; --m-w:3px; --m-gap:2px;
  display:inline-flex;align-items:flex-end;gap:var(--m-gap);height:var(--m-max)}
.dia-meter-seg{width:var(--m-w);border-radius:1.5px;background:currentColor;opacity:.22;
  height:calc(var(--m-min) + (var(--m-max) - var(--m-min)) * var(--n) / (var(--bars) - 1));
  transition:opacity var(--d-fast) var(--e-out)}
.dia-meter-seg.on{opacity:1}
/* Two sizes of the SAME component: the chip preview and the History cell. */
.dia-meter-opt{--m-min:4px; --m-max:14px; --m-w:2.5px; --m-gap:1.5px}
.dia-meter-xs{--m-min:3px; --m-max:9px; --m-w:2px; --m-gap:1.5px}

/* THE SOCIAL BATTERY (§14). One shell, one fill formula, one state mapping,
   at three sizes. Five states put the fill on exact quarters:
     Empty 0%   Running low 25%   Enough 50%   Good 75%   Full 100%   */
.dia-batt{--b-w:30px; --b-h:13px; --b-pad:2px;
  display:inline-flex;align-items:center;color:currentColor}
.dia-batt-body{position:relative;width:var(--b-w);height:var(--b-h);padding:var(--b-pad);
  overflow:hidden;box-sizing:border-box;border-radius:3.5px;
  border:1px solid currentColor;opacity:.9}
.dia-batt-fill{display:block;height:100%;border-radius:1.5px;background:currentColor;
  transition:width var(--d-base) var(--e-out)}
.dia-batt-tick{position:absolute;top:var(--b-pad);bottom:var(--b-pad);width:1px;
  background:var(--paper);opacity:.45;transform:translateX(-.5px)}
.dia-batt-cap{width:2px;height:calc(var(--b-h) * .46);border-radius:0 1.5px 1.5px 0;
  background:currentColor;opacity:.9}
.dia-batt-opt{--b-w:26px; --b-h:11px; --b-pad:1.5px}
.dia-batt-xs{--b-w:18px; --b-h:9px; --b-pad:1.5px}

/* THE DAY'S COLOUR, scoped to the check-in and no further. A wash across the
   whole page would tint the writing on the other side of the gutter, which is
   somebody's diary and not a mood indicator. Subtle enough that it can never
   be confused with a hover — §11 and §18 both depend on that. */
.dia-checkin[data-tone]{--tone:rgba(255,255,255,.04)}
.dia-checkin[data-tone="rough"],.dia-checkin[data-tone="low"]{--tone:rgba(224,138,122,.10)}
.dia-checkin[data-tone="steady"]{--tone:rgba(255,255,255,.05)}
.dia-checkin[data-tone="good"]{--tone:rgba(140,196,158,.10)}
.dia-checkin[data-tone="great"]{--tone:rgba(168,140,255,.12)}
.dia-checkin[data-tone] .dia-ci-group[data-group-id="feeling"]{
  background:var(--tone);border-color:rgba(255,255,255,.09)}

/* ── DAY RHYTHM (§8, §9, §10) ───────────────────────────────────────────
   ONE GRID FOR ALL FOUR ROWS.

   THE OVERLAP DEFECT: the label was `flex: 0 0 66px` with `overflow: visible`.
   NOURISHMENT needs 98px of content, so 24.3px of text painted on top of its
   first chip; MOVEMENT overflowed by 7.2px; the other two happened to fit. Not
   a Nourishment bug — a fixed column narrower than its widest content, and §9
   is explicit that it must not be fixed with a per-row margin.

   `.dia-rhythm` is now the grid and each row is `display: contents`, so all
   four rows share ONE `auto` label column sized to the widest label and ONE
   set of four equal option columns. The column cannot be narrower than its
   content, so the overlap is unreachable rather than merely absent. */
.dia-rhythm{display:grid;grid-template-columns:auto minmax(0,1fr);
  column-gap:9px;row-gap:7px;align-items:center}
/* The page is the constraint, not the window — a container query asks the
   right question. Below 260px the label cannot sit beside four readable
   options, so it goes above them and the options take the full width. §25
   permits exactly this: stack deliberately rather than squeeze. */
.dia-right{container-type:inline-size;container-name:diapage}
@container diapage (max-width: 264px){
  .dia-rhythm{grid-template-columns:minmax(0,1fr);row-gap:3px}
  .dia-rh-l{min-height:0;margin-top:5px}
  .dia-rh-row + .dia-rh-row .dia-rh-l{margin-top:9px}
  .dia-rhythm .dia-rh-l:not(:first-of-type),
  .dia-rhythm .dia-chips:not(:first-of-type){box-shadow:none}
}
.dia-rh-row{display:contents}
/* 9px, and the tracking with it. At 10px/.06em `NOURISHMENT` claimed a 102px
   column of a 310px page, leaving 39px per option and ellipsising `Rested`,
   `Barely` and `Plenty`. The label is a category marker beside its own glyph,
   not reading copy; the option words are the ones that must be legible. */
.dia-rh-l{display:flex;align-items:center;gap:5px;font-size:9px;font-weight:700;
  letter-spacing:.04em;text-transform:uppercase;color:var(--paper-ink-2);
  white-space:nowrap;min-height:28px}
.dia-rh-i{display:inline-flex;opacity:.6;flex:0 0 auto;
  transition:opacity var(--t-hover),color var(--t-hover)}
.dia-rh-t{display:block}
/* An answered row lights its own icon and label — the group is neutral by
   default and differentiates only where something was chosen (§2). */
.dia-rh-row:has(.dia-chip.on) .dia-rh-i{opacity:1;color:var(--ci)}
.dia-rh-row:has(.dia-chip.on) .dia-rh-l{color:var(--paper-ink)}
/* §10: more presence without more weight. Taller rows, a quiet divider
   between them, and the group gets room above and below. */
.dia-rh-row .dia-chip{min-height:28px;padding:5px 4px;font-size:10px}
.dia-ci-group[data-group-id="rhythm"]{padding-top:11px;padding-bottom:13px;gap:10px}
.dia-rhythm .dia-rh-l:not(:first-of-type){box-shadow:0 -1px 0 rgba(255,255,255,.05)}
.dia-rhythm .dia-chips:not(:first-of-type){box-shadow:0 -1px 0 rgba(255,255,255,.05)}

/* RIGHT-PAGE MOTION (D2.4 §24 = D2.3 §11, unchanged). The stated budget:
     hover                 140ms  (--t-hover)
     selection             140ms  (--t-hover)
     precise expansion     200ms  (--d-base)
     indicator movement    200ms  (--d-base)
   Nothing loops, bounces or celebrates. A selection should feel answered,
   not rewarded. Reduced motion is global and cannot break a layout, because
   nothing here owns a final state. */

/* The finer words, revealed by the broad one. Two levels, never twenty. */
.dia-detail{padding:11px 12px;border-radius:11px;background:rgba(255,255,255,.03);
  border:1px solid rgba(255,255,255,.06);animation:diaOpen var(--d-base) var(--e-out)}
.dia-detail-lead{margin:0 0 8px;font-size:11.5px;color:var(--paper-ink-2)}
@keyframes diaOpen{from{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:none}}

/* The streak: a fact, never a scoreboard. A diary that punishes the weeks you
   could not write is a diary you stop opening during the weeks that matter. */
.dia-streak{margin:6px 0 0;display:flex;align-items:baseline;gap:7px;font-size:12px;
  color:var(--paper-ink-2);padding-top:12px;border-top:1px solid rgba(255,255,255,.06)}
.dia-streak-n{font-family:'Playfair Display',Georgia,serif;font-size:22px;
  color:var(--accent-c);line-height:1}
.dia-streak-idle{min-height:38px;border-top:1px solid rgba(255,255,255,.06);margin-top:6px}

.dia-right-quiet{background:var(--paper-2)}
.dia-book-skel .bk-page{opacity:.45}
/* Ruled lines waiting for words. On the 30px grid, like everything else on
   this paper, so the real editor arrives exactly where the placeholder was. */
.dia-skel-lines{display:flex;flex-direction:column;gap:0}
.dia-skel-lines span{display:block;height:30px;position:relative}
.dia-skel-lines span::after{content:'';position:absolute;left:0;bottom:0;height:1px;
  right:calc(6% + var(--n,0%));background:var(--paper-line)}
.dia-skel-lines span:nth-child(3n+2)::after{right:22%}
.dia-skel-lines span:nth-child(4n+3)::after{right:38%}

.dia-foot{display:flex;align-items:center;gap:12px;font-size:11.5px;color:var(--muted)}
.dia-hint{flex:1;min-width:0}
.dia-save{min-width:96px;text-align:right;font-variant-numeric:tabular-nums}
.dia-save[data-state="saved"]{color:var(--ok)}
.dia-save[data-state="saving"]{color:var(--text-2)}
.dia-save[data-state="unsaved"]{color:var(--warn)}
.dia-save[data-state="failed"],.dia-save[data-state="conflict"]{color:var(--danger)}
.dia-archive{background:none;border:0;color:var(--muted);font-family:inherit;font-size:11.5px;
  cursor:pointer;padding:4px 8px;border-radius:7px;
  transition:background var(--t-hover),color var(--t-hover)}
.dia-archive:hover{background:var(--surface-2);color:var(--danger)}

.dia-arch-badge{margin:9px 0 0;display:inline-block;font-size:10px;font-weight:700;
  letter-spacing:.08em;text-transform:uppercase;color:var(--paper-ink-2);
  background:rgba(255,255,255,.06);border-radius:6px;padding:3px 9px}
.dia-arch-body{padding:20px 0 8px;display:flex;flex-direction:column;gap:10px;
  align-items:flex-start}
.dia-arch-lead{margin:0;font-size:15px;color:var(--paper-ink)}
.dia-arch-note{margin:0;font-size:13px;color:var(--paper-ink-2);line-height:1.6}

/* ── History ──────────────────────────────────────────────────────────── */
.dia-history{display:grid;grid-template-columns:minmax(0,1fr) 300px;gap:22px;
  align-items:start;width:100%}
.dia-hist-main{min-width:0;display:flex;flex-direction:column;gap:16px}
.dia-searchbar{display:flex;gap:8px;align-items:center}
.dia-search{flex:1;display:flex;align-items:center;gap:8px;background:var(--surface);
  border:1px solid var(--border);border-radius:10px;padding:0 12px;height:38px;color:var(--muted);
  transition:border-color var(--t-hover)}
.dia-search:focus-within{border-color:var(--accent)}
.dia-search input{flex:1;min-width:0;background:none;border:0;outline:0;color:var(--text);
  font-size:13px;font-family:inherit}
.dia-search input::placeholder{color:var(--muted)}

/* ── The month (D2.2 §13) ───────────────────────────────────────────────
   A COMPACT SIX-WEEK GRID THAT FITS. The cells were `aspect-ratio:1`, which on
   a 900px column makes each one ~120px tall and six rows ~740px — taller than
   the space above the composer before the header and the search bar are
   counted. A written day needs three short lines, not a square: the height is
   stated, and it is the same on every row so the grid cannot creep. */
.dia-cal{background:var(--surface);border-radius:16px;padding:16px 18px 12px;box-shadow:var(--e2)}
.dia-cal-head{display:flex;align-items:center;justify-content:space-between;gap:10px;
  margin-bottom:10px}
.dia-cal-title{margin:0;font-family:'Playfair Display',Georgia,serif;font-size:19px;
  font-weight:500;color:var(--text)}
.dia-grid{display:flex;flex-direction:column;gap:3px}
.dia-grid-row{display:grid;grid-template-columns:repeat(7,minmax(0,1fr));gap:3px}
.dia-grid-head span{text-align:center;font-size:10.5px;font-weight:700;letter-spacing:.06em;
  text-transform:uppercase;color:var(--muted);padding-bottom:3px}
.dia-day-cell{position:relative;height:80px;border:0;border-radius:9px;overflow:hidden;
  background:none;cursor:pointer;color:var(--text-2);font-family:inherit;font-size:13px;
  display:flex;flex-direction:column;align-items:stretch;justify-content:flex-start;gap:1px;
  padding:5px 6px;text-align:left;
  transition:background var(--t-hover),color var(--t-hover)}
.dia-day-cell:hover{background:var(--surface-2);color:var(--text)}
.dia-day-cell:focus-visible{outline:2px solid var(--accent);outline-offset:-2px}
.dia-day-cell.is-outside{color:var(--muted);opacity:.45}
.dia-day-n{font-size:12px;line-height:1.2;flex:0 0 auto}
.dia-day-cell.has-entry .dia-day-n{font-weight:700;color:var(--text)}

/* ── THE DAILY SNAPSHOT (D2.3 §12, rebuilt D2.4 §15–§18) ────────────────
   A written day should look richer than an empty one, and it should use the
   card it has. Three bands, in a stated order that never changes:

     top       the day number, and the three primary indicators opposite
     middle    the title, or the best line of context there is
     bottom    four passive marks, always in the same four positions

   §16 wants the primary three "sufficiently large to scan, small enough not
   to dominate": the face is 16px and the two meters are the `-xs` size of the
   SAME components the right page uses. Nothing here is redrawn. */
.dia-day-top{display:flex;align-items:center;gap:5px;flex:0 0 auto;min-width:0}
.dia-day-ind{display:flex;align-items:center;gap:5px;margin-left:auto;min-width:0;
  color:var(--text-2)}
.dia-day-ind .dia-face{opacity:.95}
/* A dimension this day did not answer holds its place rather than closing the
   gap, so the three indicators sit at the same x on every cell in the month
   and a column can be read straight down. */
.dia-day-gap{display:block;width:16px;height:9px}

/* §15: the context line is the middle band and gets the room. Two lines, and
   it truncates rather than pushing the row — the grid must never creep. */
.dia-day-prev{flex:1 1 auto;min-height:0;font-size:10px;line-height:1.32;
  color:var(--text-2);overflow:hidden;display:-webkit-box;-webkit-line-clamp:2;
  -webkit-box-orient:vertical;word-break:break-word}
.dia-day-cell.has-entry .dia-day-prev{color:var(--text)}

/* §17: four marks, one per passive dimension, in one stable order. The GLYPH
   says which dimension; its opacity says roughly how much. Exact values are
   in the tooltip and the accessible name, never written into the square.
   Subordinate to the primary three by size, weight and position. */
.dia-day-rh{display:flex;align-items:center;gap:6px;flex:0 0 auto;
  color:var(--muted);height:13px;margin-top:1px}
.dia-day-rh-i{display:inline-flex;opacity:calc(.3 + (.6 * var(--v) / 100))}
.dia-day-rh-i.is-unset{opacity:.12}
.dia-day-cell.is-open .dia-day-rh{color:#fff}

/* ══ THREE STATES, UNMISTAKABLY DIFFERENT (D2.4 §18) ════════════════════

     MOOD TINT   how the day FELT. Always present, always subtle, background
                 only. No border, no lift — it is ambience, not feedback.
     HOVER       my pointer is here. A visible outline and a lift. Temporary,
                 and it never changes the tint underneath.
     SELECTED    this is the open day. A persistent accent ring and fill.

   D2.3 used `filter: brightness(1.22)` for hover, which merely made the tint
   brighter — so "hovered Steady" and "resting Good" looked alike, which is
   exactly the confusion §18 exists to remove. Hover now adds something the
   tint never does: an outline and a shadow. */
.dia-day-cell[data-feel="rough"]{background:rgba(214,124,110,.16)}
.dia-day-cell[data-feel="low"]{background:rgba(214,142,124,.11)}
.dia-day-cell[data-feel="steady"]{background:var(--surface-2)}
.dia-day-cell[data-feel="good"]{background:rgba(122,190,146,.13)}
.dia-day-cell[data-feel="great"]{background:rgba(160,136,240,.15)}

/* HOVER — outline + lift, on any cell, tinted or not. */
.dia-day-cell:hover{box-shadow:inset 0 0 0 1.5px var(--border-strong),0 4px 12px -6px rgba(0,0,0,.5);
  transform:translateY(-1px)}
.dia-day-cell:focus-visible{outline:2px solid var(--accent);outline-offset:-2px;
  transform:translateY(-1px)}
/* TODAY — a quiet ring, so "today" is findable without being selected. */
.dia-day-cell.is-today{box-shadow:inset 0 0 0 1px var(--border-strong)}
.dia-day-cell.is-today:hover{box-shadow:inset 0 0 0 1.5px var(--border-strong),
  0 4px 12px -6px rgba(0,0,0,.5)}
/* SELECTED — the accent, filled and ringed, and it beats every tint. */
.dia-day-cell.is-open,.dia-day-cell[data-feel].is-open{background:var(--accent-soft);
  box-shadow:inset 0 0 0 2px var(--accent);color:#fff}
.dia-day-cell.is-open:hover{box-shadow:inset 0 0 0 2px var(--accent),
  0 4px 12px -6px rgba(0,0,0,.5)}
.dia-day-cell.is-open .dia-day-prev{color:#fff;opacity:.9}
.dia-day-cell.is-open .dia-day-ind{color:#fff}

.dia-cal-key{margin:8px 0 0;font-size:10.5px;line-height:1.4;color:var(--muted);
  overflow:hidden;display:-webkit-box;-webkit-line-clamp:1;-webkit-box-orient:vertical}

.dia-hist-side{background:var(--surface);border-radius:16px;padding:16px 18px;box-shadow:var(--e2)}
.dia-side-h{margin:0 0 10px;font-size:11px;font-weight:700;letter-spacing:.09em;
  text-transform:uppercase;color:var(--muted)}
.dia-side-empty{margin:0;font-size:12.5px;color:var(--muted);line-height:1.6}
.dia-recent{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:2px}
.dia-recent-row{display:flex;flex-direction:column;gap:2px;width:100%;text-align:left;
  background:none;border:0;border-radius:9px;padding:9px 10px;cursor:pointer;font-family:inherit;
  transition:background var(--t-hover)}
.dia-recent-row:hover{background:var(--surface-2)}
.dia-recent-row:focus-visible{outline:2px solid var(--accent);outline-offset:-2px}
.dia-recent-when{font-size:10.5px;font-weight:700;letter-spacing:.05em;text-transform:uppercase;
  color:var(--muted)}
.dia-recent-title{font-size:13px;color:var(--text-2);overflow:hidden;text-overflow:ellipsis;
  white-space:nowrap}
/* A day can be in this list on a check-in alone — feeling, energy, sleep — with
   nothing written anywhere. Saying so quietly beats an empty row that reads as
   a rendering fault, and beats "Untitled", which describes the label. */
.dia-recent-title.is-quiet{color:var(--muted);font-style:italic}
.dia-recent-mood{font-size:10.5px;color:var(--muted)}

.dia-results{background:var(--surface);border-radius:16px;padding:16px 18px;box-shadow:var(--e2)}
.dia-result-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:3px}
.dia-result{display:flex;flex-direction:column;gap:3px;width:100%;text-align:left;background:none;
  border:0;border-radius:10px;padding:10px 12px;cursor:pointer;font-family:inherit;
  transition:background var(--t-hover)}
.dia-result:hover{background:var(--surface-2)}
.dia-result:focus-visible{outline:2px solid var(--accent);outline-offset:-2px}
.dia-result-when{font-size:10.5px;font-weight:700;letter-spacing:.05em;text-transform:uppercase;
  color:var(--muted)}
.dia-result-title{font-size:13.5px;font-weight:600;color:var(--text)}
.dia-result-text{font-size:12.5px;color:var(--text-2);line-height:1.55;
  display:-webkit-box;-webkit-line-clamp:2;line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
.dia-empty{max-width:460px;margin:30px auto}

/* Motion — the spread shifts directionally, the frame never moves. No page
   curl: Diary is chronological, not an object you hold. */
/* THE DAY TURN (D2.3 §21). Two layers, not one element being cleared.
   The outgoing day is a detached clone stacked over the same grid cell, so the
   book frame, the gutter and the coloured edges never move — only the content
   crosses. `pointer-events:none` because it is already `inert`, and 260ms is
   the stated structural maximum. */
@keyframes diaLeaveNext{to{transform:translateX(-3%);opacity:0}}
@keyframes diaLeavePrev{to{transform:translateX(3%);opacity:0}}
@keyframes diaEnterNext{from{transform:translateX(3%);opacity:0}}
@keyframes diaEnterPrev{from{transform:translateX(-3%);opacity:0}}
.dia-ghost{position:absolute;z-index:2;pointer-events:none;
  animation-duration:var(--d-slow);animation-timing-function:var(--e-inout);
  animation-fill-mode:forwards}
.dia-ghost.leave-next{animation-name:diaLeaveNext}
.dia-ghost.leave-prev{animation-name:diaLeavePrev}
.dia-book.enter-next{animation:diaEnterNext var(--d-base) var(--e-out)}
.dia-book.enter-prev{animation:diaEnterPrev var(--d-base) var(--e-out)}
/* The stage is the positioning context for the ghost. */
.dia-stage{position:relative}

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

@media (max-width:1080px){
  .dia-history{grid-template-columns:minmax(0,1fr)}
}
@media (max-width:820px){
  /* One page at a time, the same treatment the Book gets: a 420:297 spread on
     a phone is two unreadable columns. The check-in becomes a section beneath
     the writing rather than a page beside it. */
  .dia-book.bk-spread{aspect-ratio:auto;grid-template-columns:minmax(0,1fr);
    max-width:560px;height:auto}
  /* Stacked, so the check-in is a section BENEATH the writing and there is no
     facing page to match heights with. The base shape goes with it: one column
     of a two-column ratio would demand a page half the height of the phone. */
  .dia-book.bk-spread::before{display:none}
  .dia-book.bk-spread > .dia-left{grid-row:1;grid-column:1}
  .dia-book.bk-spread > .dia-right{grid-row:2;grid-column:1}
  .dia-left,.dia-right{min-height:0}
  .dia-scroll{overflow:visible}
  .dia-right{border-top:1px dashed rgba(255,255,255,.10)}
  .dia-date{font-size:22px}
  .dia-step,.dia-today{height:44px}
  .dia-step{width:44px}
  /* On a phone a 64px cell showing two lines of 10px text is unreadable and
     the grid is 7 columns of ~44px. The context line goes; the day number, the
     weight and the tint stay, which is the same information at the density the
     screen can carry. Tapping still opens the day. */
  .dia-day-cell{height:auto;min-height:44px;align-items:center;justify-content:center;
    text-align:center;padding:4px}
  .dia-day-prev{display:none}
  .dia-day-ind{display:none}
  .dia-day-rh{display:none}
  .dia-day-cell.has-entry::after{content:'';width:4px;height:4px;border-radius:50%;
    background:var(--accent);display:block}
  .dia-jump-cell{min-height:38px}
  .dia-toolbar{position:sticky;top:0;z-index:2;background:var(--paper)}
}
@media (max-width:820px){
  /* Diary stacks rather than paginating, so the stage arrows have nothing to
     turn to and would only steal width from the writing. Day navigation is in
     the header, where it is labelled. */
  .dia-stage > .bk-arrow{display:none}
  .dia-stage{gap:0}
}
@media (max-width:480px){
  .dia-date{font-size:20px}
  .dia-daynav{flex:1;justify-content:center}
  .dia-jump{width:min(270px,calc(100vw - 60px))}
}


/* ── Library responsive (§27) ─────────────────────────────────────────── */
@media (max-width:1180px){
  .bk-tabs,.bk-foot,.bk-head{padding-left:0;padding-right:0}
}

/* ── The shelf, narrower (L3 §34/§35) ───────────────────────────────────
   The shelf does not collapse into a list. It stays a shelf and the objects
   get BIGGER, which is the opposite of what a squeezed desktop layout does and
   the reason a phone Library reads as designed rather than as left over. */

/* Tablet: fewer books across, and the depth effect simplified. A 7-degree
   rotation is a mouse-hover refinement; under a finger the prominent item is
   whatever you last touched, and turning it is noise. */
@media (max-width:1024px){
  .lib-book{--bw:124px}
  .lib-res{--rw:164px}
  .lib-shelves{gap:22px}
}

/* Phone: touch-sized covers, always-visible titles, and no hover anywhere in
   the path to opening something (§35/§36). The overflow menu stops hiding
   behind hover because there is no hover to reveal it with. */
@media (max-width:820px){
  /* Wider covers under a finger, and no overlap at any width since L3.2 -- a
     book you have to hit exactly is a book you miss. */
  .lib-book{--bw:132px;--spine-w:22px}
  .lib-res{--rw:158px}
  .lib-rail{padding:52px 0 calc(var(--shelf-drop) - var(--shelf-contact));scroll-padding-inline:16px}
  .lib-row{padding:0 16px;column-gap:14px}
  /* Always visible, because there is no hover to reveal it with (§36), and a
     44px hit area without a 44px button: the drawn control stays 30px so it
     does not take a third of a cover, and `::before` extends what a finger
     actually hits. A target measured by its ink is a target measured wrong. */
  /* TOUCH: one tap pulls forward, then a labelled Open (L3.1 S26, option A).
     Not a double tap - the second press lands on a control that says what it
     does, which is the whole reason the first tap does not feel dead. The
     control is bigger here because it is the primary route on a phone. */
  /* 44px tall, not 30. On a phone this is the PRIMARY route to opening
     anything, so it is sized as a real control rather than as a hint. */
  /* TOUCH (L3.2 S26). The footer action is the primary route on a phone, so
     it is a 44px target rather than a line of text -- but it is still part of
     the footer, under the object, and not a pill over the cover. */
  .lib-foot-a{min-height:44px;align-items:center;font-size:13px;margin-top:2px}
  .lib-obj:hover{transform:none}
  .lib-obj:hover::after{transform:none;opacity:1}

  .lib-obj-more{opacity:1;width:30px;height:30px}
  .lib-obj-more::before{content:'';position:absolute;top:50%;left:50%;
    width:44px;height:44px;transform:translate(-50%,-50%)}
  .lib-shelf-nav{display:none !important}
  .lib-bar{gap:10px}
  .lib-bar-right{margin-left:0;width:100%}
  .lib-search{flex:1}
  .lib-search input{width:100%}
  /* The rails run edge to edge, so the shelf reads as continuing past the
     screen rather than stopping at a margin — which is what tells you there is
     more to the right without an arrow saying so. -16px is `.main-wrap`'s own
     padding at this breakpoint, cancelled and then reapplied to the head and
     the row so only the SCROLLING part bleeds. */
  .lib-shelf{margin-inline:-16px}
  .lib-shelf-head{padding-inline:16px}
  .lib-found{padding-inline:16px}
}

/* Small phone: one column of shelves, larger touch targets, and the last shelf
   clears the global composer (§35). */
@media (max-width:480px){
  .lib-book{--bw:128px}
  .lib-res{--rw:150px}
  .lib-shelves{gap:18px;padding-bottom:calc(84px + env(safe-area-inset-bottom,0px))}
  .lib-open{grid-template-columns:minmax(0,1fr);gap:22px;justify-items:center}
  .lib-open-body{width:100%}
  .lib-obj:focus-visible{outline-offset:3px}
}
@media (max-width:1024px){
  .bk-stage{gap:6px}
  .bk-page{padding:22px 24px 14px 46px}
  .bk-page-right{padding:22px 46px 14px 24px}
  .bk-page::before{left:38px}
  .bk-page-right::before{left:auto;right:38px}
  .bk-cover-title{font-size:46px}
}
@media (max-width:820px){
  /* One page at a time. A 420:297 spread on a phone is two unreadable columns,
     so the spread becomes a single page and the arrows step page by page. */
  /* The Book reads one page at a time below 820px. Scoped away from Diary,
     which STACKS its two pages instead of paginating them — the check-in is
     not a page you turn to, it is the rest of the same day. Without the
     :not(), this rule hid the diary's entire right page on a phone. */
  .bk-book.bk-spread:not(.dia-book){aspect-ratio:210/297;
    grid-template-columns:minmax(0,1fr);max-width:520px}
  .bk-book.bk-spread:not(.dia-book) .bk-page-right{display:none}
  .bk-book.bk-spread.show-right:not(.dia-book) .bk-page-left{display:none}
  .bk-book.bk-spread.show-right:not(.dia-book) .bk-page-right{display:flex}
  .lib-bar-right{margin-left:0;width:100%}
  .lib-search{flex:1}
  .lib-search input{width:100%}
  .bk-arrow{width:44px;height:44px}
  .bk-search{position:fixed;left:12px;right:12px;top:70px;width:auto}
}
@media (max-width:480px){
  .bk-page{padding:18px 18px 12px 38px}
  .bk-page-right{padding:18px 38px 12px 18px}
  .bk-page::before{left:30px}
  .bk-page-right::before{left:auto;right:30px}
  .bk-cover-title{font-size:42px}
  .bk-cover{padding:32px 24px 24px}
  .lib-grid{grid-template-columns:minmax(0,1fr)}
  .bk-toolbar{width:100%;justify-content:center}

  /* On a phone the arrows sat IN the row and took 100px of a 321px column —
     a third of the screen, leaving a 221px page nobody can write on. They move
     over the outer edges of the page instead: the 44px target is kept, the
     page gets the full width, and the control stays where the thumb already is. */
  .bk-stage{position:relative;gap:0}
  .bk-arrow{position:absolute;top:50%;transform:translateY(-50%);z-index:3;
    background:rgba(20,18,32,.72);backdrop-filter:blur(6px);border-color:rgba(255,255,255,.1)}
  .bk-arrow:hover:not(:disabled){transform:translateY(-50%) scale(1.06)}
  #bk-prev{left:2px}
  #bk-next{right:2px}
  .bk-arrow:disabled{opacity:.22}
  /* The text keeps clear of what is now sitting on top of it. */
  .bk-page{padding-left:46px;padding-right:26px}
  .bk-page-right{padding-left:26px;padding-right:46px}
}

@media (prefers-reduced-motion:reduce){
  *{animation-duration:1ms!important;animation-iteration-count:1!important;
    transition-duration:1ms!important;scroll-behavior:auto!important}
}
/* ── Public landing page ──────────────────────────────────────────────────
   Read by two people who want the same thing: someone deciding whether Life
   OS is worth their evening, and a Google reviewer checking that the app says
   what it is and why it wants a calendar. Everything that matters is real
   text, in the served HTML, needing no JavaScript.

   The lockup uses the product's own lotus mark and Playfair wordmark, so the
   first thing a visitor sees is the same brand they meet after signing in. */
.lp{--gut:clamp(20px,5vw,44px);--rule:1px solid var(--hairline);
  max-width:1160px;margin:0 auto;padding:0 var(--gut) 0;position:relative}
.lp a{color:inherit}

/* One light source, behind the hero. Fixed and cheap; nothing animates. */
.lp::before{content:'';position:absolute;inset:-160px 0 auto;height:760px;z-index:-1;
  background:
    radial-gradient(46% 52% at 18% 6%,rgba(var(--accent-deep-rgb),.24),transparent 68%),
    radial-gradient(38% 44% at 82% 0%,rgba(0,217,163,.075),transparent 70%);
  pointer-events:none}

/* ── Header ───────────────────────────────────────────────────────────── */
.lp-nav{display:flex;align-items:center;justify-content:space-between;gap:20px;
  padding:22px 0 0}
.lp-brand{display:flex;align-items:center;gap:11px}
.lp-brand .logo-word{font-size:22px}
.lp-nav-links{display:flex;align-items:center;gap:22px}
.lp-nav-links a{font-size:13.5px;color:var(--muted);text-decoration:none;
  transition:color var(--t-hover)}
.lp-nav-links a:hover{color:var(--text-2)}
.lp-nav-cta{padding:9px 18px;font-size:13.5px}

/* ── Hero ─────────────────────────────────────────────────────────────────
   Two columns on desktop. The single-column version left the right half of
   every screen empty, which is most of what "too much empty space" meant. */
.lp-hero{display:grid;grid-template-columns:minmax(0,0.92fr) minmax(0,1.08fr);
  gap:clamp(28px,5vw,64px);align-items:center;padding:clamp(56px,8vh,104px) 0 0}
.lp-hero-copy{min-width:0}

/* The h1 is the brand name and nothing else — it is what Google's name check
   compares against — so the headline that does the selling sits below it. */
/* The wordmark class carries Playfair; this only sets its size here. Playfair
   belongs to the wordmark, the Book, the Diary and the shelf cover, and the
   brand name in a hero is the wordmark. */
.lp-hero h1{font-size:clamp(30px,3.4vw,40px);letter-spacing:-.01em;line-height:1;
  margin:0 0 18px}
.lp-display{font-size:clamp(31px,4.1vw,50px);line-height:1.08;letter-spacing:-.032em;
  font-weight:650;color:var(--text);margin:0 0 20px;max-width:15ch;
  text-wrap:balance}
.lp-lede{font-size:clamp(15px,1.5vw,17px);line-height:1.62;color:var(--text-2);
  margin:0 0 14px;max-width:44ch}
.lp-sub{font-size:14px;line-height:1.7;color:var(--muted);margin:0 0 30px;max-width:46ch}

.lp-cta{display:flex;flex-direction:column;align-items:flex-start;gap:12px}
.lp-cta-main{padding:14px 26px;font-size:15px;font-weight:600;border-radius:12px;
  box-shadow:0 12px 30px -12px rgba(var(--accent-deep-rgb),.75)}
.lp-cta-note{font-size:12.5px;color:var(--muted)}

/* ── The product preview ──────────────────────────────────────────────────
   Drawn, not photographed: three kilobytes, no image pipeline, and it cannot
   go stale the way a screenshot does the moment the UI moves. */
.lp-hero-shot{min-width:0}
.lp-shot{border:var(--rule);border-radius:14px;overflow:hidden;
  background:linear-gradient(180deg,#1D1A28,#181523);
  box-shadow:0 40px 80px -34px rgba(0,0,0,.9),0 0 0 1px rgba(255,255,255,.02);
  animation:lpRise .85s cubic-bezier(.16,1,.3,1) backwards}
@keyframes lpRise{from{opacity:0;transform:translateY(16px)}to{opacity:1;transform:none}}
@media (prefers-reduced-motion:reduce){.lp-shot{animation:none}}

.lp-shot-bar{display:flex;align-items:center;gap:6px;padding:11px 14px;
  border-bottom:var(--rule);background:rgba(255,255,255,.015)}
.lp-dot{width:8px;height:8px;border-radius:50%;background:var(--border-strong);flex:0 0 auto}
.lp-shot-url{margin-left:12px;font-size:10px;color:var(--muted);
  background:rgba(255,255,255,.04);border-radius:5px;padding:3px 9px}

.lp-shot-body{display:grid;grid-template-columns:118px minmax(0,1fr) 132px;min-height:290px}
.lp-side{border-right:var(--rule);padding:14px 12px;display:flex;flex-direction:column;gap:9px}
.lp-side-brand{display:flex;align-items:center;gap:7px;margin-bottom:7px}
.lp-side-brand span{display:block;height:8px;width:42px;border-radius:4px;
  background:rgba(255,255,255,.14)}
.lp-nav-item{display:block;height:8px;border-radius:4px;background:var(--surface-2);width:64%}
.lp-nav-item:nth-of-type(even){width:52%}
.lp-nav-item.is-on{background:color-mix(in srgb,var(--accent) 46%,transparent);width:78%}

.lp-main{padding:16px 17px;display:flex;flex-direction:column;gap:13px;min-width:0}
.lp-greet{font-size:14px;font-weight:600;color:var(--text)}
.lp-cols{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:9px}
.lp-col{display:flex;flex-direction:column;gap:6px}
.lp-cap{font-size:8px;font-weight:700;letter-spacing:.14em;text-transform:uppercase;
  color:var(--muted);margin-bottom:1px}
.lp-card{display:flex;align-items:center;gap:7px;height:29px;padding:0 8px;
  border-radius:7px;background:var(--surface-2);overflow:hidden}
.lp-card i{width:2px;height:15px;border-radius:2px;background:var(--c,var(--accent));flex:0 0 auto}
.lp-card span{height:6px;border-radius:3px;background:rgba(255,255,255,.13);width:100%}
.lp-card span.w80{width:80%}.lp-card span.w70{width:70%}.lp-card span.w60{width:60%}

.lp-rail{border-left:var(--rule);padding:14px 12px;display:flex;flex-direction:column;gap:11px}
.lp-rail-h{font-size:8px;font-weight:700;letter-spacing:.14em;text-transform:uppercase;
  color:var(--muted)}
.lp-habit{display:flex;align-items:center;gap:8px}
.lp-habit i{width:12px;height:12px;border-radius:50%;flex:0 0 auto;
  border:1.5px solid var(--border-strong)}
.lp-habit.is-done i{border-color:var(--ok);background:color-mix(in srgb,var(--ok) 32%,transparent)}
.lp-habit span{height:6px;border-radius:3px;background:var(--surface-2);width:100%}
.lp-habit span.w80{width:80%}.lp-habit span.w70{width:70%}.lp-habit span.w60{width:60%}

/* ── Sections ─────────────────────────────────────────────────────────────
   One rhythm: a rule, a heading with its own lede, then the content. The old
   page had headings and body text at nearly the same weight, which is what
   made the hierarchy feel flat. */
.lp-sec{padding:clamp(70px,9vw,116px) 0 0;border-top:var(--rule);margin-top:clamp(70px,9vw,112px)}
.lp-sec:first-of-type{border-top:0;margin-top:0}
.lp-sec-head{max-width:60ch;margin:0 0 clamp(30px,4vw,46px)}
.lp-sec h2{font-size:clamp(26px,3.2vw,36px);line-height:1.14;letter-spacing:-.028em;
  color:var(--text);margin:0 0 14px;font-weight:650;text-wrap:balance}
.lp-sec-lede{font-size:clamp(15px,1.6vw,17px);line-height:1.62;color:var(--text-2);
  margin:0;max-width:52ch}

.lp-prose{columns:2;column-gap:52px;max-width:96ch}
.lp-prose p{margin:0 0 15px;font-size:14.5px;line-height:1.78;color:var(--muted);
  break-inside:avoid}
.lp-prose p:last-child{margin-bottom:0}
.lp-prose b{color:var(--text-2);font-weight:600}
@media (max-width:820px){.lp-prose{columns:1}}

/* ── Capability tiles ─────────────────────────────────────────────────────
   A grid, but not seven identical boxes: Today leads at double width because
   it is the screen the app opens on, and each tile carries the accent colour
   that surface uses inside the product. */
.lp-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:14px;margin:0}
.lp-tile{position:relative;padding:22px 22px 24px;border:var(--rule);border-radius:14px;
  background:linear-gradient(180deg,rgba(255,255,255,.022),transparent 60%),var(--surface);
  transition:border-color var(--t-hover),transform var(--t-hover)}
.lp-tile:hover{border-color:color-mix(in srgb,var(--c) 42%,var(--hairline));
  transform:translateY(-2px)}
.lp-tile.is-wide{grid-column:span 2}
.lp-tile::before{content:'';position:absolute;top:22px;left:22px;width:22px;height:3px;
  border-radius:2px;background:var(--c)}
.lp-tile dt{margin:16px 0 8px;font-size:17px;font-weight:650;color:var(--text);
  letter-spacing:-.012em}
.lp-tile dd{margin:0;font-size:13.5px;line-height:1.68;color:var(--muted)}
@media (max-width:900px){
  .lp-grid{grid-template-columns:repeat(2,minmax(0,1fr))}
  .lp-tile.is-wide{grid-column:span 2}
}
@media (max-width:600px){
  .lp-grid{grid-template-columns:1fr}
  .lp-tile.is-wide{grid-column:span 1}
}

/* ── Calendar permissions ─────────────────────────────────────────────── */
.lp-panels{display:grid;grid-template-columns:minmax(0,1.35fr) minmax(0,1fr);gap:14px}
.lp-panel{padding:26px 28px;border:var(--rule);border-radius:14px;background:var(--surface)}
.lp-panel-quiet{background:transparent}
.lp-panel h3{margin:0 0 18px;font-size:12px;font-weight:700;letter-spacing:.12em;
  text-transform:uppercase;color:var(--muted)}
.lp-perm{margin:0;padding:0;list-style:none}
.lp-perm li{padding:16px 0;border-top:var(--rule);font-size:13.5px;line-height:1.68;
  color:var(--muted)}
.lp-perm li:first-child{border-top:0;padding-top:0}
.lp-perm b{display:block;color:var(--text);font-weight:600;font-size:15px;
  margin-bottom:5px;letter-spacing:-.008em}
.lp-never{margin:0 0 20px;padding:0;list-style:none}
.lp-never li{position:relative;padding:0 0 11px 20px;font-size:13.5px;line-height:1.6;
  color:var(--muted)}
.lp-never li::before{content:'';position:absolute;left:0;top:8px;width:9px;height:1.5px;
  border-radius:1px;background:var(--border-strong)}
.lp-note{margin:0;padding-top:18px;border-top:var(--rule);font-size:12.5px;
  line-height:1.65;color:var(--muted)}
.lp-note a{color:var(--accent);text-decoration:none}
.lp-note a:hover{text-decoration:underline}
@media (max-width:900px){.lp-panels{grid-template-columns:1fr}}

/* ── Closing call to action ───────────────────────────────────────────── */
.lp-close{margin-top:clamp(70px,9vw,112px);padding:clamp(52px,7vw,80px) var(--gut);
  border:var(--rule);border-radius:18px;text-align:center;
  background:radial-gradient(70% 130% at 50% 0%,rgba(var(--accent-deep-rgb),.15),transparent 68%),
    var(--surface)}
.lp-close h2{font-size:clamp(26px,3.2vw,36px);line-height:1.14;letter-spacing:-.028em;
  color:var(--text);margin:0 0 12px;font-weight:650}
.lp-close p{margin:0 auto 26px;font-size:15px;line-height:1.65;color:var(--muted);max-width:44ch}

/* ── Footer ───────────────────────────────────────────────────────────── */
.lp-foot{display:flex;align-items:center;gap:18px 28px;flex-wrap:wrap;
  margin-top:clamp(52px,6vw,76px);padding:28px 0 64px;border-top:var(--rule)}
.lp-foot-brand{display:flex;align-items:center;gap:9px}
.lp-foot-brand .logo-word{font-size:16px;color:var(--text-2)}
.lp-foot-by{margin:0;font-size:12.5px;color:var(--muted)}
.lp-foot-links{display:flex;gap:24px;flex-wrap:wrap;margin-left:auto}
.lp-foot-links a{font-size:12.5px;color:var(--muted);text-decoration:none;
  transition:color var(--t-hover)}
.lp-foot-links a:hover{color:var(--accent)}

/* ── Narrow ───────────────────────────────────────────────────────────── */
@media (max-width:960px){
  .lp-hero{grid-template-columns:1fr;gap:40px;padding-top:44px}
  .lp-display{max-width:20ch}
  .lp-lede,.lp-sub{max-width:58ch}
}
@media (max-width:760px){
  .lp-shot-body{grid-template-columns:1fr}
  .lp-side,.lp-rail{display:none}
  .lp-nav-links a{display:none}
  .lp-foot-links{margin-left:0;width:100%;gap:18px}
}

html[data-motion="reduced"] *{animation-duration:1ms!important;transition-duration:1ms!important}

/* ── Shelf spacing on touch ───────────────────────────────────────────────
   Last in the file deliberately. `.lib-rail.is-dense .lib-row{column-gap:0}`
   is both later and more specific than the mobile block, so setting the
   tuning token there had no effect on a dense shelf — which is most of them.

   Books touching is right with a mouse and wrong with a thumb: a 30px spine
   with no air beside it means a slightly-off tap opens the neighbour. Eight
   pixels is inside the documented 0-14px range and leaves the resting pose,
   lean and depth untouched. */
@media (hover:none),(pointer:coarse),(max-width:768px){
  .lib-rail .lib-row,.lib-rail.is-dense .lib-row{column-gap:8px}
}

/* ── Calendar sheets on a phone ───────────────────────────────────────────
   `.modal.ev-modal,.modal-reminder,.modal-schedule{width:min(544px,100vw-32px)}`
   was added for desktop during the Calendar pass, and it sits AFTER the
   mobile bottom-sheet rule. At 390px it computed 358px against `left:0`, so
   the sheet stopped 32px short of the right edge with a strip of the page
   showing beside it.

   Also dvh rather than vh: on a phone, vh is the tallest the viewport ever
   gets, so a 92vh sheet is taller than the screen while the browser chrome
   is showing. */
@media (max-width:768px){
  .modal.ev-modal,.modal-reminder,.modal-schedule{
    left:0;right:0;bottom:0;top:auto;transform:none;
    width:100%;max-width:100%;max-height:92dvh;
    border-radius:20px 20px 0 0}
  .modal.ev-modal .m-body,.modal-reminder .m-body,.modal-schedule .m-body{
    max-height:none;padding-bottom:calc(16px + env(safe-area-inset-bottom))}
  .modal.ev-modal .m-foot,.modal-reminder .m-foot,.modal-schedule .m-foot{
    padding-bottom:calc(14px + env(safe-area-inset-bottom))}
}

/* ── Text entry on touch ──────────────────────────────────────────────────
   iOS Safari zooms the whole page when a field with font-size under 16px
   takes focus, and then leaves it zoomed — which is the "the keyboard opens
   and everything jumps" complaint. The only accessible cure is 16px; the
   alternative, maximum-scale=1 on the viewport, disables pinch zoom for
   everyone and is worse.

   The task editor's fields were 13-13.5px, so every task title, step and note
   on a phone triggered it. */
@media (hover:none),(pointer:coarse),(max-width:768px){
  input,textarea,select,
  .m-input,.m-title,.input,.ta,.cf-input,.set-item-name,.cf-title input,.cf-title textarea,
  /* Nested rules outrank a bare class inside a media query — media queries add
     no specificity — so anything that sets its own smaller size is named. */
  .m-step-add .m-input,.st-list .m-input,.bk-page input,.bk-page textarea{
    font-size:16px}
  /* Fields grow to a comfortable target along with the type. */
  .m-input,.input,.cf-ctl,.set-item-name{min-height:44px}
  .m-input.m-textarea,textarea.m-input,.ta{min-height:96px}
}

/* ── Pinboard on touch ────────────────────────────────────────────────────
   Three things stacked up to make a pin immovable on a phone.

   1. The drag handles called preventDefault() on POINTERDOWN, which does not
      stop a touch from scrolling — only `touch-action` does. The browser won
      the gesture, fired pointercancel, and the drag died mid-move. There was
      no touch-action anywhere in this stylesheet.
   2. The grip's own marking is drawn on `.bk-pin:hover`, so on a device with
      no hover it was invisible: the handle existed and nothing showed where.
   3. It is a 16px strip.

   The handles claim the gesture; the pin body deliberately does not, so the
   board can still be scrolled by starting a drag anywhere else. */
@media (hover:none),(pointer:coarse),(max-width:768px){
  /* Footer and header links on the public page are text, and text is small. */
  .lp-foot-links a,.lp-nav-links a,.lp-note a{display:inline-flex;align-items:center;min-height:44px}

  .bk-pin-grip,[data-pin-resize],[data-pin-connect]{touch-action:none}
  .bk-pin-grip{height:28px}
  .bk-pin-grip::before{opacity:.9;top:12px}
  [data-pin-resize],[data-pin-connect]{min-width:32px;min-height:32px}
}

/* ── The task row at laptop widths ────────────────────────────────────────
   Not a mobile rule, but the same defect and the same cause.

   `.t-actions` is invisible until hover — opacity:0, which hides paint and
   still reserves layout. Three controls and a grip reserve 118px of every row
   forever. With three buckets across and the rail beside them, a 1440px
   window leaves each row about 143px, so the grid resolves to
   `20px 5px 118px` and the title gets FIVE pixels. Measured, not guessed.

   The band runs from where the buckets go three-across (1025px) to about
   1700px, which is most laptops. Above it there is room and nothing changes;
   below it the buckets are already single-column and rows are wide.

   Same answer as the phone: the controls keep their size and move to their
   own line, so the title always has the full width of the row. */
@media (min-width:1025px) and (max-width:1700px){
  .t-row{grid-template-columns:auto minmax(0,1fr);row-gap:6px}
  .t-actions{grid-column:2;justify-content:flex-start}
}

/* ── Which page a reload lands on ─────────────────────────────────────────
   Paired with the inline script in index.html. A returning visitor gets the
   boot state on the FIRST paint and the landing page never appears; a new
   visitor gets the landing page and never sees a spinner. One transition
   either way, decided before anything is drawn. */
/* ── The wait before the app paints ───────────────────────────────────
   A 17px spinner said "something is loading" and nothing else. The mark
   breathing says the same thing in the app's own voice, and it is the
   right length of statement for a wait that is usually under a second.

   The aura is a pseudo-element BEHIND the mark, so the glow swells without
   the lotus swelling with it — the light moves, the object does not. */
/* The full height, so the mark is in the MIDDLE of the screen. 60vh centred
   it in the top three-fifths, which put it a third of the way down. */
.boot-wait{display:none;place-items:center;min-height:100dvh}
html.los-returning .boot-wait,.boot-wait.is-on{display:grid}
.boot-mark{position:relative;display:grid;place-items:center;width:96px;height:96px}
.boot-mark svg{position:relative;z-index:1;width:46px;height:46px;
  animation:bootBreath 2.6s var(--e-inout) infinite}
.boot-mark::before{content:'';position:absolute;inset:0;border-radius:50%;
  background:radial-gradient(circle,
    rgba(var(--accent-rgb),.40) 0%, rgba(130,84,255,.16) 42%, rgba(var(--accent-deep-rgb),0) 70%);
  animation:bootAura 2.6s var(--e-inout) infinite}
@keyframes bootAura{
  0%,100%{opacity:.34;transform:scale(.82)}
  50%    {opacity:1;  transform:scale(1.14)}}
@keyframes bootBreath{
  0%,100%{opacity:.82}
  50%    {opacity:1}}
/* A wait is not a place to insist on movement. */
@media (prefers-reduced-motion:reduce){
  .boot-mark svg,.boot-mark::before{animation:none}
  .boot-mark::before{opacity:.62}}
html.los-returning #landing{display:none}

/* ══════════════════════════════════════════════════════════════════════
   RELATED — the semantic relationship layer, wherever an object is open

   Secondary by construction. Nobody opens a task to read its graph, so this
   sits at the foot of a surface in label type, and renders nothing at all
   when there is nothing to show: an empty "Related" heading on every object
   in the application would be a standing advertisement for a feature rather
   than a useful fact.
   ══════════════════════════════════════════════════════════════════════ */
.rel{margin-top:18px;padding-top:14px;border-top:1px solid var(--hairline)}
.rel-h{display:flex;align-items:center;gap:8px;margin:0 0 8px;
  font-size:10.5px;font-weight:700;letter-spacing:.09em;text-transform:uppercase;
  color:var(--muted)}
.rel-n{display:grid;place-items:center;min-width:17px;height:17px;padding:0 5px;
  border-radius:9px;background:var(--surface-2);color:var(--text-2);
  font-size:10px;letter-spacing:0}
.rel-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:2px}
.rel-row{display:flex;align-items:center;gap:4px;border-radius:9px;
  transition:background var(--t-hover)}
.rel-row:hover{background:rgba(255,255,255,.035)}
.rel-go{flex:1;min-width:0;display:flex;align-items:center;gap:10px;
  padding:7px 8px;text-align:left;border-radius:9px;color:inherit}
/* The type, not an icon. "Book page" and "Calendar" are two words that need no
   legend; a glyph for each of ten types is a legend nobody reads. */
.rel-kind{flex:0 0 auto;font-size:9.5px;font-weight:700;letter-spacing:.06em;
  text-transform:uppercase;color:var(--muted);min-width:62px}
.rel-body{flex:1;min-width:0;display:flex;flex-direction:column;gap:1px}
.rel-title{font-size:12.5px;color:var(--text-2);overflow:hidden;
  text-overflow:ellipsis;white-space:nowrap}
.rel-row:hover .rel-title{color:var(--text)}
.rel-sub{font-size:10.5px;color:var(--muted);overflow:hidden;
  text-overflow:ellipsis;white-space:nowrap}
/* How they are related, at the quiet end of the row: it qualifies the link
   rather than naming the thing, so it must never outrank the title. */
.rel-rel{flex:0 0 auto;font-size:10.5px;color:var(--muted);
  max-width:38%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.rel-x{flex:0 0 auto;width:26px;height:26px;border-radius:7px;color:transparent;
  font-size:16px;line-height:1;display:grid;place-items:center;
  transition:background var(--t-hover),color var(--t-hover)}
.rel-row:hover .rel-x,.rel-x:focus-visible{color:var(--muted)}
.rel-x:hover{background:rgba(255,100,110,.12);color:var(--danger)}
/* A coupled edge — today only `scheduled_as`. It is shown and marked, and it
   has no unlink: the way to undo "this task is scheduled as that event" is to
   unschedule it, not to cut a wire and leave two half-orphaned records. */
.rel-row.is-coupled .rel-rel{color:var(--accent)}
.rel-lock{flex:0 0 auto;width:26px;height:26px;display:grid;place-items:center}
.rel-lock::after{content:'';width:5px;height:5px;border-radius:50%;
  background:var(--accent);opacity:.55}

/* The dense form: a count, where a list would be noise. */
.rel-badge{display:inline-grid;place-items:center;min-width:15px;height:15px;
  padding:0 4px;border-radius:8px;background:var(--surface-2);
  color:var(--muted);font-size:9.5px;font-weight:700;
  font-variant-numeric:tabular-nums}

/* ── Making a link ────────────────────────────────────────────────────
   Search first, then say what the connection means. The other order — pick a
   type, pick a kind, then search inside it — asks two questions before the
   one the person came to answer. */
.rel-add{margin-left:auto;font-size:10.5px;font-weight:600;letter-spacing:.02em;
  text-transform:none;color:var(--accent);padding:2px 4px;border-radius:6px}
.rel-add:hover{background:var(--accent-soft)}
.rel-add-only{margin:0;font-size:11.5px}
.rel-empty{border-top:1px solid var(--hairline);padding-top:12px}
.rel-pick{max-width:520px;width:calc(100vw - 40px)}
/* `flex:1`, like every other modal's title.
 *
 * `.m-head` is a flex row and `.m-title` grows, which is what pushes the close
 * button to the right edge everywhere else. This heading is a plain <h2>, so it
 * was only as wide as the words "Link to" — leaving the × parked 12px after
 * them with 410px of empty header to its right. Measured, not guessed. */
.rel-pick-h{margin:0;flex:1;min-width:0;font-size:15px;font-weight:600}
.rel-pick-body{display:flex;flex-direction:column;gap:11px;min-height:220px}
.rel-pick-q{width:100%}
.rel-pick-kind{display:flex;align-items:center;gap:10px;font-size:12px;color:var(--muted)}
.rel-pick-kind select{flex:1;min-width:0}
.rel-pick-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;
  gap:2px;max-height:46vh;overflow-y:auto}
.rel-pick-row{width:100%;display:flex;align-items:center;gap:10px;padding:8px;
  border-radius:9px;text-align:left;color:inherit;transition:background var(--t-hover)}
.rel-pick-row:hover{background:rgba(255,255,255,.05)}
.rel-pick-hint{margin:0;font-size:11.5px;color:var(--muted);min-height:15px}

/* ── Pick a place, then the thing ───────────────────────────────────────
 *
 * The picker was a search box and nothing else, which is fine when you know
 * the name and useless when you do not. "Where is it?" is a question anybody
 * can answer about their own app; "which words are in its title" is not. */
.rel-places-h{margin:0 0 8px;font-size:11px;font-weight:600;letter-spacing:.05em;
  text-transform:uppercase;color:var(--muted)}
.rel-places-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:7px}
.rel-place{display:flex;flex-direction:column;gap:2px;text-align:left;padding:9px 11px;
  border-radius:10px;background:var(--surface-2);
  box-shadow:inset 0 0 0 1px var(--hairline);
  transition:background var(--t-hover),box-shadow var(--t-hover)}
.rel-place:hover{background:var(--surface-3);box-shadow:inset 0 0 0 1px var(--accent-soft, var(--hairline))}
.rel-place-l{font-size:13px;font-weight:600;color:var(--text)}
.rel-place-s{font-size:11px;color:var(--muted)}

/* Where you are, and the way back out. A book's pages are two levels down and
   there is no other way to know which book you are inside. */
.rel-crumbs{display:flex;align-items:center;gap:6px;font-size:12px;color:var(--muted);
  min-height:24px}
.rel-crumb-back{width:22px;height:22px;border-radius:6px;display:grid;place-items:center;
  font-size:16px;line-height:1;color:var(--text-2);flex-shrink:0;
  transition:background var(--t-hover)}
.rel-crumb-back:hover{background:rgba(255,255,255,.07)}
.rel-crumb{cursor:pointer;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.rel-crumb.is-here{color:var(--text-2);cursor:default}
.rel-crumb-sep{opacity:.5}

.rel-pick-li{display:flex;align-items:stretch;gap:4px}
.rel-pick-li .rel-pick-row{flex:1;min-width:0}
.rel-pick-row.is-chosen{background:rgba(139,127,245,.16);
  box-shadow:inset 0 0 0 1px var(--accent)}
/* A Book has an inside, and "link me to this book" and "show me its pages" are
   different answers — so they are different controls. */
.rel-pick-into{width:30px;flex-shrink:0;border-radius:8px;font-size:17px;line-height:1;
  color:var(--muted);display:grid;place-items:center;
  transition:background var(--t-hover),color var(--t-hover)}
.rel-pick-into:hover{background:rgba(255,255,255,.07);color:var(--text)}

/* ── What will happen, said before it happens ───────────────────────────
 *
 * The old picker linked the instant you clicked a row, with whatever kind
 * happened to be in the select. This says what it is about to do to this
 * particular pair — including, where there is a real relationship, that it is
 * going to add the task to the project rather than note that they are
 * "related", which is what somebody meant and did not get. */
.rel-pick-foot{display:flex;align-items:center;gap:12px;flex-wrap:wrap;
  padding:12px 20px;border-top:1px solid var(--hairline)}
.rel-foot-what{flex:1;min-width:0;display:flex;flex-direction:column;gap:1px}
.rel-foot-verb{font-size:13px;font-weight:600;color:var(--text)}
.rel-foot-note{font-size:11px;color:var(--muted)}
.rel-foot-kind span{font-size:12px;color:var(--muted)}
.rel-foot-controls{display:flex;align-items:center;gap:9px;flex-shrink:0}
.rel-foot-alt{font-size:11.5px}
.rel-foot-select{max-width:170px}
