/* Dust design system. The look Steven approved on 04.10.2026 ("It looks wonderful, I love it ... I approve this,
   proceed"), ported from the round-3 prototype (control/v2/dust-control.html). DESIGN.md at the worktree root has a
   snippet for every class. Do not redesign here: change the prototype's look only with Steven.
   The look: one phone column over a full-bleed world plate per page. Matte smoked-glass tiles (a dark tint, the
   room crisp behind it, no blur, a 1px hairline). Empty slots carry the fine 135 degree hatch; a hover pointer lifts
   it onto any glass key. Text set straight on the world carries its own soft dark pool. The off-white block is the
   one current thing in each group. Yellow is the objective and now, red is urgency, teal is done (as text, outline
   or bar, never a filled tile). Sentence case everywhere. Archivo for the interface, Newsreader for paper only.
   One dark world on purpose, Ask's astral plane at dusk included (its own top bar, see ask.css).
   Sections: fonts, tokens, feel kit, base, world, material, type roles, HUD, app and screens, dock, keys and trays,
   popups and toasts, fields and choices, switches and chips, subtitles and lines, rows and lists, paper, Today,
   Plan, Post, Ask, More, Talk, states, motion. The cascade order is the prototype's: keep new rules in their section. */

/* ---------- Fonts: self-hosted variable fonts, OFL (fonts/OFL-*.txt). Archivo is the interface, its latin and latin-ext
   subsets with the weight and width axes (a grotesk of Akzidenz's family, Control's document face; its narrower width
   is Control's objective voice). Manrope and Public Sans are the faces Settings can switch to (html[data-face]) ---------- */
@font-face { font-family: "Archivo"; font-style: normal; font-weight: 100 900; font-stretch: 62% 125%; font-display: swap; src: url("fonts/archivo-latin.woff2") format("woff2"); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }
@font-face { font-family: "Archivo"; font-style: normal; font-weight: 100 900; font-stretch: 62% 125%; font-display: swap; src: url("fonts/archivo-latin-ext.woff2") format("woff2"); unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; }
@font-face { font-family: "Public Sans"; font-style: normal; font-weight: 100 900; font-display: swap; src: url("fonts/public-sans-latin.woff2") format("woff2"); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }
@font-face { font-family: "Public Sans"; font-style: normal; font-weight: 100 900; font-display: swap; src: url("fonts/public-sans-latin-ext.woff2") format("woff2"); unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; }
@font-face { font-family: "Manrope"; font-style: normal; font-weight: 200 800; font-display: swap; src: url("fonts/manrope-latin.woff2") format("woff2"); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }
@font-face { font-family: "Newsreader"; font-style: normal; font-weight: 200 800; font-display: swap; src: url("fonts/newsreader-latin.woff2") format("woff2"); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }
@font-face { font-family: "Newsreader"; font-style: italic; font-weight: 200 800; font-display: swap; src: url("fonts/newsreader-italic-latin.woff2") format("woff2"); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }

/* ---------- Tokens ---------- */
@property --lift { syntax: "<number>"; inherits: false; initial-value: 0; }
:root {
  color-scheme: dark;
  /* Colour */
  --world: #050609;
  --glass: rgb(16 17 19 / 0.84);
  --glass-deep: rgb(14 15 17 / 0.9);
  --glass-dim: rgb(12 13 15 / 0.72);
  --band: rgb(7 8 10 / 0.9); /* 0.9: red speaker names keep 4.5:1 over the brightest plate */
  --line: rgb(214 218 222 / 0.22);
  --line-hi: rgb(214 218 222 / 0.4);
  --line-hover: rgb(214 218 222 / 0.62);
  --ink: #EDEDEA;
  --ink-hi: #FFFFFF;
  /* Readability pass 06.10 (Steven: "some text is a bit hard to read"): the greys went one step lighter. Measured
     over the real plates at 393x852, the worst small line is now 6.1:1 (was 5.1:1); muted lines 7.6:1 (was 6.2:1) */
  --muted: #BCBDB9;
  --dim: #A9ABA8;
  --off-ink: #8E8F8C; /* disabled: still readable, clearly out of play */
  --meta-ink: #E1E1DE;
  --faint: rgb(237 237 234 / 0.18);
  --paper: #E4E4E1;
  --paper-hi: #F2F2EF;
  --on-paper: #151515;
  --paper-muted: #4A4A48; /* 6.9:1 on the paper */
  --ink-hit: #121212;
  --red: #FF4A4A; /* text only (marks use --red-mark): light enough for 4.5:1 on glass over the brightest part of every plate */
  --red-mark: #E7000D;
  --yellow: #FEDB0D;
  --teal: #00C7A4;
  --teal-paper: #006A56; /* teal ink on the off-white paper, 5.2:1 */
  --chip: rgb(40 41 43 / 0.95);
  --chip-ink: #A3A3A0;
  --empty-tint: rgb(9 10 12 / 0.55); /* the most see-through surface; 0.55 keeps its words 4.5:1 over the brightest plate spot */
  --dim-world: rgb(2 3 6 / 0.72);
  --halo: 0 1px 2px rgb(0 0 0 / 0.55);
  --ring: var(--paper);
  --ring-halo: rgb(4 5 9 / 0.92); /* under the ring, so it reads over an off-white neighbour too */
  /* Hatch: the fine 135 degree stripe, only on empty or locked slots. Flat is the still stripe; sweep is loading (one
     period, 6px across the stripes, is 8.49px along x per loop) */
  --hatch-flat-img: repeating-linear-gradient(135deg, rgb(255 255 255 / 0.05) 0 1px, transparent 1px 3.5px);
  --sweep-img: repeating-linear-gradient(135deg, rgb(237 237 234 / 0.24) 0 1.5px, transparent 1.5px 6px);
  /* Pool: text protection that travels with the text, dense where the words are, gone at its edges */
  --pool: radial-gradient(ellipse farthest-side at 30% 50%, rgb(4 5 9 / 0.84), rgb(4 5 9 / 0.8) 50%, rgb(4 5 9 / 0.45) 78%, rgb(4 5 9 / 0));
  /* Each plate's own scrim, row by row (from _work/scrim.py, scaled to 0.62), so the lit zones read at Control's 50 to
     70 grey. Contrast is carried by the pools and the tiles, not by the scrim. */
  --scrim-today: linear-gradient(rgb(4 5 9 / 0.49) 0%, rgb(4 5 9 / 0.49) 5%, rgb(4 5 9 / 0.48) 10%, rgb(4 5 9 / 0.46) 15%, rgb(4 5 9 / 0.41) 20%, rgb(4 5 9 / 0.37) 25%, rgb(4 5 9 / 0.40) 30%, rgb(4 5 9 / 0.44) 35%, rgb(4 5 9 / 0.44) 40%, rgb(4 5 9 / 0.44) 45%, rgb(4 5 9 / 0.45) 50%, rgb(4 5 9 / 0.47) 55%, rgb(4 5 9 / 0.44) 60%, rgb(4 5 9 / 0.40) 65%, rgb(4 5 9 / 0.29) 70%, rgb(4 5 9 / 0.19) 75%, rgb(4 5 9 / 0.34) 80%, rgb(4 5 9 / 0.34) 85%, rgb(4 5 9 / 0.31) 90%, rgb(4 5 9 / 0.19) 95%, rgb(4 5 9 / 0.19) 100%);
  --scrim-post: linear-gradient(rgb(4 5 9 / 0.38) 0%, rgb(4 5 9 / 0.38) 5%, rgb(4 5 9 / 0.40) 10%, rgb(4 5 9 / 0.41) 15%, rgb(4 5 9 / 0.50) 20%, rgb(4 5 9 / 0.50) 25%, rgb(4 5 9 / 0.27) 30%, rgb(4 5 9 / 0.20) 35%, rgb(4 5 9 / 0.40) 40%, rgb(4 5 9 / 0.40) 45%, rgb(4 5 9 / 0.34) 50%, rgb(4 5 9 / 0.40) 55%, rgb(4 5 9 / 0.40) 60%, rgb(4 5 9 / 0.44) 65%, rgb(4 5 9 / 0.45) 70%, rgb(4 5 9 / 0.43) 75%, rgb(4 5 9 / 0.40) 80%, rgb(4 5 9 / 0.48) 85%, rgb(4 5 9 / 0.50) 90%, rgb(4 5 9 / 0.50) 95%, rgb(4 5 9 / 0.50) 100%);
  --scrim-plan: linear-gradient(rgb(4 5 9 / 0.49) 0%, rgb(4 5 9 / 0.48) 5%, rgb(4 5 9 / 0.49) 10%, rgb(4 5 9 / 0.49) 15%, rgb(4 5 9 / 0.49) 20%, rgb(4 5 9 / 0.49) 25%, rgb(4 5 9 / 0.48) 30%, rgb(4 5 9 / 0.48) 35%, rgb(4 5 9 / 0.47) 40%, rgb(4 5 9 / 0.43) 45%, rgb(4 5 9 / 0.33) 50%, rgb(4 5 9 / 0.37) 55%, rgb(4 5 9 / 0.42) 60%, rgb(4 5 9 / 0.42) 65%, rgb(4 5 9 / 0.40) 70%, rgb(4 5 9 / 0.40) 75%, rgb(4 5 9 / 0.41) 80%, rgb(4 5 9 / 0.41) 85%, rgb(4 5 9 / 0.40) 90%, rgb(4 5 9 / 0.38) 95%, rgb(4 5 9 / 0.38) 100%);
  /* Ask's plane at dusk (09.10, direction A "Transcript"): the white void darkened to a grey sky, deepest under
     the top bar and over the composer, where the words are */
  --scrim-ask: linear-gradient(rgb(4 5 9 / 0.94) 0%, rgb(4 5 9 / 0.75) 22%, rgb(4 5 9 / 0.73) 55%, rgb(4 5 9 / 0.93) 100%);

  /* Type roles. Every type rule reads these. Archivo is the interface, weights 400 to 600 and never a light one; the
     date, titles and big figures are a step narrower (semi-condensed, 87.5% on the width axis), like Control's
     objective line. Newsreader is paper only. Figures are proportional; only columns that line up (times, amounts)
     take tabular ones (see "Digits that line up"). Settings' Type switches the face (html[data-face]); a face
     without a width axis just ignores the width. */
  --t-face: "Archivo", "Helvetica Neue", system-ui, sans-serif;
  --t-paper-face: "Newsreader", Georgia, "Times New Roman", serif;
  --t-display: 600 semi-condensed 1.875rem/1.05 var(--t-face);  --t-display-tr: -0.01em; /* the month on Plan */
  --t-title:   600 semi-condensed 1.375rem/1.15 var(--t-face);  --t-title-tr: -0.005em;  /* objective, running item, popup and tray titles, the date in Today's HUD */
  --t-number:  600 semi-condensed 2.375rem/1 var(--t-face);     --t-number-tr: -0.01em;  /* days left, cash */
  --t-head:    600 .9375rem/1.3 var(--t-face);                              /* the few headings left, popup kicker */
  --t-meta:    500 .875rem/1.3 var(--t-face);   --t-meta-tr: 0;            /* times, sender, day names, small facts */
  --t-key:     600 .9375rem/1.1 var(--t-face);   --t-key-tr: 0;             /* buttons, capture keys, tabs */
  --t-body:    400 1rem/1.45 var(--t-face);  --t-body-tr: 0;
  --t-paper:       400 1.0625rem/1.5 var(--t-paper-face);                    /* letter, draft and document body */
  --t-paper-title: 500 1.625rem/1.12 var(--t-paper-face);
  --t-paper-head:  500 1.0625rem/1.5 var(--t-paper-face);
  --t-paper-org:   500 1rem/1.3 var(--t-paper-face);

  /* Space */
  /* A window without insets (desktop, mobile Safari's bars) still keeps the HUD and the dock off its edges */
  --sat: max(env(safe-area-inset-top, 0px), 10px);
  --sab: max(env(safe-area-inset-bottom, 0px), 8px);
  /* 16px on a phone; a wide screen centres a 430px column; landscape keeps clear of the notch */
  --gut: max(16px, calc(50vw - 199px), env(safe-area-inset-left, 0px), env(safe-area-inset-right, 0px));
  --gap: 4px;          /* between tiles and keys in a group */
  --pad: 14px;         /* inside a tile, left and right */
  --sp-head: 10px;     /* a heading to its list */
  --sp-section: 24px;  /* one section to the next */
  --hit: 44px;         /* the smallest target */
  --key-h: max(54px, 3.375rem); /* grows with the iPhone's text size */
  --dock-h: var(--sab); /* the dock's height, exact in CSS: tabs 50, rail 50, a 6px gap, 16px over and 6px under */

  /* Layers */
  --z-world: 0; --z-app: 1; --z-hud: 20; --z-dock: 20; --z-scrim: 30; --z-tray: 31; --z-pops: 45; --z-ghost: 46;

  /* Motion. ui.js reads the --t-* durations, so CSS and script keep one clock */
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  --ease-in: cubic-bezier(0.55, 0, 1, 0.45);
  --ease-move: cubic-bezier(0.65, 0, 0.35, 1);
  --t-rise: 220ms;     /* a list's item rises 8px and fades in, inside a page */
  --t-step: 30ms;      /* a list's stagger */
  --t-collapse: 240ms; /* a row leaves and its neighbours close the gap */
  --t-roll: 280ms;     /* a number rolls */
  --t-fade: 120ms;     /* what movement becomes under reduced motion */

  /* Tap feel kit. A glass plate inverts to the off-white block on the hit. */
  --feel-hit: 60ms;
  --feel-up: 520ms;
  --feel-nudge: 320ms;
  --feel-pop: linear(0, 0.145, 0.463, 0.8, 1.059, 1.2, 1.233, 1.19, 1.113, 1.036, 0.979, 0.951, 0.946, 0.958, 0.976, 0.994, 1.006, 1.012, 1.012, 1.009, 1.005, 1.001, 0.998, 0.997, 0.997, 1);
  --feel-snap: linear(0, 0.111, 0.346, 0.599, 0.81, 0.958, 1.043, 1.079, 1.082, 1.067, 1.046, 1.026, 1.01, 1, 0.995, 0.993, 0.994, 0.995, 0.997, 1);
  --feel-out: cubic-bezier(0.2, 0.8, 0.2, 1);
  --feel-plate: transparent;
  --feel-ink: var(--paper);
  --feel-on-ink: var(--on-paper);
  --feel-ok: var(--teal);
}
/* Settings' Type (prefs: html[data-face]); without the attribute the face is Archivo */
:root[data-face="manrope"] { --t-face: "Manrope", "Avenir Next", "Helvetica Neue", system-ui, sans-serif; }
:root[data-face="public-sans"] { --t-face: "Public Sans", "Helvetica Neue", system-ui, sans-serif; }
:root:has(.dock) { --dock-h: calc(72px + var(--sab)); }
:root:has(.dock .rail:not([hidden])) { --dock-h: calc(128px + var(--sab)); }

/* ---------- Tap feel kit (feel.js). [data-feel] gets the look before the script wires it, so nothing flashes ---------- */
:is(.feel, [data-feel]) { position: relative; isolation: isolate; touch-action: manipulation; -webkit-tap-highlight-color: transparent; -webkit-touch-callout: none; -webkit-user-select: none; user-select: none; background-color: var(--feel-plate); color: var(--feel-ink); transition: scale 90ms var(--feel-out), border-color 140ms linear, --lift 160ms linear; }
:is(.feel, [data-feel])::before { content: ""; position: absolute; inset: 0; z-index: -1; background: color-mix(in srgb, var(--feel-ink) 22%, transparent); scale: 0 1; transform-origin: 0 50%; transition: scale 160ms var(--feel-out); pointer-events: none; }
/* Down shows Control's target mark: a 1px outline on the tile's own edge, not a web focus ring inside it */
:is(.feel, [data-feel])::after { content: ""; position: absolute; inset: -1px; border: 1px solid currentColor; opacity: 0; transition: opacity 90ms linear; pointer-events: none; }
/* Done is Control's unlocked tile: teal ink, a 1px teal outline and a milestone bar wiping left to right */
:is(.feel, [data-feel])[data-feel-done] { color: var(--feel-ok); box-shadow: inset 0 0 0 1px var(--feel-ok); }
:is(.feel, [data-feel])[data-feel-done]::before { top: auto; height: 3px; scale: 1 1; background: var(--feel-ok); transition: scale 240ms var(--feel-out); }
/* The squish follows the tile: 0.95 on small keys, 0.99 on full-width tiles, so long edges move 2px, not 18 */
.feel[data-feel-state="down"], .feel[data-feel-state="hold"] { scale: calc(1 - (1 - var(--feel-squish, 0.95)) * 0.6); }
.feel[data-feel-state="down"]::after, .feel[data-feel-state="hold"]::after, .feel[data-feel-state="armed"]::after { opacity: 1; scale: 1; }
.feel[data-feel-state="hold"]::before { scale: 1 1; transition: scale var(--feel-hold, 700ms) ease-in; }
.feel[data-feel-state="armed"] { scale: calc(1 - (1 - var(--feel-squish, 0.95)) * 0.6); }
.feel[data-feel-state="armed"], .feel[data-feel-state="hit"] { background-color: var(--feel-ink); color: var(--feel-plate); }
.feel[data-feel-state="armed"]::before { scale: 1 1; background: var(--feel-ink); transition: none; }
.feel[data-feel-state="hit"] { scale: var(--feel-squish, 0.95); transition: none; }
.feel[data-feel-state="hit"]::after { opacity: 0; transition: none; }
.feel[data-feel-state="up"] { animation: feel-pop var(--feel-up) var(--feel-pop) both; }
.feel[data-feel-state="up"]::after { animation: feel-ring 300ms var(--feel-out) both; }
.feel[data-feel-state="fail"] { animation: feel-nudge var(--feel-nudge) linear both; }
:is(.feel, [data-feel]):where([aria-pressed="true"], [aria-current]:not([aria-current="false"]), [aria-selected="true"]) { background-color: var(--feel-ink); color: var(--feel-plate); }
@keyframes feel-pop { from { scale: var(--feel-squish, 0.95); } to { scale: 1; } }
@keyframes feel-ring { from { opacity: 1; } to { opacity: 0; } }
@keyframes feel-nudge { 0%, 100% { translate: 0; } 20% { translate: -6px; } 45% { translate: 5px; } 70% { translate: -3px; } 88% { translate: 1px; } }
:is(.trow, .arow, .ltile, .prow, .setrow, .obj, .act, .btn) { --feel-squish: 0.99; }
/* The iOS haptic: a clear label over the control, wired to a hidden switch; the finger's own click ticks it */
[data-feel-haptic] { position: absolute; inset: 0; z-index: 1; -webkit-tap-highlight-color: transparent; touch-action: manipulation; }
[data-feel-haptic] > input { position: absolute; width: 1px; height: 1px; margin: 0; visibility: hidden; }
:disabled > [data-feel-haptic], [aria-disabled="true"] > [data-feel-haptic] { display: none; }
/* Swipe rows (Feel.swipe): the words follow the finger, the action shows in the gap they leave, as glass with the
   hatch until it arms. Armed, Done is teal (ink, hairline and bar, never a filled tile), Snooze the off-white block */
[data-swipe] { position: relative; touch-action: pan-y; -webkit-user-drag: none; -webkit-touch-callout: none; -webkit-user-select: none; user-select: none; }
[data-swipe].swiping { overflow: hidden; }
[data-swipe].swiping > :not(.swipe-act) { translate: var(--sx, 0px) 0; }
.swipe-act { position: absolute; z-index: 2; top: 0; bottom: 0; display: flex; align-items: center; gap: 8px; padding: 0 var(--pad); overflow: hidden; white-space: nowrap; pointer-events: none; font: var(--t-key); letter-spacing: var(--t-key-tr); color: var(--muted); background: var(--hatch-flat-img), var(--glass-deep); }
.swipe-act[data-dir="right"] { left: 0; width: max(0px, var(--sx, 0px)); }
.swipe-act[data-dir="left"] { right: 0; width: max(0px, -1 * var(--sx, 0px)); justify-content: flex-end; }
.swipe-act .ico { flex: none; }
.swipe-act.armed[data-dir="right"] { color: var(--teal); box-shadow: inset 0 0 0 1px var(--teal), inset 0 -3px var(--teal); }
.swipe-act.armed[data-dir="left"] { background: var(--paper); color: var(--on-paper); }
@media (prefers-reduced-motion: reduce) {
  .swipe-act[data-dir] { left: 0; right: 0; width: auto; opacity: 0; transition: opacity var(--t-fade) linear; background: var(--hatch-flat-img), var(--world); } /* over the words: solid */
  .swipe-act.armed { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  :is(.feel, [data-feel]), :is(.feel, [data-feel])::before, :is(.feel, [data-feel])::after { animation: none !important; scale: none !important; translate: none !important; }
  :is(.feel, [data-feel])::before { opacity: 0; transition: opacity 160ms linear; }
  .feel[data-feel-state="hold"]::before { opacity: 1; transition: opacity var(--feel-hold, 700ms) linear; }
  .feel[data-feel-state="armed"]::before { opacity: 1; }
  :is(.feel, [data-feel])[data-feel-done]::before { opacity: 1; transition: opacity 160ms linear; }
  :is(.feel, [data-feel])::after { transition: opacity 90ms linear; }
  .feel[data-feel-state="fail"] { outline: 2px solid currentColor; outline-offset: 4px; }
}
/* The hit and the selection both land on the off-white block with dark ink, whatever the plate was */
.feel:is([data-feel-state="hit"], [data-feel-state="armed"]) { color: var(--feel-on-ink); }
:is(.feel, [data-feel]):is([aria-pressed="true"], [aria-current]:not([aria-current="false"]), [aria-selected="true"], .sel) { background-color: var(--feel-ink); color: var(--feel-on-ink); }

/* ---------- Base ---------- */
/* An app, not a document: a quick second tap never zooms (pinch still does), and the page itself never bounces */
html { height: 100%; -webkit-text-size-adjust: 100%; text-size-adjust: 100%; touch-action: manipulation; overscroll-behavior: none; }
*, *::before, *::after { box-sizing: border-box; }
body { height: 100%; margin: 0; background: var(--world); color: var(--ink); font: var(--t-body); letter-spacing: var(--t-body-tr); -webkit-font-smoothing: antialiased; }
button, input, select, textarea { font: inherit; color: inherit; letter-spacing: inherit; }
button { appearance: none; -webkit-appearance: none; margin: 0; padding: 0; border: 0; border-radius: 0; background: none; text-align: inherit; cursor: pointer; }
a:is(.g, .gc, .ctr, .prompt, .btn) { text-decoration: none; }
h1, h2, h3, h4, p, ol, dl, dd { margin: 0; }
b { font-weight: inherit; }
[hidden] { display: none !important; }
svg { display: block; flex: none; }
.ico { width: 24px; height: 24px; }
.sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
::selection { background: var(--paper); color: var(--on-paper); }
.paper ::selection { background: var(--on-paper); color: var(--paper); }
/* Focus: a clear 2px off-white ring for keyboard focus only, on a thin dark halo so it reads over light neighbours.
   On paper it inks dark on a light halo */
:focus-visible { outline: 2px solid var(--ring); outline-offset: 2px; box-shadow: 0 0 0 6px var(--ring-halo); }
:is(.feel, [data-feel]):focus-visible { z-index: 3; }
:is(.feel, [data-feel])[data-feel-done]:focus-visible { box-shadow: inset 0 0 0 1px var(--feel-ok), 0 0 0 6px var(--ring-halo); }
:is(.feel, [data-feel], .field)[aria-invalid="true"]:focus-visible { box-shadow: inset 0 0 0 1px var(--red-mark), 0 0 0 6px var(--ring-halo); }

/* ---------- World: one plate per page (data-world on <html>), with its scrim and a slow settle on arrival.
   Between pages the world cuts with the page (see Motion). More reuses the Today atrium. Under each plate lies its own copy
   at 20x36 px, inline: until the plate is decoded the room shows soft, never black ---------- */
.world { position: fixed; inset: 0; z-index: var(--z-world); overflow: hidden; background: var(--world); view-transition-name: world; }
.world::before { content: ""; position: absolute; inset: 0; background-repeat: no-repeat; background-position: center; background-size: cover; animation: drift 30s cubic-bezier(0.25, 0.1, 0.25, 1) both; }
:is([data-world="today"], [data-world="more"]) .world::before { background-image: var(--scrim-today), url("plates/today-atrium-2.webp"), url("data:image/webp;base64,UklGRp4AAABXRUJQVlA4IJIAAADQBQCdASoUACQAPtFaoEwoJSMiKq35ABoJZQDAKBckwTVl+MP6Fewx4Q3AFitOtxpDCs3G9MAAAP7unTPtcCX6mkM1a4wVrGpzQaOaUVany4Z2L4DcafuU8XG7vOHsL6vF53EC5MElqmPCXafupyO1r135tFZ2HjmPlJz1uFMJJaguPOK6GZHsrWqdBUwHHqqYAA=="); }
[data-world="plan"] .world::before { background-image: var(--scrim-plan), url("plates/plan-corridor-2.webp"), url("data:image/webp;base64,UklGRo4AAABXRUJQVlA4IIIAAADQBQCdASoUACQAPtFWo00oJKMiMBVaqQAaCWkA0nAJr3Yf/E0X6271M8o1+NitonfMx2AoDRdoAP7vTrXPX/HdM18VnGLdqLIRS2Q86+g0ZWl2iA2gmYJe0j1gQwt5utwzZyzlGhEQrLy1AvrAACEZleKo5cB5HbaMiyFKKGq5AAAA"); }
[data-world="post"] .world::before { background-image: var(--scrim-post), url("plates/post-archive-2.webp"), url("data:image/webp;base64,UklGRo4AAABXRUJQVlA4IIIAAACQBQCdASoUACQAPtFcpk2oJSOiJWzJABoJZwDQzAne5naXynQciWLHQrkIn12nJbU5LZe2MAD+6YaEufUl5FmnT2mlAFZPCnTPPzN3ZbQNMCRBspQPL/oqIF/RxyjK/js6ydM7rkWpUgC+kyldWkjbtYPsdUchYnJKLeJSA/Fg5AAA"); }
[data-world="ask"] .world::before { background-image: var(--scrim-ask), url("plates/ask-astral-2.webp"), url("data:image/webp;base64,UklGRqQAAABXRUJQVlA4IJgAAADwBQCdASoUACQAPtFaok0oJSMiKqwBABoJaQDRgBZtjqqKt2tSMIdLgwviFPrluTdK4AlkzcKncAD+9gL76kDanvOfvW/cqkEAzjC7TYmsbrlOZN622m/QbrVQmcMwQWxFVIi28L9cM5OEiDOSzXJ8iEHdxE7OnbQeJHeZQhgpmwbtCnB6Vm6rhQ6cHirIz95fURxMYBV0AA=="); }
/* A window wider than the plate (941:1672) crops it by width; the scrim then scales with the plate, so each of its
   rows still lies on the plate row it was tuned for (a phone is always narrower, so this never applies there) */
@media (min-aspect-ratio: 941 / 1672) { .world::before { background-size: 100% calc(100vw * 1672 / 941); } }
@keyframes drift { from { transform: scale(1.04); } to { transform: none; } }
/* A reload (the app's pull to refresh on a page without refetch, Reload) shows the same page: the plate stays put */
[data-reload] .world::before { animation: none; }

/* ---------- Material: matte smoked glass. A dark tint, the room crisp behind it, a 1px hairline ---------- */
.g, .gg { background-color: var(--glass); border: 1px solid var(--line); color: var(--ink); }
.gg { position: relative; }
.g { --feel-plate: var(--glass); }
.gc { --feel-plate: transparent; background-color: transparent; color: var(--ink); }
.g.deep, .gg.deep { background-color: var(--glass-deep); }
/* The hatch every key carries, invisible at rest: a hover pointer lifts it (--lift 0 to 1) */
:is(.g, .gc, .prompt, .ctr) { background-image: repeating-linear-gradient(135deg, rgb(255 255 255 / calc(var(--lift) * 0.075)) 0 1px, transparent 1px 3.5px); }
/* An empty slot: a light tint and the fine hatch, the most see-through thing on screen */
.empty, .free { --feel-plate: var(--empty-tint); background-color: var(--empty-tint); background-image: repeating-linear-gradient(135deg, rgb(255 255 255 / calc(0.09 + var(--lift) * 0.07)) 0 1px, transparent 1px 3.5px); }
/* Inverted: hit, armed, selected, current. Solid block, no hatch, the border takes the fill */
:is(.g, .gc):is([data-feel-state="hit"], [data-feel-state="armed"], [aria-pressed="true"], [aria-current]:not([aria-current="false"]), [aria-selected="true"], .sel) { background-image: none; border-color: var(--paper); }
:is(.g, .gc):is([data-feel-state="hit"], [data-feel-state="armed"], [aria-pressed="true"], [aria-current]:not([aria-current="false"]), [aria-selected="true"], .sel) :is(.sub, .k, .tm, small, .wdn, .cnt, .from) { color: var(--paper-muted); }
/* Landed (ui.land): where a moved thing arrived inverts for 90 ms */
:is(.g, .gc).land { background-color: var(--paper); color: var(--on-paper); background-image: none; }
:is(.g, .gc).land :is(.sub, .tm, .k) { color: var(--paper-muted); }

/* ---------- Type roles ---------- */
/* A heading, only where the screen cannot be read without it: sentence case, 15px, 600, no tracking.
   Muted ink; red only when it means urgency. On the world it carries its own pool. */
.hd { position: relative; isolation: isolate; display: block; font: var(--t-head); color: var(--muted); text-shadow: var(--halo); }
.hd::before { content: ""; position: absolute; z-index: -1; inset: -16px -16px; background: var(--pool); pointer-events: none; }
:is(.tray, .info) .hd::before { content: none; }
.cnt { font: var(--t-meta); letter-spacing: var(--t-meta-tr); color: var(--ink); text-shadow: var(--halo); }
.k { font: var(--t-key); letter-spacing: var(--t-key-tr); color: var(--muted); }
.sub { display: block; font: var(--t-meta); letter-spacing: var(--t-meta-tr); color: var(--muted); text-wrap: pretty; }
/* The triangle means one thing, a legal deadline: Control's filled triangle inside a faint outline 2px out */
.tri { display: inline-block; width: 19px; height: 16px; flex: none; color: var(--red-mark); }
/* The objective mark: an outlined diamond with a filled centre */
.dia { display: inline-block; width: 11px; height: 11px; flex: none; border: 1.5px solid var(--yellow); background: linear-gradient(var(--yellow) 0 0) center / 4px 4px no-repeat; rotate: 45deg; }
/* Pools: text set straight on the world carries its own soft dark ground, so the world itself stays lit */
:is(.info, .pool) { position: relative; isolation: isolate; }
:is(.info, .pool)::before { content: ""; position: absolute; z-index: -1; inset: -18px -16px; background: var(--pool); pointer-events: none; }
/* A rolling number (ui.roll): the old figure leaves, the new one arrives, both in one grid cell */
.roll { display: inline-grid; }
.roll > * { grid-area: 1 / 1; }

/* ---------- HUD: wordmark left, counters right, like Control's menu header. It floats on the world with no bar:
   scrolled content is masked out before it gets here (see .app) ---------- */
.hud { position: fixed; z-index: var(--z-hud); top: 0; left: 0; right: 0; height: calc(var(--sat) + 48px); padding: var(--sat) var(--gut) 0; display: flex; align-items: center; justify-content: space-between; pointer-events: none; view-transition-name: hud; }
.hud > * { position: relative; isolation: isolate; }
/* Its pool holds the words over whatever scrolls under the soft top edge (the off-white running block included) */
.hud > *::before { content: ""; position: absolute; z-index: -1; inset: -16px -22px; background: radial-gradient(ellipse farthest-side, rgb(4 5 9 / 0.8), rgb(4 5 9 / 0.55) 60%, rgb(4 5 9 / 0)); pointer-events: none; }
/* On a tab the HUD's left is the 16px Dust mark and the tab's context ("Week 41", "2 to act on"), never its name */
.mark { display: flex; align-items: center; gap: 8px; min-height: 24px; font: var(--t-key); letter-spacing: var(--t-key-tr); color: var(--ink); text-shadow: var(--halo); }
.mark .ico { width: 16px; height: 16px; }
/* Today puts its date there instead: the day in the title role, "As of 09:29" (or the data's age) under it */
.hdate { display: grid; gap: 1px; }
.hdate .tc { font: var(--t-title); letter-spacing: var(--t-title-tr); color: var(--ink); text-shadow: var(--halo); white-space: nowrap; }
.hdate .stamp { display: block; font: 500 13px/1.3 var(--t-face); color: var(--meta-ink); text-shadow: var(--halo); }
:root[data-large-text] .hdate .stamp { font-size: 15px; }
.ctrs { display: flex; gap: 12px; align-items: center; }
.ctr { display: flex; align-items: center; gap: 6px; font: var(--t-key); letter-spacing: var(--t-key-tr); color: var(--ink); text-shadow: var(--halo); }
.ctr .ico { width: 16px; height: 16px; color: var(--teal); }
/* A counter that opens something (letters that need you open Post) is a 44px key */
:is(a, button).ctr { pointer-events: auto; min-width: var(--hit); height: var(--hit); justify-content: center; padding: 0 8px; --feel-plate: transparent; --feel-ink: var(--ink); --feel-on-ink: var(--on-paper); }
.ctrs > :is(a, button).ctr:last-child { margin-right: -8px; }
:is(a, button).ctr .ico { color: inherit; }
.ctr.land { color: var(--yellow); }
/* Back, on every page that is not a tab (base.html): Control's key prompt where the wordmark sits, its circle on the
   gutter. It goes to the page he came from, else to the page's parent */
.hudback { pointer-events: auto; margin-left: -10px; }
/* The app's pull to refresh (base.html): a line under the HUD fills from the left as he pulls, then the hatch sweeps
   along it while the page refreshes. Only where the document bounces: the app with its own dock */
.pull { display: none; position: fixed; z-index: var(--z-hud); top: calc(var(--sat) + 50px); left: var(--gut); right: var(--gut); height: 3px; pointer-events: none;
  background-color: var(--paper); transform: scaleX(var(--pull, 0)); transform-origin: 0 50%; opacity: var(--pull, 0); transition: opacity var(--t-fade) linear; }
html[data-docscroll]:not([data-tabs]) .pull { display: block; }
.pull.work { transform: none; opacity: 1; background-color: rgb(237 237 234 / 0.3); background-image: var(--sweep-img); background-size: calc(100% + 8.49px) 100%; background-repeat: no-repeat; animation: sweep 640ms linear infinite; }

/* ---------- App and screens. .app is the scroller, fixed to the screen and masked: content fades out before it
   reaches the HUD and the dock, like a Control panel ending at a hard frame. Nothing inside it may blur (the mask
   makes it the backdrop root). Scroll lives here, not on the window. ---------- */
/* scroll-padding: whatever focus or a link scrolls to lands between the HUD and the dock, never under them */
.app { position: fixed; inset: 0; z-index: var(--z-app); overflow-x: hidden; overflow-y: auto; overscroll-behavior-y: contain; view-transition-name: content;
  scroll-padding: calc(var(--sat) + 62px) 0 calc(var(--dock-h) + 16px);
  -webkit-mask-image: linear-gradient(transparent calc(var(--sat) + 40px), #000 calc(var(--sat) + 60px), #000 calc(100% - var(--dock-h) - 6px), transparent calc(100% - var(--dock-h) + 18px));
  mask-image: linear-gradient(transparent calc(var(--sat) + 40px), #000 calc(var(--sat) + 60px), #000 calc(100% - var(--dock-h) - 6px), transparent calc(100% - var(--dock-h) + 18px)); }
/* Clears the HUD by its own height and the dock by its own */
.screen { min-height: 100%; padding: calc(var(--sat) + 62px) var(--gut) calc(var(--dock-h) + 18px); }
/* The offline line stays above the dock until the network is back: the page gets room to scroll its last key clear */
body:has(#offline-bar) .screen { padding-bottom: calc(var(--dock-h) + 18px + 64px); }

/* ---------- Dock: the capture keys and the tab strip in thumb reach, words on the world split by 1px rules.
   It reaches the bottom edge on a smoked ground that never turns solid: darkest behind the tabs, easing off in the
   home indicator's strip (no words there), so the world runs to the edge with no black band and no seam ---------- */
.dock { position: fixed; z-index: var(--z-dock); left: 0; right: 0; bottom: 0; display: grid; gap: 6px; padding: 16px var(--gut) calc(var(--sab) + 6px); background: linear-gradient(rgb(4 5 9 / 0), rgb(4 5 9 / 0.5) 28px, rgb(4 5 9 / 0.62) calc(100% - var(--sab) - 8px), rgb(4 5 9 / 0.4)); view-transition-name: dock; }
[data-world="ask"] .dock { background: linear-gradient(rgb(7 8 10 / 0), rgb(7 8 10 / 0.8) 28px, rgb(7 8 10 / 0.88) calc(100% - var(--sab) - 8px), rgb(7 8 10 / 0.7)); }
/* Capture keys are Control's key prompts: a ring glyph beside the key word, no frame. Not on Ask: its field captures */
.rail { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); height: 50px; }
.capk { display: flex; align-items: center; justify-content: center; gap: 10px; font: var(--t-key); letter-spacing: var(--t-key-tr); text-shadow: var(--halo); }
.capk + .capk { border-left: 1px solid var(--line); }
.capk .ring { width: 30px; height: 30px; display: grid; place-items: center; border: 1.5px solid currentColor; border-radius: 50%; }
.capk .ico { width: 16px; height: 16px; }
/* Tabs: Today, Plan, Post, Ask, More. The current one is the off-white block (aria-current="page") */
.tabs { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); height: 50px; }
.tab { position: relative; display: flex; align-items: center; justify-content: center; gap: 5px; font: var(--t-key); letter-spacing: var(--t-key-tr); color: var(--muted); }
.tab + .tab { border-left: 1px solid var(--line); }
/* Compact navigation stays legible in five columns; reading text uses the full Dynamic Type size. */
.dock { --t-key: 600 clamp(15px, .9375rem, 20px)/1.1 var(--t-face); --t-meta: 500 14px/1.3 var(--t-face); }
.capk { gap: clamp(4px, calc(26px - 1rem), 10px); }
/* Unread is a count block like Control's chip; the triangle is kept for legal deadlines */
.tab .ucount { position: relative; top: -8px; min-width: 18px; height: 18px; display: grid; place-items: center; padding: 0 4px; background: var(--chip); color: var(--ink); font: var(--t-meta); letter-spacing: var(--t-meta-tr); }

/* ---------- Dock b, slim (?dock=b): one framed glass panel, the capture keys on a 46px tier over 46px tabs (44px inside their hairlines), with
   Control's corner ticks. The same words and the same off-white block for the current tab ---------- */
.tmark { display: none; }
:root[data-dock="b"] .dock { gap: 0; padding: 10px var(--gut) calc(var(--sab) + 4px); }
:root[data-dock="b"] .dock > :is(.rail, .tabs) { background-color: var(--glass-deep); border: 1px solid var(--line); }
:root[data-dock="b"] .rail { height: 46px; border-bottom: 0; }
:root[data-dock="b"] .tabs { height: 46px; }
:root[data-dock="b"] .capk .ring { width: 24px; height: 24px; border-width: 1.25px; }
:root[data-dock="b"] .capk .ico { width: 13px; height: 13px; }
:root[data-dock="b"] .dock::before { content: ""; position: absolute; inset: 7px calc(var(--gut) - 3px) calc(var(--sab) + 1px); pointer-events: none;
  background: linear-gradient(var(--line-hover) 0 0) 0 0 / 9px 1px, linear-gradient(var(--line-hover) 0 0) 0 0 / 1px 9px,
    linear-gradient(var(--line-hover) 0 0) 100% 0 / 9px 1px, linear-gradient(var(--line-hover) 0 0) 100% 0 / 1px 9px,
    linear-gradient(var(--line-hover) 0 0) 0 100% / 9px 1px, linear-gradient(var(--line-hover) 0 0) 0 100% / 1px 9px,
    linear-gradient(var(--line-hover) 0 0) 100% 100% / 9px 1px, linear-gradient(var(--line-hover) 0 0) 100% 100% / 1px 9px;
  background-repeat: no-repeat; }
:root[data-dock="b"]:has(.dock .tabs) { --dock-h: calc(60px + var(--sab)); }
:root[data-dock="b"]:has(.dock .rail:not([hidden])) { --dock-h: calc(106px + var(--sab)); }

/* ---------- Dock c, line (?dock=c): one 52px strip under a hairline. The tabs as words, then the capture keys as rings
   at the thumb's end (their words stay for screen readers). The current tab is lit, with a 2px off-white line on
   top. Larger text keeps the first dock's two rows ---------- */
:root[data-dock="c"]:not([data-large-text]) .dock { grid-template-columns: minmax(0, 1fr) auto; gap: 0; padding: 12px 8px calc(var(--sab) + 4px); }
:root[data-dock="c"]:not([data-large-text]) .dock > :is(.rail, .tabs) { height: 52px; border-top: 1px solid var(--line); }
:root[data-dock="c"]:not([data-large-text]) .rail { order: 1; grid-template-columns: repeat(3, 44px); border-left: 1px solid var(--line); }
:root[data-dock="c"]:not([data-large-text]) .capk + .capk { border-left: 0; }
:root[data-dock="c"]:not([data-large-text]) .capk .cw { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
:root[data-dock="c"]:not([data-large-text]) .tabs { position: relative; }
:root[data-dock="c"]:not([data-large-text]) .tab + .tab { border-left: 0; }
:root[data-dock="c"]:not([data-large-text]) .tabs .tab[aria-current] { background-color: transparent; border-color: transparent; color: var(--ink-hi); }
:root[data-dock="c"]:not([data-large-text]) .tmark { display: block; position: absolute; top: -1px; left: calc(var(--i, 0) * 20%); width: 20%; height: 2px; background: var(--paper); pointer-events: none; view-transition-name: tmark; }
:root[data-dock="c"]:not([data-large-text]):has(.dock .tabs) { --dock-h: calc(68px + var(--sab)); }
/* A 49px tab has no room for the word and the count side by side: the count sits over the word's end */
:root[data-dock="c"]:not([data-large-text]) .tab .ucount { position: absolute; top: 0; right: 0; }

/* ---------- In the iPhone app the document scrolls (DustScroll/ or DustTabs/ in its user agent, html[data-docscroll])
   instead of .app, so iOS's own bounce and the tap on the status bar reach the page (Dust.ui.scroller() says which one
   scrolls). The world, the HUD and the dock stay fixed: WebKit holds fixed layers to the view through the bounce.
   Content runs edge to edge, under the status bar, the HUD and the dock, softened at the two edges by the rim. ---------- */
html[data-docscroll] { height: auto; overscroll-behavior: auto; scroll-padding: calc(var(--sat) + 62px) 0 calc(var(--dock-h) + 16px); }
html[data-docscroll] body { height: auto; }
html[data-docscroll] .app { position: relative; inset: auto; overflow: visible; overflow-x: clip; -webkit-mask-image: none; mask-image: none; }
html[data-docscroll] .screen { min-height: 100dvh; }
/* ui.js's screen-reader line sits at the end of <body>: in a scrolling document it would add a pixel of scroll */
html[data-docscroll] body > .sr { top: 0; }
/* The rim (base.html's .rim, drawn only here): the two soft edges, a blur and a dim where content passes under the
   status bar and the HUD, and under the dock (iOS 26's soft scroll edge, in Dust's smoke). The one blur in the
   system; tiles and trays stay unblurred. It sits on the rim's own layers because .hud and .dock carry a
   view-transition-name, which makes each a backdrop root: a blur inside them would see nothing of the page. The top
   edge ends where a page's first line sits at rest, so nothing is touched until it scrolls */
.rim { display: none; }
html[data-docscroll] .rim { display: block; position: fixed; inset: 0; z-index: calc(var(--z-app) + 1); pointer-events: none; }
html[data-docscroll] .rim::before, html[data-docscroll] .rim::after { content: ""; position: absolute; left: 0; right: 0; -webkit-backdrop-filter: blur(14px); backdrop-filter: blur(14px); }
html[data-docscroll] .rim::before { top: 0; height: calc(var(--sat) + 62px); background: linear-gradient(rgb(4 5 9 / 0.42), rgb(4 5 9 / 0.32) calc(var(--sat) + 44px), rgb(4 5 9 / 0));
  -webkit-mask-image: linear-gradient(#000 calc(var(--sat) + 44px), transparent); mask-image: linear-gradient(#000 calc(var(--sat) + 44px), transparent); }
html[data-docscroll] .rim::after { bottom: 0; height: calc(var(--dock-h) + 40px); background: linear-gradient(rgb(4 5 9 / 0), rgb(4 5 9 / 0.36) 64px);
  -webkit-mask-image: linear-gradient(transparent, #000 56px); mask-image: linear-gradient(transparent, #000 56px); }
/* A tray is modal: the page under it stays put, and a pull on the scrim cannot refresh it */
html[data-docscroll]:has(body > .tray:not([hidden])) { overflow: hidden; }
/* Tabs mode (DustTabs/, html[data-tabs]): the 1.2.0 app draws its own tab bar, so the page's tab row goes and the
   capture row is the bottom bar; Ask, without capture keys, has no dock. Its web view ends above the app's bar, so the
   bottom inset is 0 there.
   ponytail: only for phones still on the 1.2.0 tabs JS; delete with base.html's tabsApp */
html[data-tabs] .tabs { display: none; }
html[data-tabs] .dock:not(:has(.rail:not([hidden]))) { display: none; }
:root[data-tabs]:has(.dock) { --dock-h: var(--sab); }
:root[data-tabs]:has(.dock .rail:not([hidden])) { --dock-h: calc(72px + var(--sab)); }

/* ---------- Keys and trays ---------- */
/* A glyph tile: a white geometric glyph in a square */
.gt { width: var(--hit); height: var(--hit); display: grid; place-items: center; border: 1px solid var(--line); }
.btn { min-height: var(--key-h); display: flex; align-items: center; justify-content: center; gap: 10px; padding: 0 var(--pad); font: var(--t-key); letter-spacing: var(--t-key-tr); overflow-wrap: anywhere; }
.btn.wide { width: 100%; margin-top: 16px; }
/* The primary key: the off-white block, at most one per screen. On the hit it inverts to dark */
.pri { --feel-plate: var(--paper); --feel-ink: var(--ink-hit); --feel-on-ink: var(--paper); background-color: var(--paper); color: var(--on-paper); border: 1px solid var(--paper); }
.pri[data-feel-done] { --feel-plate: var(--glass); background-color: var(--glass); border-color: var(--line); }
/* Two keys side by side, sharing the row; a hidden first key gives the second the whole row */
.acts2 { display: grid; grid-template-columns: 1fr 1fr; gap: var(--gap); margin-top: 16px; }
.acts2 > [hidden] + * { grid-column: 1 / -1; }
.acts2 > :first-child:has(+ [hidden]) { grid-column: 1 / -1; } /* and a hidden second key gives the first the row */
/* Key prompt: a circled glyph and the key word, Control's B Exit (Back, Undo, Ask something else) */
.prompt { justify-self: start; display: inline-flex; align-items: center; gap: 10px; min-height: var(--hit); padding: 0 14px 0 10px; font: var(--t-key); letter-spacing: var(--t-key-tr); color: var(--ink); text-shadow: var(--halo); --feel-plate: transparent; }
.prompt .key { width: 26px; height: 26px; display: grid; place-items: center; border-radius: 50%; background: var(--paper); color: var(--on-paper); }
.prompt .key .ico { width: 16px; height: 16px; }
/* A square key with one glyph: the month and day arrows. Forward is the sprite's back arrow mirrored */
.mkey { width: var(--key-h); height: var(--key-h); display: grid; place-items: center; --feel-squish: 0.95; }
.ico.fwd { scale: -1 1; }
/* Trays: Control's sub-menu. No panel: the title and the tiles sit straight on the world, dimmed and never blurred,
   with a Back prompt at the bottom left. They spring up over a fading scrim and close downward (ui.tray). The 4px
   padding keeps focus rings inside the scroller. */
/* The last stop is opaque at the tray's top edge: the tray has no panel, so light paper under it would show through */
.scrim { position: fixed; inset: 0; z-index: var(--z-scrim); background: linear-gradient(var(--dim-world) calc(var(--tray-top, 50%) - 120px), rgb(2 3 6 / 0.96) calc(var(--tray-top, 50%) - 36px), rgb(2 3 6) var(--tray-top, 50%)); animation: fade 200ms linear both; }
.scrim.out { animation: fade-out 180ms linear both; }
.tray { position: fixed; z-index: var(--z-tray); left: calc(var(--gut) - 4px); right: calc(var(--gut) - 4px); bottom: calc(var(--sab) + 4px); max-height: calc(var(--tray-room, 100%) - var(--sat) - var(--sab) - 20px); padding: 4px; overflow-y: auto; overscroll-behavior: contain; animation: tray-in 440ms var(--feel-snap) both; }
.tray.out { animation: tray-out 180ms var(--ease-in) both; }
.tray:focus { outline: none; box-shadow: none; } /* focus moves into the tray; the tray itself shows no mark */
/* A sheet: a panel from the bottom edge with a grabber (Ask's History, + and More, the model and effort picker). ui.tray
   gives the focus trap, Escape and the scrim, closes it on a drag down or a tap on the grabber, and lifts it over the
   keyboard (its inline bottom, hence the margin) */
.tray.sheet { left: 0; right: 0; display: flex; flex-direction: column; max-height: min(86dvh, calc(var(--tray-room, 100dvh) - var(--sat) - 24px)); margin-bottom: calc(-4px - var(--sab)); padding: 0 var(--gut) calc(var(--sab) + 20px); overflow: hidden; background: rgb(10 11 13 / 0.97); border-top: 1px solid var(--line-hi); }
.tray.sheet > * { flex: none; }
.tray.sheet.drag { animation: none; transition: none; }
.tray.sheet:not(.drag) { transition: translate 220ms var(--ease-out); }
.grab { display: grid; place-items: center; height: 32px; margin: 0 calc(var(--gut) * -1); touch-action: none; cursor: grab; }
.grab::before { content: ""; width: 40px; height: 4px; background: var(--line-hi); }
.shead { display: flex; align-items: center; justify-content: space-between; gap: 12px; min-height: 48px; }
.shead .ttl { margin: 0; }
.shead .btn { min-height: var(--hit); }
.shead .btn .ico { width: 20px; height: 20px; }
.tray.sheet .errline { margin-top: 10px; }
/* The model and effort picker (_macros.html pick_sheet): two lists of choices, the picked one the off-white block; it
   scrolls inside the sheet at large text */
#pick-sheet { overflow-y: auto; overscroll-behavior: contain; }
#pick-sheet .hd { margin: 14px 0 var(--sp-head); }
#pick-sheet .arow { align-items: center; min-height: 64px; }
#pick-sheet .arow .tn { font-weight: 600; }
.thead { display: flex; align-items: baseline; gap: 10px; }
.ttl { margin-top: 6px; font: var(--t-title); letter-spacing: var(--t-title-tr); text-wrap: balance; text-shadow: var(--halo); }
.tray > .prompt { margin-top: 16px; padding-left: 0; }
.tlead { margin-top: 8px; }
/* A tray's summary: the glyph tile beside the title and its meta */
.tsum { display: grid; grid-template-columns: 52px minmax(0, 1fr); gap: 12px; align-items: center; margin-top: 6px; }
.tsum .gt { width: 52px; height: 52px; }
.tsum .ttl { margin-top: 0; }
.note { margin-top: 12px; font: var(--t-meta); letter-spacing: var(--t-meta-tr); color: var(--muted); }
/* A small fact set straight on the world takes the brighter meta ink over its pool */
.note.pool { color: var(--meta-ink); text-shadow: var(--halo); }
/* What a moved thing becomes on its way (ui.fly) */
.ghost { position: fixed; z-index: var(--z-ghost); pointer-events: none; background: var(--paper); }

/* ---------- Popups and toasts ---------- */
/* Control popups: no panel. A small white line over a yellow title, set on the world in a soft dark pool, in from the
   right edge, then home to their counter. Done and Snooze keep an Undo key for 5 s (ui.popup) */
.pops { position: fixed; z-index: var(--z-pops); left: 0; right: 0; bottom: calc(var(--dock-h) + 10px); display: grid; justify-items: end; gap: 10px; pointer-events: none; }
.pops::before { content: ""; position: absolute; z-index: -1; left: 0; right: 0; top: -56px; bottom: -10px; background: linear-gradient(rgb(4 5 9 / 0), rgb(4 5 9 / 0.86) 56px); opacity: 0; transition: opacity 200ms linear; }
.pops:has(.pop, .toast)::before { opacity: 1; }
/* Under an open tray the current tab drops its block, which would show through the scrim behind the tray's keys */
body:has(> .tray:not([hidden])) .tabs .tab[aria-current] { background-color: transparent; border-color: transparent; color: var(--muted); }
/* The dock's words would ghost through the scrim beside a short tray; it takes no tap while a tray is open anyway. It
   goes once the scrim is up and is back as the tray starts to close, so focus can return to the dock key that opened it */
body:has(> .tray:not([hidden], .out)) .dock { visibility: hidden; transition: visibility 0s 200ms; }
/* A tray is modal: while one is open, popups and toasts (an offline line that stays, a Reload notice) wait under its
   scrim instead of covering its keys */
body:has(> .tray:not([hidden])) .pops { z-index: calc(var(--z-scrim) - 1); }
.pop { position: relative; isolation: isolate; display: grid; grid-template-columns: minmax(0, auto) 50px; align-items: center; gap: 14px; max-width: calc(324px + var(--gut)); padding: 10px var(--gut) 10px 26px; text-align: right; animation: pop-in 220ms var(--ease-out) both; }
/* A dark pool wide and deep enough that the row text under it does not read through (Steven, 09.10) */
.pop::before { content: ""; position: absolute; z-index: -1; inset: -24px 0 -24px -60px; background: radial-gradient(farthest-side at 75% 50%, rgb(4 5 9 / 0.92), rgb(4 5 9 / 0.75) 60%, rgb(4 5 9 / 0)); }
.pb { display: grid; justify-items: end; }
.pop .pk { display: block; font: var(--t-head); color: var(--ink); text-shadow: var(--halo); }
/* The clamp clips at its padding edge: 0.15em under the second line keeps g, y and p whole */
.pop .pt { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden; margin-top: 4px; padding-bottom: 0.15em; font: var(--t-title); letter-spacing: var(--t-title-tr); color: var(--yellow); text-shadow: var(--halo); }
.pop .undo { justify-self: end; margin: 6px -10px -8px 0; pointer-events: auto; }
.pop .pg { width: 50px; height: 50px; display: grid; place-items: center; border: 1px solid var(--line-hi); color: var(--ink); }
/* A toast is a subtitle band above the dock, at the left; an error toast is an error line (ui.toast) */
.toasts { justify-self: stretch; display: grid; justify-items: start; gap: var(--gap); padding: 0 var(--gut); }
/* The status region is always there (so the first toast is read out); empty, it leaves the grid: no row, no gap */
.toasts:empty { position: absolute; }
.toast { display: flex; align-items: center; gap: 12px; pointer-events: auto; }
.toast .prompt { flex: none; margin: -10px 0; }
.toast .prompt:last-child { margin-right: -14px; }

/* ---------- Fields and choices ---------- */
.field { width: 100%; height: var(--key-h); padding: 0 var(--pad); border-radius: 0; font: var(--t-body); font-size: max(16px, 1rem); letter-spacing: var(--t-body-tr); color: var(--ink); -webkit-appearance: none; appearance: none; }
textarea.field { display: block; height: auto; min-height: 108px; padding: 14px var(--pad); resize: vertical; }
.field::placeholder { color: var(--muted); }
.field:focus { border-color: var(--line-hi); box-shadow: inset 0 0 0 1px var(--yellow); }
/* A text field matches :focus-visible on any focus; the ring is for keyboard users, a finger gets the yellow line */
:root:not([data-input="key"]) .field:focus-visible { outline: none; }
/* Native pickers draw dark, and a date reads from the left like every field (WebKit centres it) */
.field:is([type="time"], [type="date"], [type="datetime-local"], select) { color-scheme: dark; }
.field::-webkit-date-and-time-value { text-align: left; }
/* A select keeps the field and gets a square-capped chevron (the glyph rules: 2 unit stroke, mitre) */
.selw { position: relative; display: block; }
.selw::after { content: ""; position: absolute; right: 20px; top: 50%; width: 8px; height: 8px; margin-top: -6px; border: solid var(--muted); border-width: 0 2px 2px 0; rotate: 45deg; pointer-events: none; }
.selw > select { padding-right: 46px; text-overflow: ellipsis; }
/* type="search" brings the iPhone's search key; its native clear and decoration would break the square material */
.field[type="search"]::-webkit-search-cancel-button, .field[type="search"]::-webkit-search-decoration { -webkit-appearance: none; appearance: none; display: none; }
form > .hd + .field { margin-top: var(--sp-head); }
/* A field with its key: the label over the field, the key beside it */
.pick { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: var(--gap); align-items: end; margin-top: 12px; }
.pick label { display: block; margin-bottom: 8px; }
/* A line under a field: the hint, or what went wrong (.err) */
.addrow { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px; margin-top: 12px; }
.addrow .tag { margin: 0; }
/* Add's suggested time: a small key, the off-white block once taken (aria-pressed) */
.addrow .btn { min-height: var(--hit); padding: 0 12px; gap: 8px; }
.addrow .btn .ico { width: 18px; height: 18px; }
.sub.err { color: var(--red); }
/* Choices: cells sharing 1px rules; the picked one is the off-white block (aria-pressed="true"; .sel marks the
   likely answer before a pick). .opts is the snooze grid (six cells, three a row); .seg is one row of any number */
.opts { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); margin-top: 14px; }
.opt { height: 78px; display: flex; flex-direction: column; justify-content: center; align-items: flex-start; gap: 8px; padding: 0 12px; text-align: left; border-right: 1px solid var(--line); border-bottom: 1px solid var(--line); }
.opt:nth-child(3n) { border-right: 0; }
.opt:nth-child(n + 4) { border-bottom: 0; }
.opt b { font: var(--t-key); letter-spacing: var(--t-key-tr); }
.opt small { font: var(--t-meta); letter-spacing: var(--t-meta-tr); color: var(--muted); }
.seg { display: grid; grid-auto-flow: column; grid-auto-columns: minmax(0, 1fr); }
.seg > * { min-height: 54px; display: flex; align-items: center; justify-content: center; gap: 8px; padding: 0 10px; font: var(--t-key); letter-spacing: var(--t-key-tr); text-align: center; color: var(--muted); }
.seg > * + * { border-left: 1px solid var(--line); }
.seg > * .ico { width: 16px; height: 16px; }
/* The picked choice is lit glass with a 2px off-white line at its foot, so the screen's one off-white block stays the
   current tab or day */
.seg > :is(.feel, [data-feel]):is([aria-pressed="true"], [aria-current]:not([aria-current="false"])):not([data-feel-state="hit"], [data-feel-state="armed"]) { background-color: var(--glass); color: var(--ink-hi); border-color: var(--line); box-shadow: inset 0 -2px 0 var(--paper); }
/* At accessibility sizes the choices reflow instead of shrinking their words. The default layout stays intact. */
:root[data-large-text] .seg { grid-auto-flow: row; grid-template-columns: repeat(auto-fit, minmax(min(100%, 5rem), 1fr)); }
:root[data-large-text] .opts, :root[data-large-text] .slots { grid-template-columns: repeat(2, minmax(0, 1fr)); }
:root[data-large-text] .opt { height: auto; min-height: 78px; padding-block: 12px; overflow-wrap: anywhere; }
:root[data-large-text] .setrow:has(> .gt) { grid-template-columns: 44px minmax(0, 1fr); }
:root[data-large-text] .setrow:has(> .gt) > :is(.seg2, .btn, .prompt) { grid-column: 2; justify-self: start; }
:root[data-large-text] .acts2 { grid-template-columns: minmax(0, 1fr); }
:root[data-large-text] .ofoot { flex-wrap: wrap; }
:root[data-large-text] .wfuse { min-width: 100%; }
:root[data-large-text] .hud { --t-title: 600 semi-condensed 22px/1.15 var(--t-face); --t-meta: 500 14px/1.3 var(--t-face); }
:root[data-large-text] .mnav, :root[data-large-text] .dnav { flex-shrink: 0; }
:root[data-large-text] .mkey { width: 54px; }
:root[data-large-text] .drain { flex-wrap: wrap; gap: 4px 12px; padding-block: 6px; }
:root[data-large-text] .rm { flex-wrap: wrap; }

/* ---------- Switches, chips, tags, cards ---------- */
/* A switch is a whole row: glyph tile, words, and the setting mark. The current value is teal text in a 1px teal
   outline; the other stays grey (role="switch" aria-checked) */
.setrow { display: grid; grid-template-columns: 44px minmax(0, 1fr) auto; gap: 12px; align-items: center; width: 100%; min-height: 84px; padding: 10px 12px 10px 10px; text-align: left; }
.setrow + .setrow { margin-top: var(--gap); }
.setrow b { display: block; font: var(--t-body); letter-spacing: var(--t-body-tr); }
.setrow:where([aria-checked]) { background-color: var(--glass); color: var(--ink); }
.setrow:where([aria-checked])[data-feel-state="hit"] { background-color: var(--paper); color: var(--on-paper); }
.seg2 { display: grid; grid-template-columns: 42px 42px; gap: var(--gap); height: 34px; }
.seg2 span { display: grid; place-items: center; border: 1px solid var(--line); font: var(--t-meta); letter-spacing: var(--t-meta-tr); color: var(--muted); }
[role="switch"][aria-checked="true"] .seg2 .s-on, [role="switch"][aria-checked="false"] .seg2 .s-off { border-color: var(--teal); color: var(--teal); }
/* A switch row without a glyph tile: the words, then the mark */
.setrow.bare { grid-template-columns: minmax(0, 1fr) auto; padding-left: var(--pad); }

/* A med (Health, Today's meds tray): one tap, taken today. Taken is teal (the kit's done: ink, outline, bar) with the time under the dose; the
   tick fills in. A dose shown taken by the server does not animate again on arrival, only one he just tapped */
.med { min-height: 72px; }
.med b { overflow-wrap: anywhere; }
.med .at { color: var(--teal); }
.med .at:empty { display: none; }
.med .at.err { color: var(--red); } /* a save that failed, said on the row in Today's tray */
.tick { width: 34px; height: 34px; display: grid; place-items: center; border: 1px solid var(--line-hi); }
.tick .ico { width: 16px; height: 16px; visibility: hidden; }
.med[aria-checked="true"] .tick { border-color: var(--teal); }
.med[aria-checked="true"] .tick .ico { visibility: visible; }
.med[data-feel-done]:not(.live) .ico { animation: none; }
/* The hit is the inverted block: the teal parts take its dark ink for those 60 ms */
.med[data-feel-state] :is(.at, .tick) { color: inherit; border-color: currentColor; }
/* A count block (Control's chip). Plain digits, never zero-padded */
.chip { min-width: 26px; height: 24px; padding: 0 5px; display: inline-grid; place-items: center; background: var(--chip); font: var(--t-key); letter-spacing: var(--t-key-tr); color: var(--chip-ink); }
/* A tag: a small off-white label for what a thing became (a date, a time) */
.tag { display: inline-block; margin-top: 6px; padding: 4px 7px 3px; background: var(--paper); color: var(--on-paper); font: var(--t-meta); letter-spacing: var(--t-meta-tr); }
/* Result cards: a glyph tile and a title with its tag */
.cards { display: grid; gap: var(--gap); margin-top: 12px; }
.card { display: grid; grid-template-columns: 44px minmax(0, 1fr); gap: 12px; align-items: center; padding: 8px 12px 8px 8px; }
.card b { display: block; font: var(--t-body); letter-spacing: var(--t-body-tr); }
/* A card with a key (Undo, Open) in its last column */
.card:has(> .prompt) { grid-template-columns: 44px minmax(0, 1fr) auto; }
/* Undone: the card or row stays as a dim, struck line */
:is(.card, .setrow).undone { background-color: var(--glass-dim); }
:is(.card, .setrow).undone :is(b, .gt) { color: var(--dim); }
:is(.card, .setrow).undone b { text-decoration: line-through; text-decoration-thickness: 1.5px; }

/* ---------- Subtitles and lines ---------- */
/* Subtitles: Control's dialogue line. The speaker's name in red (sentence case, "Dust:", "You:"), the words in white */
.subl { font: var(--t-body); letter-spacing: var(--t-body-tr); color: var(--ink); }
.who { font-weight: 600; color: var(--red); margin-right: 6px; }
.band { justify-self: start; width: fit-content; max-width: 100%; padding: 10px 14px; background: var(--band); animation: band-in 220ms var(--ease-out) both; }
/* Loading in a line: the hatch sweep, there only while something loads */
.sweep, .band.work .subl::after { display: block; height: 6px; background-color: rgb(237 237 234 / 0.1); background-image: var(--sweep-img); background-size: calc(100% + 8.49px) 100%; background-repeat: no-repeat; animation: sweep 640ms linear infinite; }
.band.work .subl::after { content: ""; margin-top: 10px; }
.sweep { margin-top: 14px; }
/* A slow refetch sweeps under a header without moving anything below it (the header is positioned) */
.sweep.edge { position: absolute; left: 0; right: 0; bottom: -10px; height: 4px; margin: 0; }
/* Error in a line: a red edge, a nudge, one plain sentence on what to do */
.errline { margin-top: 12px; padding: 12px 14px 12px 17px; background: var(--band); box-shadow: inset 3px 0 0 var(--red-mark); font: var(--t-body); letter-spacing: var(--t-body-tr); color: var(--ink); animation: feel-nudge var(--feel-nudge) linear both; }
.toasts .errline { margin-top: 0; }
/* A load error under a header carries its Retry key at the end of the line */
.errline:has(> .prompt) { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.errline > .prompt { flex: none; margin: -10px -6px -10px 0; text-shadow: none; }
/* Success in a line: teal words in a teal hairline ("Nothing to do. The debit runs by itself.") */
.okline { display: flex; align-items: center; gap: 10px; min-height: 56px; margin-top: var(--gap); padding: 0 var(--pad); background: var(--band); border: 1px solid var(--teal); font: var(--t-body); letter-spacing: var(--t-body-tr); color: var(--teal); }
.okline .ico { width: 16px; height: 16px; }

/* ---------- Rows and lists ---------- */
/* One column that never grows past the screen. Rows, not boxes: a list of glass rows is one glass panel and its rows
   are lines in it, split by 1px rules (Control frames the panel, not each line). An empty slot in a list keeps its
   hatch; a list of only an empty slot is that slot */
.alist, .plist { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--gap); }
:is(.alist, .plist):has(> .g:not(.empty), > .pr) { gap: 0; background-color: var(--glass); border: 1px solid var(--line); }
:is(.alist, .plist):where(:has(> .g:not(.empty), > .pr)) > :is(.g, .pr) { border-width: 0; margin: 0; }
:is(.alist, .plist) > :is(.g, .pr) + :is(.g, .pr) { border-top: 1px solid var(--line); }
/* A selected or landing row keeps its block: :where() skips them without raising the specificity */
:is(.alist, .plist) > .g:not(.empty):where(:not(.sel, .land, [aria-pressed="true"], [aria-current]:not([aria-current="false"]), [aria-selected="true"])),
:is(.alist, .plist) > .pr > .g:where(:not(.sel, .land, [aria-pressed="true"], [aria-current]:not([aria-current="false"]), [aria-selected="true"])) { --feel-plate: transparent; background-color: transparent; }
:is(.alist, .plist) > .pr > .g { border-width: 0; }
:is(.alist, .plist) > .pr > .g + .g { border-left: 1px solid var(--line); }
.tt { flex: 1; min-width: 0; display: grid; grid-template-columns: minmax(0, 1fr); gap: 4px; }
.tm { display: flex; gap: 8px; align-items: baseline; font: var(--t-meta); letter-spacing: var(--t-meta-tr); color: var(--muted); white-space: nowrap; }
/* One way to separate facts on a meta line: relative · date · kind, a dot between them */
.tm > :is(span, b) + :is(span, b)::before { content: "·"; content: "·" / ""; margin-right: 8px; color: var(--dim); font-weight: 500; }
.tm .tri { width: 16px; height: 14px; align-self: center; }
.tm b { color: var(--yellow); }
.tn { font: var(--t-body); letter-spacing: var(--t-body-tr); text-wrap: pretty; }
/* German compounds and pasted references break inside their tile ("anywhere", not "break-word": only it lowers the
   min-content width that grid and flex tracks size to) */
.tn, .rt, .ot, .osub, .arow .sub { overflow-wrap: anywhere; }
/* Overdue is the one urgency on a list: its words go red */
.tm b.late { color: var(--red); }
/* An iPhone calendar item: its calendar's colour as a small square before the calendar's name */
.cdot { display: inline-block; width: 7px; height: 7px; margin-right: 6px; vertical-align: 1px; background: var(--c, var(--muted)); }
/* A calendar's name, a place or a second fact on a meta line gives way before the time does: each ends in an ellipsis
   at the tile's edge, never cut mid-letter, even after bold words or the triangle. The bold words keep their width; the
   first span (the time) gives way only once the others are gone. Shrink factors stay at 1 or more (under 1, flexbox
   scales the overflow by their sum and the text is cut again), and the others' are 100000 times the time's: at 100 the
   time took a pixel's share, and that sliver was enough for an ellipsis to eat "00" from "18:00" */
:is(.prow, .trow, .arow) .tm { min-width: 0; overflow: hidden; }
:is(.prow, .trow, .arow) .tm > span { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
:is(.prow, .trow, .arow) .tm > span:not(:first-of-type) { flex-shrink: 100000; }
/* The calendar's name goes first (its colour still says which one), down to its square and the ellipsis. With a place
   the square leads the place and the name is for the screen reader (today.js calSpan), so the place keeps its room */
:is(.prow, .trow, .arow) .tm > span:has(> .cdot) { flex-shrink: 400000; min-width: 2em; }
/* A row's words fold to three lines (all of them show without script) */
.clamp { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3; overflow: hidden; }
/* A plain row: glyph, title, one line under it */
.arow { display: flex; align-items: flex-start; gap: 12px; width: 100%; min-height: 52px; padding: 12px var(--pad); text-align: left; }
.arow .ico { margin-top: 1px; }
.arow .tt { gap: 2px; }
/* A timed row: glyph, the time line, the title */
.prow { display: grid; grid-template-columns: 24px minmax(0, 1fr); gap: 12px; align-items: start; width: 100%; min-height: 52px; padding: 12px var(--pad); text-align: left; }
.prow .tm { margin-bottom: 4px; }
.prow .tn { display: block; }
.prow.legal .tm { color: var(--ink); }
.prow.active { box-shadow: inset 0 0 0 1px var(--yellow); }
.prow.active .tn { color: var(--yellow); }
.prow.donei .tn { color: var(--dim); text-decoration: line-through; text-decoration-thickness: 1.5px; }
.prow.past { color: var(--dim); }
/* Empty state: the most see-through tile, its words from the left like every row, one line where it can. An empty
   slot's words take the meta ink (muted would be 3.2:1 over the corridor floor) */
.nothing { display: grid; place-items: center start; align-content: center; width: 100%; min-height: 56px; padding: 12px var(--pad); color: var(--ink); text-align: left; }
.nothing .k { display: block; color: var(--meta-ink); }
.nothing .tn { display: block; margin-top: 4px; }
.prow.nothing { grid-template-columns: auto; }
/* The quiet slot at the end of a list: add something there (the most see-through tile, words in meta ink) */
.addslot { display: flex; align-items: center; gap: 12px; width: 100%; min-height: 52px; padding: 0 var(--pad); }
.addslot .ico { width: 16px; height: 16px; margin: 0 4px; color: var(--meta-ink); }
.addslot .k { color: var(--meta-ink); }
/* A row with its own key beside it (Done, Transcribe): the row opens the thing, the key acts. One hairline between */
.pr { display: grid; grid-template-columns: minmax(0, 1fr) auto; }
.pr > :first-child { border-right-width: 0; }
/* Done, on its way: the words dim and are struck through, and a row without a key of its own takes the teal hairline.
   Nothing lands on it. A beat later it leaves its list (ui.collapse) or settles struck on its day */
.struck :is(.rt, .tn) { color: var(--dim); text-decoration: line-through; text-decoration-thickness: 1.5px; transition: color 160ms linear; }
.rest.struck :is(.rt, .rm b) { color: var(--paper-muted); }
.rest.struck .rlit { opacity: 0.35; }
.rest.struck { box-shadow: inset 0 0 0 2px var(--teal-paper); }
:is(.trow, .arow, .prow).struck { box-shadow: inset 0 0 0 1px var(--teal); }

/* ---------- Paper: the Bureau document. Stays light on every world and is set in Newsreader, so it reads as a real
   letter. Interface on it (the tap hints, Dust's footnote, the numbers) keeps the interface face. ---------- */
.paper { position: relative; background: var(--paper); color: var(--on-paper); padding: 22px 20px 20px; font: var(--t-paper); --ring: var(--on-paper); --ring-halo: var(--paper); --feel-ok: var(--teal-paper); }
.phead { display: grid; grid-template-columns: minmax(0, 1fr); gap: 12px; align-items: start; }
.org { font: var(--t-paper-org); color: var(--paper-muted); }
.ptl { display: block; margin-top: 8px; font: var(--t-paper-title); letter-spacing: -0.005em; text-wrap: balance; }
/* German compounds (Einkommensteuerbescheid) break inside the column instead of running off the paper */
.paper :is(.org, .ptl) { overflow-wrap: anywhere; -webkit-hyphens: auto; hyphens: auto; }
/* The deadline as a countdown, between two hairlines under the title; the triangle when it is within a week */
div.due { display: flex; align-items: center; gap: 10px; min-height: 58px; margin-top: 16px; padding: 6px 0; border-block: 1px solid rgb(21 21 21 / 0.2); font: var(--t-key); letter-spacing: var(--t-key-tr); }
div.due .dt { flex: 1; min-width: 0; }
div.due .tri { width: 17px; height: 15px; }
/* An entry on paper: the time line in the interface face, the words in the paper's, as written */
.paper .emeta { font: var(--t-meta); letter-spacing: var(--t-meta-tr); color: var(--paper-muted); font-variant-numeric: tabular-nums; }
.paper .etext { margin-top: 8px; white-space: pre-wrap; overflow-wrap: anywhere; }
.u { display: block; margin-top: 14px; font: var(--t-paper-head); text-decoration: underline; text-underline-offset: 3px; text-decoration-thickness: 1px; }
.paper > .u:first-child { margin-top: 0; } /* a draft opens on its "Draft:" line */
.paper p { margin-top: 6px; text-wrap: pretty; }
.paper :is(ol, ul) { margin: 6px 0 0; padding-left: 1.3em; }
.paper li + li { margin-top: 4px; }
.paper .pfoot { margin-top: 16px; padding-top: 10px; border-top: 1px solid rgb(21 21 21 / 0.2); font: var(--t-meta); letter-spacing: var(--t-meta-tr); color: var(--paper-muted); }
/* A secret is a plain black block at text height in the line; a tap slides it off and copies the value. Its hit area
   reaches 44px without moving it */
.secret { position: relative; display: inline-flex; align-items: center; min-height: var(--hit); margin: -11px 0 -11px -4px; padding: 0 4px; vertical-align: middle; text-align: left; --feel-plate: transparent; --feel-ink: var(--ink-hit); --feel-on-ink: var(--paper); }
.secret .v { position: relative; overflow: hidden; padding: 0 3px; font: var(--t-key); letter-spacing: var(--t-key-tr); white-space: nowrap; }
.secret .bar { position: absolute; inset: 0; background-color: var(--on-paper); transition: transform 260ms var(--ease-out); }
.secret.open .bar { transform: translateX(104%); }
.paper .ref { display: grid; justify-items: start; gap: 4px; margin-top: 12px; }
.paper .rk { font: var(--t-meta); letter-spacing: var(--t-meta-tr); color: var(--paper-muted); }
/* The paper's reference line under the title: when Dust read it, the letter's date, its pages (Control's document
   numbers). Here, not in post.css: Scan shows the same paper */
.paper .pref { margin-top: 8px; font: 500 13px/1.35 var(--t-face); color: var(--paper-muted); }
/* A key on paper: dark ink in a dark hairline, the interface face like all interface on paper. The hit inverts to the dark
   block (as the secret bar does); done is the paper teal, so it keeps its contrast on the light ground */
.pkey { display: inline-flex; align-items: center; justify-content: center; gap: 8px; min-height: var(--hit); padding: 0 12px; border: 1px solid rgb(21 21 21 / 0.35); font: var(--t-key); letter-spacing: var(--t-key-tr); white-space: nowrap; --feel-plate: transparent; --feel-ink: var(--ink-hit); --feel-on-ink: var(--paper); }
@media (hover: hover) {
  .pkey:is(.feel, [data-feel]):not(:disabled, [aria-disabled="true"], [aria-busy="true"], [data-feel-done]):hover { border-color: var(--on-paper); background-color: rgb(21 21 21 / 0.07); }
}
/* Reading: bars sweep where the summary will be; .read swaps them for .sum, .failed stops them */
.reading .bars { display: grid; gap: 7px; margin-top: 12px; }
.reading .bars i { height: 17px; background-color: var(--on-paper); background-image: repeating-linear-gradient(135deg, rgb(228 228 225 / 0.3) 0 1.5px, transparent 1.5px 6px); background-size: calc(100% + 8.49px) 100%; background-repeat: no-repeat; animation: sweep 640ms linear infinite; }
.reading .bars i:nth-child(2) { width: 92%; }
.reading .bars i:nth-child(3) { width: 60%; }
.reading .rk { margin-top: 10px; }
.paper.failed .bars i { animation: none; background-image: none; opacity: 0.35; }
.paper.read .reading { display: none; }
/* A redaction over words that are not ready yet; .open wipes it off */
.redact { color: transparent; background: linear-gradient(var(--on-paper) 0 0) right / 100% 100% no-repeat; -webkit-box-decoration-break: clone; box-decoration-break: clone; transition: background-size 260ms var(--ease-out); }
.redact.open { color: inherit; background-size: 0% 100%; }
.tray .paper { margin-top: 14px; }
/* Keys attached under a document: the likely answer is the off-white block */
.attach { display: grid; grid-template-columns: 1fr 1fr; gap: var(--gap); margin-top: var(--gap); }
.attach.stack { grid-template-columns: 1fr; }
.attach .btn { white-space: normal; text-align: center; line-height: 1.2; }
/* Edit turns a draft into a field on the same paper */
.draft-edit { display: block; width: 100%; min-height: 250px; margin-top: 8px; padding: 0; border: 0; border-radius: 0; background: transparent; color: var(--on-paper); font: var(--t-paper); resize: vertical; }
.draft-edit:focus-visible { outline: 2px solid var(--on-paper); outline-offset: 6px; }
/* A finger gets a dark writing line under the words, as a field on the world gets its yellow line; the ring is for
   keyboard users. An error is the red edge every field gets */
.draft-edit:focus { box-shadow: inset 0 -2px 0 var(--on-paper); }
:root:not([data-input="key"]) .draft-edit:focus-visible { outline: none; }
.draft-edit[aria-invalid="true"] { box-shadow: inset 0 0 0 2px var(--red-mark); }

/* ---------- Today: the date in the HUD (see HUD), the strip, one objective card, the day, the untimed things at its end ---------- */
/* The objective: the most urgent deadline as one compact glass card. Yellow title, days left as the figure, and the
   seven days from today as a fuse burning toward the deadline: today's segment drains with the clock (--burn), the
   debit and the legal deadline sit above their days. Overdue drops the fuse for one red line (today.css). The whole
   card opens the letter. */
.obj { display: grid; gap: 3px; width: 100%; margin-top: 12px; padding: 12px 14px 11px; text-align: left; }
.ohead { display: flex; align-items: center; gap: 10px; }
.ot { min-width: 0; font: var(--t-title); letter-spacing: var(--t-title-tr); color: var(--yellow); text-wrap: balance; }
.osub { padding-left: 21px; font: var(--t-meta); letter-spacing: var(--t-meta-tr); color: var(--meta-ink); }
.ofoot { display: flex; align-items: flex-end; gap: 18px; margin-top: 6px; padding-left: 21px; }
.cd { flex: none; display: flex; align-items: baseline; gap: 7px; padding-bottom: 20px; }
.cd b { font: var(--t-number); letter-spacing: var(--t-number-tr); color: var(--ink); }
.cd small { font: var(--t-meta); letter-spacing: var(--t-meta-tr); color: var(--meta-ink); }
.cd .okc { width: 26px; height: 26px; align-self: center; color: var(--teal); }
.wfuse { flex: 1; min-width: 0; display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: 3px; }
.ws { display: grid; grid-template-rows: 15px 8px auto; row-gap: 5px; }
.wm { position: relative; }
.wb { position: relative; display: block; background: var(--yellow); }
.ws.today .wb { background: linear-gradient(90deg, var(--faint) var(--burn, 42%), var(--yellow) var(--burn, 42%)); }
.spark { position: absolute; left: var(--burn, 42%); top: -3px; bottom: -3px; width: 2px; margin-left: -1px; background: var(--ink-hi); }
.wn { font: var(--t-meta); letter-spacing: var(--t-meta-tr); text-align: center; color: var(--muted); }
.ws.today .wn { color: var(--ink); }
.ws.due .wn { color: var(--red); }
.wm .tri { position: absolute; left: 50%; bottom: 0; translate: -50% 0; width: 15px; height: 13px; }
/* Filed: the same card turns teal and the fuse goes quiet */
.obj.ok { border-color: var(--teal); }
.obj.ok :is(.ot, .cd small) { color: var(--teal); }
.obj.ok .dia { border-color: var(--teal); background-image: linear-gradient(var(--teal) 0 0); }
.obj.ok :is(.wb, .ws.today .wb) { background: var(--faint); }
.obj.ok :is(.spark, .wm .tri) { display: none; }
.obj.ok .ws.due .wn { color: var(--muted); }
.obj:is([data-feel-state="hit"], [data-feel-state="armed"]) :is(.ot, .osub, .cd b, .cd small, .wn) { color: inherit; }
.obj:is([data-feel-state="hit"], [data-feel-state="armed"]) :is(.wb, .ws.today .wb) { background: var(--on-paper); }
.obj:is([data-feel-state="hit"], [data-feel-state="armed"]) .dia { border-color: var(--on-paper); background-image: linear-gradient(var(--on-paper) 0 0); }
/* The day. Tile height is time (1.2px a minute), at least 60px and at least the tile's own words; the page script
   sets each .row's top and height. Free time is a quiet hatched line that says until when and what comes then.
   Earlier things dim. The running item is the off-white block with its fuse, Done and Snooze under it. Left of it the
   scale: a 1x6px tick at each hour where the day's axis puts it, the hour beside it */
.tlsec { margin-top: 16px; }
.tl { position: relative; --axis: 30px; }
.row { position: absolute; left: var(--axis); right: 0; }
.hr { position: absolute; left: 0; width: var(--axis); height: 1px; pointer-events: none; }
.hr::after { content: ""; position: absolute; right: 0; top: 0; width: 6px; height: 1px; background: var(--line-hover); }
.hr b { position: absolute; right: 11px; top: 0; translate: 0 -50%; font: 600 12px/1 var(--t-face); color: var(--muted); text-shadow: var(--halo); font-variant-numeric: tabular-nums; }
.trow { position: absolute; inset: 0; display: flex; align-items: flex-start; gap: 12px; padding: 12px var(--pad); text-align: left; overflow: hidden; }
.trow .ico { margin-top: 1px; }
.trow.past { background-color: var(--glass-dim); border-color: transparent; color: var(--dim); }
.trow.past .tm { color: var(--dim); }
.trow.appt { cursor: default; }
.free { position: absolute; left: var(--axis); right: 0; display: flex; align-items: center; padding: 0 var(--pad); }
.free .k { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font: var(--t-meta); letter-spacing: var(--t-meta-tr); color: var(--meta-ink); }
.free.gone { opacity: 0.45; }
.run { display: flex; flex-direction: column; height: 100%; }
.drain { flex: none; display: flex; align-items: center; justify-content: space-between; padding: 0 var(--pad); border-bottom: 0; }
.ntag { display: flex; align-items: center; gap: 8px; font: var(--t-meta); letter-spacing: var(--t-meta-tr); color: var(--yellow); }
.from { font: var(--t-meta); letter-spacing: var(--t-meta-tr); color: var(--muted); }
.rest { position: relative; flex: none; display: grid; align-content: start; gap: 4px; padding: 12px 14px 14px; background: var(--paper); color: var(--on-paper); }
.rt { font: var(--t-title); letter-spacing: var(--t-title-tr); text-wrap: balance; }
.rm { display: flex; align-items: baseline; gap: 8px; font: var(--t-meta); letter-spacing: var(--t-meta-tr); color: var(--paper-muted); }
.rm b { font-weight: 700; color: var(--on-paper); }
/* The running item's fuse: a segment per 5 minutes (--n), spent on the left, draining in real time (the page script
   sets a clip-path transition as long as the time that is left) */
.rfuse { position: relative; height: 6px; margin: 8px -3px 0 0; color: rgb(21 21 21 / 0.16); background: linear-gradient(90deg, currentColor 0 calc(100% - 3px), transparent 0) 0 0 / calc(100% / var(--n, 9)) 100% repeat-x; }
.rlit { position: absolute; inset: 0; background: linear-gradient(90deg, var(--on-paper) 0 calc(100% - 3px), transparent 0) 0 0 / calc(100% / var(--n, 9)) 100% repeat-x; }
.acts { flex: none; display: grid; grid-template-columns: 1fr 1fr; min-height: 52px; }
.acts .act + .act { border-left-width: 0; }
.act { min-height: 52px; padding: 8px 4px; display: flex; align-items: center; justify-content: center; gap: 10px; font: var(--t-key); letter-spacing: var(--t-key-tr); overflow-wrap: anywhere; }
/* The now line: yellow, with the objective's diamond at its start and the time (and the free time left) on it */
.now { position: absolute; z-index: 4; left: var(--axis); right: 0; height: 1px; background: var(--yellow); pointer-events: none; }
.now::before { content: ""; position: absolute; left: -5px; top: -5px; width: 11px; height: 11px; border: 1.5px solid var(--yellow); background: linear-gradient(var(--yellow) 0 0) center / 4px 4px no-repeat, var(--world); rotate: 45deg; }
.now .ntag { position: absolute; left: 14px; right: var(--pad); bottom: 7px; text-shadow: var(--halo); }
.now .ntag > span:first-child { white-space: nowrap; }
.now.below .ntag { bottom: auto; top: 8px; }
.nfree { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--meta-ink); }
.nfree:empty { display: none; }
.now.inrun .ntag { display: none; }
/* Anytime: plain rows at the end of the day */
.anysec { margin-top: var(--sp-section); }
.anysec .alist { margin-top: var(--sp-head); }

/* ---------- Plan: the week as seven cells, the picked day as a list of the same rows ---------- */
.pday { margin: 22px 0 10px; }
/* Week strip: seven cells sharing 1px rules inside one glass row; the picked day is the off-white block */
.week { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); }
.wd { min-height: 66px; padding-block: 3px; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px; text-align: center; }
.wd + .wd { border-left: 1px solid var(--line); }
.wdn { font: var(--t-meta); letter-spacing: var(--t-meta-tr); color: var(--muted); }
.wd b { font: var(--t-title); letter-spacing: var(--t-title-tr); line-height: 1; } /* digits only, in a cell of at least 66px */
.mk { display: flex; gap: 5px; height: 12px; align-items: center; }
.mk i { width: 7px; height: 7px; background: var(--muted); }
.mk .tri { width: 14px; height: 12px; }
.mk .m-debit { background: var(--ink); }
.mk .m-appt { width: 7px; height: 7px; background: var(--yellow); rotate: 45deg; }
.wd .tbar { position: absolute; top: 0; left: 0; right: 0; height: 3px; background: var(--yellow); }

/* ---------- Post: letter tiles (the open one is the off-white block), then the document ---------- */
.ltile { color: var(--muted); display: grid; grid-template-columns: 16px minmax(0, 1fr) auto; align-items: center; gap: 12px; width: 100%; min-height: 72px; padding: 10px var(--pad); text-align: left; }
.ltile > .ico { width: 16px; height: 16px; align-self: start; margin-top: 4px; color: var(--meta-ink); }
.ltile:is([data-feel-state="hit"], [data-feel-state="armed"]) > .ico { color: inherit; }
.ltile + .ltile, .ltiles + .ltiles { margin-top: var(--gap); }
.ltile b { display: block; font: var(--t-body); letter-spacing: var(--t-body-tr); color: var(--ink); text-wrap: pretty; }
.ltile:is([aria-pressed="true"], [data-feel-state="hit"]) b { color: inherit; }
/* The open letter's tile: lit glass with a 3px off-white edge, so the paper under it is the one light thing */
.ltile.ltile[aria-pressed="true"]:not([data-feel-state="hit"], [data-feel-state="armed"]) { background-color: var(--glass); color: var(--ink-hi); border-color: var(--line-hi); box-shadow: inset 3px 0 0 var(--paper); }
.ltile[aria-pressed="true"]:not([data-feel-state="hit"], [data-feel-state="armed"]) .sub { color: var(--meta-ink); }
.ltile .sub { display: block; margin-top: 2px; text-wrap: pretty; }
.ltile .tri { width: 19px; height: 16px; }
.letter { margin-top: 18px; }
.letter.in { animation: paper-in 200ms var(--ease-out) both; }

/* ---------- Ask: the astral plane at dusk, a transcript (its own look lives in screens/ask.css). The field with its
   key below is shared: Search, Files and Memories set their search fields with it ---------- */
.screen.ask { display: flex; flex-direction: column; justify-content: flex-end; }
.askform { display: grid; grid-template-columns: minmax(0, 1fr) 54px; margin-top: 12px; }
.askform .field { border-right: 0; }
.send { display: grid; place-items: center; }

/* ---------- More: switch rows, the connected slots, and what the picked slot describes ---------- */
.more-h { margin: 26px 0 10px; }
/* Connected: material slots, glyph centred, count chip in the corner, the name under it */
.slots { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--gap); }
/* A last tile alone on its row takes the whole row (seven tiles: Settings), in three columns and in two */
.slots > .slot:last-child:nth-child(3n + 1) { grid-column: 1 / -1; }
:root[data-large-text] .slots > .slot:last-child:nth-child(3n + 1) { grid-column: auto; }
:root[data-large-text] .slots > .slot:last-child:nth-child(odd) { grid-column: 1 / -1; }
.slot { display: grid; grid-template-rows: 76px auto; align-content: start; min-height: 128px; padding: 0 0 12px; text-align: left; }
.slot .glyph { position: relative; display: grid; place-items: center; }
.slot .glyph .ico { width: 40px; height: 40px; }
.slot .chip { position: absolute; right: 18px; bottom: 6px; }
.slot .nm { padding: 0 10px; font: var(--t-body); letter-spacing: var(--t-body-tr); }
.slot:is([data-feel-state="hit"], [aria-pressed="true"]) .chip { background: var(--on-paper); color: var(--paper); }
.info { margin-top: 18px; display: grid; gap: 6px; }
.info + .setrow { margin-top: 26px; }
.info .iv { font: var(--t-body); letter-spacing: var(--t-body-tr); color: var(--ink); text-shadow: var(--halo); }
.info .k { font: var(--t-meta); letter-spacing: var(--t-meta-tr); color: var(--meta-ink); text-shadow: var(--halo); }

/* ---------- Talk: the voice tray. Words form as a subtitle line over a segmented level bar ---------- */
.talk-live { margin-top: 10px; }
.caret { display: inline-block; width: 9px; height: 18px; margin-left: 3px; vertical-align: -3px; background: var(--ink); animation: blink 1s steps(1) infinite; }
.lvl { display: grid; grid-template-columns: repeat(20, minmax(0, 1fr)); gap: 3px; height: 12px; margin-top: 14px; }
.lvl i { background: var(--faint); }
.lvl i.on { background: var(--ink); }

/* Digits that line up: times on a time line, counts that roll, amounts in a column. The font shorthand resets the
   numeric variant, so it is set again after the roles. Everything else keeps proportional figures, so "11", "1 h 10 min"
   and a date read without gaps */
:is(.cnt, .ctr b, .chip, .ucount, .tm, .ntag, .from, .rm b, .num) { font-variant-numeric: tabular-nums; }

/* ---------- States: one system for every control ----------
   rest; hover (hover pointers only); focus-visible (the ring, see Base); pressed (feel.js: down, hit, up);
   selected (aria-pressed, aria-current, aria-selected, .sel); disabled (disabled or aria-disabled); loading
   (aria-busy, set by ui.setBusy); success (data-feel-done, set by Feel.done); error (aria-invalid, Feel.fail). */
/* Hover, only where a pointer can hover: the border and the ink brighten, the hatch lifts */
@media (hover: hover) {
  :is(.g, .gc, .prompt, .ctr):is(.feel, [data-feel]):not(:disabled, [aria-disabled="true"], [aria-busy="true"]):hover { --lift: 1; }
  .g:is(.feel, [data-feel]):not(:disabled, [aria-disabled="true"], [aria-busy="true"], [aria-pressed="true"], [aria-current], [aria-selected="true"], .sel, [data-feel-done]):hover { border-color: var(--line-hover); }
  :is(.g, .gc, .prompt, .ctr):is(.feel, [data-feel]):not(:disabled, [aria-disabled="true"], [aria-busy="true"], [aria-pressed="true"], [aria-current], [aria-selected="true"], .sel, [data-feel-done]):hover { color: var(--ink-hi); }
  :is(.g, .gc):is(.feel, [data-feel]):not(:disabled, [aria-disabled="true"], [aria-pressed="true"], [aria-current], [aria-selected="true"], .sel):hover :is(.sub, .tm, .k, .from, .wdn, small) { color: var(--meta-ink); }
  .ltile:is(.feel, [data-feel]):not([aria-pressed="true"]):hover b { color: var(--ink-hi); }
  .prompt:is(.feel, [data-feel]):not(:disabled, [aria-disabled="true"]):hover .key { background: var(--ink-hi); }
  :is(.pri, [aria-pressed="true"], [aria-current]:not([aria-current="false"]), .sel):is(.feel, [data-feel]):not(:disabled, [aria-disabled="true"], [aria-busy="true"], [data-feel-done], .seg > *, .ltile):hover { background-color: var(--paper-hi); border-color: var(--paper-hi); }
  .secret:is(.feel, [data-feel]):not(.open):hover .bar { background-image: repeating-linear-gradient(135deg, rgb(228 228 225 / 0.3) 0 1px, transparent 1px 3.5px); }
  .secret:is(.feel, [data-feel]).open:hover .v { text-decoration: underline; text-underline-offset: 3px; }
  .obj:is(.feel, [data-feel]):hover .ot { color: #FFE440; }
  /* An empty slot is the most see-through tile: hovered, all of its words brighten (its hatch lifts behind them) */
  .empty:is(.feel, [data-feel]):not(:disabled, [aria-disabled="true"]):hover :is(.k, .sub, .tm) { color: var(--ink-hi); }
}
/* Disabled: dim glass and dim ink, no stripe (the hatch means an empty slot), no feel, out of the tab order */
:is(.feel, [data-feel]):is(:disabled, [aria-disabled="true"]):not([aria-busy="true"]) { cursor: not-allowed; color: var(--off-ink); background-color: var(--glass-dim); background-image: none; border-color: rgb(214 218 222 / 0.12); }
/* A disabled switch shows neither value lit */
[role="switch"]:is(:disabled, [aria-disabled="true"]) .seg2 :is(.s-on, .s-off) { border-color: rgb(214 218 222 / 0.12); color: inherit; }
:is(.feel, [data-feel]):is(:disabled, [aria-disabled="true"]):not([aria-busy="true"]) :is(.sub, .k, small, .ring) { color: inherit; }
:is(.feel, [data-feel]):is(:disabled, [aria-disabled="true"]):not([aria-busy="true"]) .key { background: var(--off-ink); }
.field:disabled { color: var(--off-ink); background-color: var(--glass-dim); background-image: none; border-color: rgb(214 218 222 / 0.12); }
/* Loading: the key locks, the hatch sweeps across it, the label says what is happening */
:is(.feel, [data-feel])[aria-busy="true"] { cursor: progress; pointer-events: none; }
:is(.feel, [data-feel])[aria-busy="true"][aria-busy="true"] { color: var(--ink); background-color: var(--glass); border-color: var(--line-hi); background-image: var(--sweep-img); background-size: calc(100% + 8.49px) 100%; background-repeat: no-repeat; animation: sweep 640ms linear infinite; }
.feel[aria-busy="true"][data-feel-state="up"] { animation: feel-pop var(--feel-up) var(--feel-pop) both, sweep 640ms linear infinite; }
/* Over the sweep's light stripes the small lines take the brighter meta ink (muted would be 3.5:1) */
:is(.feel, [data-feel])[aria-busy="true"] :is(.sub, .k, .tm, .from, small) { color: var(--meta-ink); }
/* Success: teal (the kit's done) and the glyph settles in softly after the hit */
:is(.feel, [data-feel])[data-feel-done] .ico { animation: settle 200ms var(--ease-out) var(--feel-hit) both; }
/* Error: a red edge on the control at fault; the kit nudges it and plays the error tone */
:is(.feel, [data-feel], .field)[aria-invalid="true"] { border-color: var(--red-mark); box-shadow: inset 0 0 0 1px var(--red-mark); }
/* Last word: the hit is always the inverted block, whatever state colour a tile had */
.feel.feel:is([data-feel-state="hit"], [data-feel-state="armed"]) { background-color: var(--feel-ink); color: var(--feel-on-ink); }

/* ---------- Motion ---------- */
@keyframes tray-in { from { transform: translateY(56px); opacity: 0; } 35% { opacity: 1; } }
@keyframes tray-out { to { transform: translateY(40px); opacity: 0; } }
@keyframes band-in { from { transform: translateY(8px); opacity: 0; } }
@keyframes paper-in { from { transform: scale(0.98); opacity: 0; } }
@keyframes settle { from { scale: 0.7; opacity: 0; } }
@keyframes pop-in { from { transform: translateX(60px); opacity: 0; } }
@keyframes rise { from { translate: 0 8px; opacity: 0; } }
@keyframes fade { from { opacity: 0; } }
@keyframes fade-out { to { opacity: 0; } }
@keyframes blink { 50% { opacity: 0; } }
@keyframes sweep { from { background-position: -8.49px 0; } to { background-position: 0 0; } }
/* Between pages: a straight cut to the new page, like Control's menus, with no cross-document view transition. One
   held the old page 150 to 250 ms longer before the new one could paint (measured 09.10.2026), so a switch felt slow.
   The plate's inline copy covers the moment before it is decoded. The view-transition-names stay: they keep the HUD
   and the dock backdrop roots (see the rim) */
/* Reduced motion: movement becomes short opacity changes; the feedback (colour, strike, sound) stays */
@media (prefers-reduced-motion: reduce) {
  .world::before { animation: none; }
  .tray, .pop, .band, .letter.in, .scrim, .errline { animation: fade var(--t-fade) linear both; }
  .tray.out, .scrim.out { animation: fade-out var(--t-fade) linear both; }
  .tray.sheet:not(.drag) { transition: none; }
  .pops::before { transition-duration: var(--t-fade); }
  .secret .bar, .redact { transition: none; }
  .caret, .sweep, .band.work .subl::after, .reading .bars i, :is(.feel, [data-feel])[aria-busy="true"][aria-busy="true"] { animation: none; }
  :is(.feel, [data-feel])[data-feel-done] .ico { animation: fade var(--t-fade) linear both; }
  .pull.work { animation: none; }
}

/* Share-sheet uploads (base.html, dust:inbox): the band carries a 2px bar under its words that fills as files go up
   (the counts come from ios-app/inbox.ts) */
.toast.inbox { display: block; min-width: min(100%, 17em); font-variant-numeric: tabular-nums; }
.toast.inbox::after { content: ""; display: block; height: 2px; margin-top: 10px; background: linear-gradient(var(--paper) 0 0) 0 0 / calc(var(--p, 0) * 100%) 100% no-repeat, var(--faint); transition: background-size var(--t-roll) var(--ease-out); }
@media (prefers-reduced-motion: reduce) { .toast.inbox::after { transition: none; } }
