/* Base shell styles. All styling lives here — CSP forbids inline style/script. */

*,
*::before,
*::after {
  box-sizing: border-box;
  margin: 0;
}

html {
  color-scheme: dark;
}

body {
  background: var(--bg);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 15.5px;
  line-height: 1.45;
  min-height: 100vh;
  -webkit-font-smoothing: antialiased;
}

a {
  color: inherit;
  text-decoration: none;
}

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

/* The wordmark font — the ONE sanctioned webfont (DESIGN): self-hosted
   IBM Plex Mono 600 subset, used nowhere else. */
@font-face {
  font-family: "Plex Wordmark";
  src: url("../fonts/plex-mono-600.e71a597e376e.woff2") format("woff2");
  font-weight: 600;
  font-display: swap;
}

/* Benchtop mark (chosen 2026-08-01): the word stands on its amber bench. */
.wordmark {
  display: inline-block;
  font-family: "Plex Wordmark", var(--mono);
  font-weight: 600;
  font-size: 16px;
  letter-spacing: 0.01em;
  color: var(--ink);
}

.wordmark .bench {
  display: inline-block;
  padding: 0 6px 9px;
  background:
    linear-gradient(var(--accent), var(--accent)) left 12% bottom 0 / 1.5px 5.5px no-repeat,
    linear-gradient(var(--accent), var(--accent)) right 12% bottom 0 / 1.5px 5.5px no-repeat,
    linear-gradient(var(--accent), var(--accent)) left 0 bottom 5px / 100% 2px no-repeat;
}

.label,
.field > label:not(.check-label),
.field-head > label {
  font-family: var(--mono);
  font-size: 10.5px;
  font-weight: 400; /* a group's <h2 class="label"> is the label, not a bold heading */
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-3);
}

/* Bare <label>s in forms get the same treatment as .label — plus air, so
   they read as captions rather than text snugged to the control.
   .check-label is excluded from both rules: a checkbox's label IS its
   interactive text (sans, flex row), not a caption. */
.field > label:not(.check-label) {
  display: block;
  margin-bottom: 6px;
}

.display {
  font-family: var(--serif);
  font-weight: 600;
  font-size: 32px;
  letter-spacing: -0.01em;
}

@media (min-width: 720px) {
  .display {
    font-size: 36px;
  }
}

.card {
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: 16px;
  padding: 24px;
}

.stack > * + * {
  margin-top: 14px;
}

.field .label {
  display: block;
  margin-bottom: 6px;
}

.field input,
.field select,
.field textarea {
  width: 100%;
  background: var(--raised);
  border: 1px solid var(--hairline);
  border-radius: 9px;
  padding: 10px 12px;
  color: var(--ink);
  font: inherit;
}

/* One control height for inputs AND selects — a select's intrinsic macOS
   height ignores padding and renders visibly shorter otherwise. 44px is
   the DESIGN touch target. (Textareas keep their rows; checkboxes stay
   checkbox-sized.) */
.field input:not([type="checkbox"]):not([type="radio"]),
.field select {
  height: 44px;
}

.field textarea {
  resize: vertical;
}

.field input:focus,
.field select:focus,
.field textarea:focus {
  outline: 2px solid var(--accent);
  outline-offset: 0;
  border-color: transparent;
}

/* The touch floor — the ONE place zoom-on-focus is handled. iOS zooms the
   page onto any text control under 16px when it takes focus, and a zoomed
   page also throws off the keyboard maths the capture sheet rides on (#29).
   Desktop keeps the 15–15.5px DESIGN body size; a touch screen gets 16px.

   `!important`, deliberately: this is a platform floor, not a style. A dozen
   component rules size controls (12.5–15.5px) at specificities up to (0,2,1),
   and each new one would otherwise have to remember to opt in — and fail
   silently, on the phone only, when it didn't. The fields are the ones that
   raise a keyboard or a picker — app.js ENTRY_FIELD, mirrored here and in the
   two "a field has focus" rules below (core/tests/test_entry_fields.py keeps
   the three the same). Checkboxes, radios and buttons raise none. */
@media (pointer: coarse) {
  input:not([type]),
  input[type="text"],
  input[type="search"],
  input[type="email"],
  input[type="url"],
  input[type="tel"],
  input[type="number"],
  input[type="password"],
  input[type="date"],
  input[type="datetime-local"],
  input[type="time"],
  textarea,
  select {
    font-size: 16px !important;
  }
}

.field input[type="date"] {
  display: block;
  min-width: 0; /* NO appearance:none — it strips the native control on iOS
                   and leaves a blank textarea-looking box */
}

/* iOS lays the VALUE out in its own inner box, sized to content and centred,
   and that box — not the input — is what used to push the control past the
   form (ARCHITECTURE §3). Constrain the box and the field behaves, with the
   native picker intact. Ignored everywhere that isn't WebKit. */
.field input[type="date"]::-webkit-date-and-time-value {
  text-align: left;
  min-width: 0;
}

/* The face and the real control, stacked. The INPUT sits on top, invisible
   but tappable: iOS opens its picker for a genuine tap on a date input, which
   is the one behaviour that is guaranteed — showPicker() on a collapsed input
   silently did nothing (2026-08-14). The wrapper is the positioning context
   the first attempt lacked (the input had only a sibling, so it was absolute
   against nothing) and carries the 44px target, so the visible chip can stay
   chip-sized. */
.date-field {
  position: relative;
  display: inline-flex;
  align-items: center;
  /* The same box as a .wheel — this is a control sitting beside hh/mm, not a
     chip sitting under one (Jaroslav, 2026-08-14). Height and treatment match
     so the datetime row reads as one set of controls. */
  min-height: 44px;
  min-width: 68px;
  background: var(--raised);
  border: 1px solid var(--hairline);
  border-radius: 9px;
  padding: 10px 12px;
  color: var(--ink);
  font-family: var(--sans);
  font-size: 15px;
  /* Load-bearing: iOS gives a date input an intrinsic width it will not give
     up, and the invisible one hung out past this box to the right — enough to
     make the whole PAGE scroll sideways. Clipping it costs nothing, since the
     tap area is this box. */
  overflow: hidden;
}

/* The focusable element inside is invisible, so its own outline is invisible
   too — this control had no focus indicator at all, on the one path where it
   exists (iPad keyboard, Full Keyboard Access). The ring goes on the wrapper.
   Not clipped by its own overflow: hidden clips descendants, not self. */
.date-field:focus-within {
  outline: 2px solid var(--accent);
  border-color: transparent;
}

/* The compound selector is deliberate: `:not()` takes its argument's
   specificity, so `.field input:not([type="checkbox"]):not([type="radio"])`
   is (0,3,1) and a plainer selector here loses — the overlay silently became
   a hard 44px rather than covering its face. The mechanism rests on that
   cover, so this height has to win. */
.field .date-field input[type="date"],
.date-field input[type="date"] {
  position: absolute;
  inset: 0;
  width: 100%;
  max-width: 100%;
  height: 100%;
  min-width: 0;
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
  opacity: 0;
  cursor: pointer;
}

/* The date when set, a calendar glyph when empty. */
.date-face {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  white-space: nowrap;
}

.date-face.empty {
  color: var(--ink-3);
}

/* On a mouse the field stays a real typeable input (a keyboard beats any
   calendar), just sized to its content instead of eating the row. */
.field input.date-alt {
  width: auto;
  min-width: 150px;
  /* A text input carries an intrinsic width of ~20 characters and flatpickr's
     alt input is a text input, so `width: auto` is ~206px here — wider than a
     third of a form. It overflowed its column and hung past the form's right
     edge, and past the routing card's border (2026-08-15). The field is a full
     line now, but nothing should depend on that to stay inside its box. */
  max-width: 100%;
}

.btn {
  display: inline-block;
  width: 100%;
  background: var(--accent);
  color: var(--accent-ink);
  border: 0;
  border-radius: 12px;
  padding: 12px 16px;
  min-height: 44px; /* touch target per DESIGN */
  font: inherit;
  font-weight: 600;
  cursor: pointer;
}

/* An <a class="btn"> needs the centring a <button> gets for free: with
   inline-block the label sits at the top padding edge, so min-height's
   spare 6px lands underneath it and the text rides high. `align-items`
   does the work. */
a.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  text-decoration: none;
}

.btn:hover {
  filter: brightness(1.06);
}

.btn-compact {
  width: auto;
  min-height: 36px;
  padding: 8px 18px;
  border-radius: 10px;
  font-size: 14px;
}

/* The capture composer: a bordered writing surface, not a form — the
   textarea is naked inside it and the whole card takes the focus ring. */
.composer {
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: 16px;
  padding: 6px 6px 8px;
}

.composer textarea {
  width: 100%;
  background: transparent;
  border: 0;
  outline: none;
  padding: 10px 12px;
  color: var(--ink);
  font: inherit;
  resize: vertical;
}

.composer:focus-within {
  border-color: color-mix(in srgb, var(--accent) 45%, transparent);
}

/* The same notes line in the inbox composer, which pads tighter and sizes
   its text from the body. `font: inherit` matches the textarea beside it
   exactly; on a phone both rise to 16px together (the touch floor). */
.composer .notes-line {
  padding: 10px 12px;
  font: inherit;
  line-height: 1.45;
}

.composer-bar {
  display: flex;
  justify-content: flex-end;
  align-items: center;
  gap: 12px;
  padding: 0 8px 4px;
}

.kbd-hint {
  font-family: var(--mono);
  font-size: 9.5px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-3);
  margin-right: auto;
}

/* Sidebar mini calendar — quiet month grid; edge-week days dimmed, today
   amber, every day a link into /tasks/?day=. */
.minical {
  margin-bottom: 18px;
}

.minical-head {
  margin-bottom: 8px;
}

.minical-grid {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 1px;
}

.minical-wd {
  font-family: var(--mono);
  font-size: 8.5px;
  letter-spacing: 0.08em;
  color: var(--ink-3);
  text-align: center;
  padding-bottom: 3px;
}

.minical-day {
  font-family: var(--mono);
  font-size: 10.5px;
  color: var(--ink-2);
  text-align: center;
  text-decoration: none;
  padding: 3px 0;
  border-radius: 6px;
}

.minical-day:hover {
  background: var(--raised);
  color: var(--ink);
}

.minical-day.out {
  color: var(--ink-3);
  opacity: 0.55;
}

.minical-day.is-today {
  color: var(--accent);
  font-weight: 700;
}

/* Settings: draggable area rows — the dot previews the color select. */
.area-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 0;
  border-bottom: 1px solid var(--hairline);
  cursor: grab;
}

/* The picked day's tasks lead the list in their own raised section. */
.day-sec {
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: 14px;
  padding: 14px 16px 10px;
}

/* Interactive text is SANS; mono is reserved for capitalized informative
   labels (TODAY · W31, section heads, chips) — Jaroslav, 2026-08-01.
   One carve-out: .minical-day links stay mono for tabular calendar digits. */
.btn-quiet {
  background: none;
  border: 0;
  padding: 0;
  font-family: var(--sans);
  font-size: 13.5px;
  line-height: 1.2; /* identical box for <a> and <button> — keeps rows level */
  color: var(--ink-2);
  cursor: pointer;
}

.btn-quiet:hover {
  color: var(--ink);
}

.muted {
  color: var(--ink-2);
}

/* System-voice help/hints — "notes from the shop": serif italic, sentence
   case, quiet ink. Visually distinct so guidance never reads as data
   (Jaroslav, 2026-08-01). */
.note {
  font-family: var(--serif);
  font-style: italic;
  font-size: 14.5px;
  line-height: 1.5;
  color: var(--ink-3);
  margin: 2px 0 14px; /* breathing room before the controls it introduces */
}

.note a {
  color: var(--ink-2);
  text-decoration: underline;
  text-decoration-color: var(--ink-3);
}

/* Area identity: 7px dot always paired with a mono text label (DESIGN).
   One class per approved palette color — inline styles are CSP-banned. */
.dot {
  display: inline-block;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  margin-right: 6px;
  vertical-align: 1px;
}

.c-3987e5 { background: #3987e5; }
.c-d95926 { background: #d95926; }
.c-199e70 { background: #199e70; }
.c-c98500 { background: #c98500; }
.c-d55181 { background: #d55181; }
.c-9085e9 { background: #9085e9; }
.c-8a8f98 { background: #8a8f98; }

.chip-row {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.chip {
  font-size: 13px;
  padding: 6px 10px;
  border: 1px solid var(--hairline);
  border-radius: 8px;
  background: var(--surface);
  color: var(--ink-2);
}

.chip-on {
  color: var(--ink);
  border-color: var(--accent);
}

/* A filter chip is ~33px tall (32.85: 13px × 1.45 + 12 padding + 2 border),
   short of the 44px touch target. The row is navigation and already wraps
   to three lines at 375, so the chips keep their size and grow an invisible
   hit area instead (the .r-cb technique). The overhang is sized to the gaps
   so neighbours never overlap: half the 8px column gap each side, and a row
   gap of 12px for the two ~5.6px vertical overhangs that meet in it — 11
   left them 0.15px into each other. Overlap would not be shared: the later
   chip paints on top and takes every tap in it, the earlier one's included.
   (The extra 4px between wrapped lines is a layout change, touch only.) */
@media (pointer: coarse) {
  .chip-row {
    row-gap: 12px;
  }

  .chip-row .chip {
    position: relative;
  }

  .chip-row .chip::before {
    content: "";
    position: absolute;
    top: calc(50% - 22px);
    bottom: calc(50% - 22px);
    left: -4px;
    right: -4px;
  }
}

.log-row {
  display: flex;
  gap: 12px;
  padding: 10px 0;
  border-bottom: 1px solid var(--hairline);
  font-size: 14px;
}

.log-link:hover {
  text-decoration: underline;
  text-decoration-color: var(--ink-3);
}

.log-meta {
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-3);
  white-space: nowrap;
  padding-top: 3px;
}

.errors {
  color: var(--ink-2);
  font-size: 14px;
}

.danger {
  color: var(--danger);
}

/* ── capture sheet (design/capture-mobile.html) ─────────────────────── */

.scrim {
  position: fixed;
  inset: 0;
  background: rgba(5, 6, 8, 0.55);
  z-index: 50;
}

.sheet {
  position: fixed;
  left: 50%;
  transform: translateX(-50%);
  bottom: 0;
  width: 100%;
  max-width: 640px;
  /* Never taller than what is actually visible. With the keyboard up the
     sheet is lifted by --kb, so 85vh alone left a long one (the routing
     card's Adjust panel) with its top above the screen and no way to scroll
     back to it (#29). --vvh is the visual viewport's height (app.js), and
     the status-bar inset plus a sliver keep the grabber clear of the clock.
     At rest --vvh is the full screen, so 75vh is the cap — DESIGN's "a
     sheet is ≤ 75%" (2026-10-03; the Adjust panel stood at ~83%). With the
     keyboard up the --vvh term is the smaller and wins, as #29 needs. */
  max-height: min(75vh, calc(var(--vvh, 100dvh) - env(safe-area-inset-top, 0px) - 12px));
  overflow-y: auto;
  overscroll-behavior: contain; /* scrolling the sheet never drags the page */
  background: var(--raised);
  border-radius: 22px 22px 0 0;
  border-top: 1px solid var(--hairline);
  box-shadow: 0 -18px 60px rgba(0, 0, 0, 0.6);
  padding: 10px 20px calc(20px + env(safe-area-inset-bottom));
  z-index: 60;
  transition: transform 0.32s cubic-bezier(0.32, 0.72, 0.33, 1);
}

/* Capture sheet visibility is class-driven (never display:none) so its
   textarea can be focused synchronously in the opening gesture — the only
   timing where iOS raises the keyboard. `away` parks it below the viewport. */
.sheet.away {
  transform: translateX(-50%) translateY(112%);
  pointer-events: none;
  box-shadow: none;
}

.sheet.in {
  /* --kb = keyboard height (app.js visualViewport tracking) — the sheet
     rides ABOVE the iOS keyboard instead of opening behind it */
  transform: translateX(-50%) translateY(calc(0px - var(--kb, 0px)));
  /* The home-indicator inset is room under the LAST control; once the
     keyboard is up it covers that strip, and the sheet sits on the
     keyboard instead. Give the inset back to the content then — the sheet
     is capped at the visible height, so every pixel of padding is a pixel
     of composer pushed under the fold (at a 420px keyboard the plain
     composer scrolled). With no keyboard this is the base padding exactly. */
  padding-bottom: max(20px, calc(20px + env(safe-area-inset-bottom, 0px) - var(--kb, 0px)));
}

.sheet.dragging {
  transition: none; /* follow the finger exactly during a pull-down */
}

@media (prefers-reduced-motion: reduce) {
  .sheet {
    transition: none;
  }
}

.grabber {
  width: 38px;
  height: 5px;
  border-radius: 3px;
  background: #33363d;
  margin: 0 auto 14px;
}

.sheet-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 14px;
}

.types {
  display: flex;
  gap: 6px;
  margin-bottom: 14px;
}

.type {
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-2);
  border: 1px solid var(--hairline);
  border-radius: 9px;
  padding: 6px 11px;
  background: transparent;
  cursor: pointer;
}

.type.on {
  background: var(--ink);
  color: var(--bg);
  border-color: var(--ink);
  font-weight: 600;
}

/* The type chips (26px) and `esc` (22×16) are under the 44px target. The
   sheet rides above the keyboard, where every pixel of height is the
   textarea's, so neither grows — each gets an invisible 44px hit area.
   The type chips' sideways overhang is half their 6px gap, so they abut;
   esc sits at the far right, clear of both the chips and the grabber, and
   a touch on it still starts a pull-down (it is inside .sheet-head). */
@media (pointer: coarse) {
  .type,
  .sheet-head > .btn-quiet {
    position: relative;
  }

  .type::before {
    content: "";
    position: absolute;
    top: calc(50% - 22px);
    bottom: calc(50% - 22px);
    left: -3px;
    right: -3px;
  }

  .sheet-head > .btn-quiet::before {
    content: "";
    position: absolute;
    top: calc(50% - 22px);
    left: calc(50% - 22px);
    width: 44px;
    height: 44px;
  }
}

/* The composer is ONE box with two lines: the thought, and a quiet line
   under it for a link or a scrap of context. The border moved here from the
   textarea so the pair reads as a single surface, and the focus ring is
   drawn once around both (:focus-within) rather than jumping between them. */
.input-wrap {
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: 16px;
}

.input-wrap:focus-within {
  outline: 2px solid var(--accent);
  border-color: transparent;
}

.input-wrap textarea,
.notes-line {
  display: block;
  width: 100%;
  background: transparent;
  border: 0;
  color: var(--ink);
  font: inherit;
  font-size: 16px; /* the capture surface's size at every width; zoom is the touch floor's job */
}

.input-wrap textarea:focus,
.notes-line:focus {
  outline: none; /* the wrap carries the ring */
}

.input-wrap textarea {
  padding: 14px 15px;
  min-height: 96px;
  line-height: 1.45;
  resize: vertical;
}

/* Subordinate by weight and room, never by type size — on a phone it could
   not go smaller anyway (the touch floor), and this field exists to take a
   pasted link there. It is not the anchor while formulating; the textarea is. */
.notes-line {
  border-top: 1px solid var(--hairline);
  padding: 11px 15px;
  line-height: 1.3;
  color: var(--ink-2);
}

.notes-line:focus {
  color: var(--ink);
}

.notes-line::placeholder {
  color: var(--ink-3);
}

.route {
  margin-top: 14px;
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-left: 2px solid var(--accent); /* the routing card's mark, sheet and inbox alike */
  border-radius: 16px;
  padding: 13px 15px;
}

/* One frame per capture, never a card inside a card (2026-10-02). `.route`
   frames the router's read only; the Adjust panel and the action row sit
   after it. In the sheet that leaves them the sheet's whole width (335 at
   375, not the 303 inside a frame). On the inbox page the capture's own card
   is the frame: it takes the read-out's amber edge, and the read-out inside
   it draws nothing — the frame-in-a-frame left 253px for seven area chips. */
.capture-card.card {
  border-color: var(--hairline);
  border-left: 2px solid var(--accent);
}

.capture-card.card .route {
  background: none;
  border: 0;
  border-radius: 0;
  padding: 0;
}

/* A task never lands nowhere: with no area confirm_route files it under
   personal, so while the card builds a task its "none" chip goes (routecard
   moves the check to personal). `.js` — without JS nothing would bring the
   chip back for a note, and the server's fallback stands in. Only while a
   personal chip is there to stand in: with personal archived, hiding "none"
   would leave a task's row with nothing checked. */
.js .capture-card[data-kind="task"]:has([name="area_slug"][value="personal"]) .pick:has([name="area_slug"][value=""]) {
  display: none;
}

/* The controls follow the frame now, so its last line — `.note`'s 14px of
   room "before the controls it introduces" — was padding the box's bottom
   on the not-routed card instead. */
.route > :last-child {
  margin-bottom: 0;
}

/* A phone card is 16px in, the same as the quote and a detail panel: at the
   page's 24px the chip rows lost 16px for nothing. */
@media (max-width: 640px) {
  .capture-card.card {
    padding: 16px;
  }
}

/* The confirmation is a notice, not a panel: one line, tighter box, green
   edge. It stays until the next capture replaces it — he has just acted,
   and a receipt that vanishes is a receipt you can miss. */
.route.confirmed {
  border-left-color: color-mix(in srgb, var(--a-rejobs) 70%, transparent);
  padding: 10px 14px;
}

/* While a routing decision is pending, the composer gets out of the way:
   the chips, the textarea and the Capture/mic row all belong to writing a
   NEW capture, and there is exactly one decision to make on screen. They
   come back by themselves — dismiss empties the slot, confirming swaps in
   `.route.confirmed`, and only the pending card carries `.capture-card`. */
.sheet:has(#sheet-route .capture-card) .types,
.sheet:has(#sheet-route .capture-card) .input-wrap,
.sheet:has(#sheet-route .capture-card) .micbar {
  display: none;
}

.route-head {
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--accent);
}

.route-parse {
  margin-top: 9px;
  font-size: 14.5px;
  line-height: 1.45;
}

/* The captured notes line, echoed back on the card. Quiet, and allowed to be
   a bare URL: without overflow-wrap a long address has no break opportunity
   and pushes the whole card off a phone screen. */
.route-note {
  margin-top: 7px;
  font-size: 13.5px;
  line-height: 1.4;
  color: var(--ink-2);
  overflow-wrap: anywhere;
}

/* The margin above separates the line from `.route-head`. The confirmation
   receipt has no head, so there it was separating the text from nothing —
   19px of space above the line against 10px below, and the one line in the
   box sat visibly low. A first child has nothing to be pushed away from. */
.route-parse:first-child {
  margin-top: 0;
}

/* The receipt's title is a link to the thing just created. Underline rather
   than colour: amber is reserved (DESIGN) and the box already carries the
   green edge, so the affordance has to come from the text itself. */
.confirmed .route-parse a {
  text-decoration: underline;
  text-decoration-color: color-mix(in srgb, currentColor 35%, transparent);
  text-underline-offset: 3px;
}

.confirmed .route-parse a:hover {
  text-decoration-color: currentColor;
}

.facts {
  display: flex;
  gap: 8px;
  margin-top: 10px;
  flex-wrap: wrap;
}

.fact {
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-2);
  border: 1px solid var(--hairline);
  border-radius: 7px;
  padding: 4px 8px;
  display: flex;
  align-items: center;
  gap: 6px;
}

.fact .dot {
  margin-right: 0;
}

.actions {
  display: flex;
  gap: 8px;
  margin-top: 12px;
}

/* `class="actions"` exists in exactly two places, and BOTH override the
   stretch: a routing card sizes its buttons to their labels (the stretched
   twins wrapped their labels and buried Dismiss — see .route-actions below),
   and so does the resurfacing card, where the quote is the point and the
   buttons are an aside. So `flex: 1` here had no consumers; deleting it beats
   adding a third override, and takes a source-order trap with it. */
.actions .btn {
  width: auto;
}

/* Routing card: one calm row — Add task · Adjust · Dismiss (right). The
   old stretched twins wrapped their labels and buried dismiss below. */
/* Left-aligned on a wide screen like the rest of the card. Dismiss, the
   escape, is `.route-escape`, pinned to the far left (One-handed layout ›
   Action rows split in two). */
.route-actions {
  align-items: center;
  /* Without wrap the overflow escapes LEFT — right-aligned, the first child
     (Dismiss) walks off the viewport instead of the row growing a line. At
     375 the children already need 254.8px in a 253px box. */
  flex-wrap: wrap;
  gap: 10px;
}

.route-actions .btn {
  flex: 0 0 auto;
  /* 11px keeps the most-tapped control in the app at the 44px DESIGN
     minimum; min-height:0 had it at 42.5. */
  padding: 11px 18px;
  white-space: nowrap;
  border-radius: 10px;
}

/* When the card sat inside a card (~253px at 375), Dismiss · Adjust ·
   Add task needed 255 and Add task wrapped onto a line by itself. Narrower
   SIDES fit it; the gap stays, because distance is what keeps Dismiss away
   from the thumb, and the 44px height is not negotiable either. The row is
   301px on the inbox card and 335 in the sheet now (2026-10-02) — the
   narrower sides stay as headroom for a longer label. */
@media (max-width: 640px) {
  .route-actions .btn {
    padding-left: 14px;
    padding-right: 14px;
  }
}

.btn-ghost {
  background: transparent;
  color: var(--ink-2);
  border: 1px solid var(--hairline);
}

.adjust {
  margin-top: 12px;
}

/* Capture actions sit AFTER the thought, quiet and right-aligned — the
   textarea is the only visual anchor while formulating (2026-08-01 round:
   "huge and bright" buttons distract during cognitively demanding capture). */
.micbar {
  display: flex;
  justify-content: flex-end;
  align-items: center;
  gap: 10px;
  margin-top: 14px;
}

.mic {
  width: 42px;
  height: 42px;
  border-radius: 50%;
  background: var(--surface);
  border: 1px solid var(--hairline);
  display: grid;
  place-items: center;
  color: var(--ink-2);
  cursor: pointer;
  flex: none;
}

.mic.rec {
  border-color: var(--accent);
  outline: 2px solid color-mix(in srgb, var(--accent) 35%, transparent);
  animation: pulse 1.4s ease-in-out infinite;
}

@keyframes pulse {
  50% {
    outline-color: color-mix(in srgb, var(--accent) 8%, transparent);
  }
}

.voice-original summary {
  cursor: pointer;
  list-style: none;
  display: flex;
  align-items: center;
  gap: 7px;
}

.voice-badge {
  font-family: var(--mono);
  font-size: 9.5px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--a-rejobs);
  border: 1px solid color-mix(in srgb, var(--a-rejobs) 40%, transparent);
  border-radius: 5px;
  padding: 2px 6px;
}

.voice-toggle {
  font-family: var(--mono);
  font-size: 9.5px;
  letter-spacing: 0.08em;
  color: var(--ink-3);
  text-decoration: underline dotted;
}

.send {
  flex: none;
  height: 42px;
  border: 0;
  border-radius: 12px;
  padding: 0 22px;
  background: var(--accent);
  color: var(--accent-ink);
  font: inherit;
  font-size: 14.5px;
  font-weight: 600;
  letter-spacing: -0.01em;
  cursor: pointer;
}

.auth {
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: calc(20px + env(safe-area-inset-top, 0px)) 20px
    calc(20px + env(safe-area-inset-bottom, 0px));
}

.auth .card {
  width: 100%;
  max-width: 360px;
}

.auth .wordmark {
  display: block;
  text-align: center;
  margin-bottom: 18px;
  font-size: 17px;
}

/* ── app shell: sidebar (desktop, design/today-desktop.html) + bottom bar
   (mobile, design/today-mobile.html) ─────────────────────────────────── */

.layout {
  /* the third track is the right rail — `auto` collapses to nothing when a
     page renders no rail (or it's empty) */
  display: grid;
  grid-template-columns: 216px minmax(0, 1fr) auto;
  min-height: 100vh;
}

.rail {
  /* The rail takes the slack the main column cannot use. `.content` caps at
     860px, so on anything wider than 1376px everything past sidebar+cap is
     dead gutter — at 1596px that was 224px of nothing sitting between the
     two columns while the quote wrapped at 30 characters.

     Fluid, not a breakpoint, and that is the point: the previous step at
     1600px left a 1596px window — a 13" MacBook at "More Space", which is
     the actual machine — stranded 4px on the narrow side of the cliff.
     1076px = 216px sidebar + 860px content cap. Floor 300px (at 1280 the
     content is the squeezed one, so the rail must not grow); ceiling 460px,
     past which a quote stops being a column and becomes a paragraph.

     100vw counts a classic scrollbar the grid does not, so where scrollbars
     take space this runs ~15px wide; the middle track is minmax(0, 1fr) and
     absorbs it. Nothing overflows either way. */
  width: clamp(300px, calc(100vw - 1076px), 460px);
  border-left: 1px solid var(--hairline);
  padding: 30px 22px;
  /* Three panels stack here (resurfaced, slipping, done today) and none of
     them carries a margin — per design/today-desktop.html. */
  display: flex;
  flex-direction: column;
  gap: 22px;
}

.rail-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 10px;
  padding: 5px 0;
}

.rail-row a {
  color: var(--ink-2);
  text-decoration: none;
  text-decoration-line: line-through;
  font-size: 13.5px;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.rail-row a:hover {
  color: var(--ink);
}

.rail-time {
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.05em;
  color: var(--ink-3);
  flex: none;
}

/* One authoritative breakpoint for the rail/strip split. Two complementary
   queries (max-width:1279 + min-width:1280) leave a gap at fractional widths
   — browser zoom, non-integer DPI, iPad split view — where NEITHER matched
   and both copies rendered at once. */
.rail {
  display: none;
}

@media (min-width: 1280px) {
  /* [hidden] is a UA rule without !important, so ANY author `display` beats
     it — the aside carries `hidden` when all three panels are empty and would
     still paint a bordered 364px column here. Below this breakpoint it was
     hidden by accident (display: none), which is why it was never noticed. */
  .rail[hidden] {
    display: none;
  }

  .rail {
    display: flex;
  }

  .strip-only {
    display: none;
  }
}

.sidebar {
  border-right: 1px solid var(--hairline);
  padding: 26px 18px 20px;
  display: flex;
  flex-direction: column;
  position: sticky;
  top: 0;
  height: 100vh;
}

.sidebar .wordmark {
  font-size: 17px;
  font-weight: 700;
  letter-spacing: 0.04em;
}

.tagline {
  font-family: var(--mono);
  font-size: 9px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-3);
  margin-top: 3px;
}

.sidenav {
  margin-top: 30px;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.sidenav a {
  display: flex;
  justify-content: space-between;
  align-items: center;
  color: var(--ink-2);
  font-size: 14px;
  letter-spacing: -0.01em;
  padding: 8px 10px;
  border-radius: 9px;
}

.sidenav a.on {
  background: var(--surface);
  color: var(--ink);
  font-weight: 600;
}

.badge {
  font-family: var(--mono);
  font-size: 10px;
  color: var(--ink-3);
}

.badge.hot {
  color: var(--accent);
}

.spacer {
  flex: 1;
}

.kbd-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 9px 10px;
  border-top: 1px solid var(--hairline);
}

.kbd-row .k {
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-3);
}

kbd {
  font-family: var(--mono);
  font-size: 10px;
  color: var(--ink-2);
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: 5px;
  padding: 2px 6px;
}

.content {
  padding: 30px 36px calc(40px + env(safe-area-inset-bottom, 0px));
  max-width: 860px;
  width: 100%;
}

.mobile-only {
  display: none;
}

@media (max-width: 1023px) {
  .sidebar,
  .desktop-only {
    display: none;
  }

  .layout {
    grid-template-columns: minmax(0, 1fr);
  }

  /* The bottom pads for the bar and a breath (DESIGN, One-handed layout ›
     the bottom stack); a page with a shelf pads for both — see the
     primitives at the end of this file. */
  .content {
    padding: var(--content-top) 20px calc(var(--bar-h) + 24px);
    max-width: 640px;
    margin: 0 auto;
  }

  /* The 560px measure is for a WIDE screen, where a form left to fill 860px
     would read as a stretched line. Here the column is already capped at 640
     and centred, so that second cap only ends the form 40px short of the
     section head above it and the actions below — a ragged right edge with
     nothing in it (Jaroslav's screenshot, 2026-08-14). Both, because DESIGN
     ties them: "a record and its edit form should not be different widths". */
  .task-form,
  .detail-panel {
    max-width: none;
  }

  .mobile-only {
    display: block;
  }
}

/* The bar's height, for what stacks on it (the stepped footer): 10px over
   and 14px under a 46px button, plus the home-indicator inset. Change the
   padding or the button and this moves with them, or the footer overlaps. */
:root {
  --bar-h: calc(70px + env(safe-area-inset-bottom, 0px));
}

.bottombar {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  padding: 10px 24px calc(14px + env(safe-area-inset-bottom, 0px));
  display: flex;
  align-items: center;
  justify-content: space-between;
  background: linear-gradient(to top, rgba(11, 12, 14, 0.97) 65%, rgba(11, 12, 14, 0));
  z-index: 30;
}

/* While a field is being typed into, the bar stands down. iOS keeps a fixed
   bottom bar on top of the keyboard, where it covers the very lines being
   written and offers four actions nobody reaches for mid-sentence.

   An ALLOWLIST of the fields that raise a keyboard or a picker, not "every
   input but…": Android Chrome and keyboard users leave focus on a checkbox
   or button after pressing it (iOS Safari doesn't), and a denylist hides
   the bar for every control type nobody thought to exclude — the bar would
   blink on every toggle. A typeless <input> is a text field. The list is
   app.js ENTRY_FIELD (see the touch floor). A field in a LAYER (the
   capture sheet, ⌘K, a settings sheet) doesn't count: the layer's scrim
   already covers the bar, and the same exclusion on the stack switches
   below is what keeps the page under a layer from changing shape while
   its field has focus — the two rules stay in step. On a stepped page the
   footer drops onto the keyboard in its place (see "Stepped forms").
   Gated on :has() support: without it the rule is simply dropped and the
   bar behaves as it always has. */
@supports selector(:has(*)) {
  @media (pointer: coarse) {
    body:has(
        :is(
            input:not([type]),
            input[type="text"],
            input[type="search"],
            input[type="email"],
            input[type="url"],
            input[type="tel"],
            input[type="number"],
            input[type="password"],
            input[type="date"],
            input[type="datetime-local"],
            input[type="time"],
            textarea,
            select
          ):focus:not(:is(.sheet, .ksearch) *)
      )
      .bottombar {
      display: none;
    }
  }
}

/* Four equal actions (search · today · capture · menu) — the capture
   button keeps the accent identity at the same footprint as its siblings
   (2026-08-01 feedback: no oversized floating fab). */
.bottombar .side {
  width: 46px;
  height: 46px;
  border-radius: 50%;
  background: var(--raised);
  border: 1px solid var(--hairline);
  display: grid;
  place-items: center;
  color: var(--ink-2);
  font-size: 17px;
  cursor: pointer;
  text-decoration: none;
}

.bottombar .side svg {
  display: block; /* kills the inline-baseline gap — geometric centering */
}

/* Scoped like the rule it overrides. A bare `.side-accent` (0,1,0) lost
   `background` and `color` to `.bottombar .side` (0,2,0) above, and the
   capture button rendered as grey as its neighbours (found 2026-10-03). */
.bottombar .side-accent {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-ink);
}

/* The menu sheet's rows and head: SLICE B, at the end of this file (#35). */
.menu-sheet {
  padding-bottom: calc(28px + env(safe-area-inset-bottom, 0px));
}

/* ── today + task rows (design/today-*.html) ─────────────────────── */

.eyebrow {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
}

.label .n {
  color: var(--ink-2);
}

.tz {
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: 0.1em;
  color: var(--ink-3);
  border: 1px solid var(--hairline);
  border-radius: 6px;
  padding: 3px 7px;
}

.today-date {
  font-family: var(--serif);
  font-weight: 600;
  font-size: 32px;
  letter-spacing: -0.01em;
  margin-top: 6px;
}

@media (min-width: 1024px) {
  .today-date {
    font-size: 36px;
    margin: 8px 0 4px;
  }
}

/* A long date shortens its month on a phone so the heading never wraps
   (planner.services.today_heading measures it); wider screens always have
   room for the full one. */
.today-date .date-short {
  display: none;
}

@media (max-width: 640px) {
  .today-date .date-full {
    display: none;
  }

  .today-date .date-short {
    display: inline;
  }
}

.sec {
  margin-top: 26px;
}

.sec-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  margin-bottom: 8px;
}

/* A form page's head belongs to the form under it, not to the page. `.task-form`
   keeps its 560px measure on a wide screen while `.content` runs to 860, so
   "← back" floated some 300px right of every field it belongs to — the same
   "gap on the right" reported below 1024px (2026-08-14), where the fix was the
   other way round: drop the cap and let the form fill the already-narrow
   column. Above 1024 the measure is worth keeping and the head is what yields.
   Marked in the template rather than inferred with :has(), because .task-form
   also appears on settings and search, where the head belongs to the page. */
@media (min-width: 1024px) {
  .sec-head.narrow {
    max-width: 560px; /* the measure — see .task-form and .detail-panel */
  }
}

.sec-head a {
  font-size: 13px;
  color: var(--ink-3);
}

.sec-head a:hover {
  color: var(--ink-2);
}

/* "+ new", "all →", "← back", "all tasks →" are ~19px tall: 44px hit areas
   on touch, invisible, so the heads keep their rhythm. A head sits ABOVE
   its content, so the area hangs upward into the space before it and only
   2px down: the filter chips start 8px below "+ new" and reach ~5.6px up,
   and the two must not meet — the chips come later and would win. */
@media (pointer: coarse) {
  .sec-head a,
  .eyebrow a {
    position: relative;
  }

  .sec-head a::before,
  .eyebrow a::before {
    content: "";
    position: absolute;
    left: -8px;
    right: -8px;
    bottom: -2px;
    height: 44px;
  }
}

.row {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 11px 0;
  border-bottom: 1px solid var(--hairline);
}

.row:last-child {
  border-bottom: none;
}

.cb {
  flex: none;
  width: 22px;
  height: 22px;
  border: 1.5px solid color-mix(in srgb, var(--ink) 18%, var(--bg));
  border-radius: 7px;
  margin-top: 1px;
  background: transparent;
  cursor: pointer;
  position: relative;
  padding: 0;
}

.cb.done {
  background: color-mix(in srgb, var(--ink) 10%, var(--bg));
  border-color: transparent;
}

/* The box stays 22px (DESIGN), the tap target is 44: the .r-cb technique.
   An absolute ::before is placed inside its host's BORDER, so the area is
   measured from the 19px padding box: 12.5px each way makes 44 (the -11 it
   had made 41). Past the box's edge that is 11px — the row's 12px gap
   stops it 1px short of the title link, its 11px padding keeps it inside
   the row. ::before because ::after is the tick. Touch only: a mouse
   doesn't need it, and an invisible overhang there only catches clicks
   meant for the text beside it. */
@media (pointer: coarse) {
  .cb::before {
    content: "";
    position: absolute;
    inset: -12.5px;
  }
}

.cb.done::after {
  content: "✓";
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  color: var(--ink-3);
  font-size: 13px;
}

.row .body {
  flex: 1;
  min-width: 0;
}

.row .t {
  font-size: 15.5px;
  line-height: 1.35;
  letter-spacing: -0.01em;
}

.row.done .t {
  color: var(--ink-3);
  text-decoration: line-through;
}

.meta {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 4px;
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-3);
}

.meta .dot {
  margin-right: 0;
}

/* A meta item (`.mi`): its "·" travels with it and it never breaks inside,
   so a wrapped line can't end on a stray separator; a long one ellipses. */
.meta .mi {
  min-width: 0;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.meta .mi .dot {
  margin-right: 6px;
}

.due-today {
  color: var(--accent);
}

/* "today" reads amber wherever a date means today — due chips, Up Next
   group label, the Today screen eyebrow, the mini calendar cell. */
.is-today {
  color: var(--accent);
}

.overdue {
  color: var(--danger);
}

.rec {
  font-size: 11px;
}

/* A reminder still ahead. Amber because it is the one bit of a row that is
   going to act on its own — everything else there is a fact, this is a
   promise. Never --danger: a pending reminder is not trouble. */
.rem {
  color: var(--accent);
}

.star {
  flex: none;
  background: none;
  border: 0;
  color: var(--accent);
  font-size: 15px;
  margin-top: 2px;
  cursor: pointer;
  padding: 0 2px;
}

.star.dim {
  color: var(--ink-3);
}

/* 19×23 visible, 44×44 to the thumb. The star ends its row, so the hit area
   reaches 12px left — exactly the row's gap, never over the title — and the
   rest to the right, into the page's side padding where nothing else is. */
@media (pointer: coarse) {
  .star {
    position: relative;
  }

  .star::before {
    content: "";
    position: absolute;
    top: calc(50% - 22px);
    left: -12px;
    width: 44px;
    height: 44px;
  }
}

/* Top-3 drag-to-reorder */
[data-sortable] .row {
  cursor: grab;
}

.drag-ghost {
  opacity: 0.35;
  background: var(--raised);
  border-radius: 10px;
}

.nudge-form {
  flex: none;
  display: flex;
  align-items: center;
}

.nudge {
  background: none;
  border: 0;
  color: var(--ink-3);
  font-size: 13px;
  cursor: pointer;
  padding: 0 2px;
}

/* the arrows are the non-JS fallback only */
.js .nudge-form {
  display: none;
}

/* Task detail + form */
/* A detail page is a record, so it should read as one object: a bordered
   panel rather than loose text with an action row stranded across the full
   width of the viewport. The frame is also what gives the actions a footer
   to sit in — which is what makes "destructive far left, primary far right"
   read as a deliberate dialog footer instead of two buttons adrift. */
.detail-panel {
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: 16px;
  padding: 20px 22px;
  max-width: 560px; /* same measure as .task-form — a record and its edit
                       form should not be different widths */
}

.detail-head {
  margin-bottom: 16px;
}

.detail-panel .detail-head {
  margin-bottom: 0;
}

.detail-body {
  margin-top: 16px;
}

@media (max-width: 640px) {
  .detail-panel {
    padding: 18px 16px;
  }
}

.detail-title {
  font-family: var(--serif);
  font-weight: 600;
  font-size: 26px;
  letter-spacing: -0.01em;
  margin: 6px 0 8px;
}

.detail-title.struck {
  color: var(--ink-3);
  text-decoration: line-through;
  text-decoration-color: var(--ink-3);
}

.detail-head .meta {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.06em;
  color: var(--ink-2);
}

.detail-head .meta a {
  color: var(--ink-2);
  text-decoration: underline;
  text-decoration-color: var(--ink-3);
}

/* A bare link is 16px tall. These are real targets in a real row — Delete,
   Cancel, Dismiss — so they get the 44px DESIGN asks for, as padding only:
   the box stays invisible, the tap area doesn't. */
.route-actions .btn-quiet,
.rs-actions .btn-quiet {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
}

/* Buttons live inside per-action <form>s — flatten them so every action
   (Done, Edit, Delete…) sits on the same baseline as the bare links.

   Without this the <form> is the flex item, not the button: it stretches to
   the tallest sibling and its button sits at the TOP of that box, so a bare
   Skip link floated 14px above the Keep button beside it. Every row whose
   actions are separate posts needs the rule, which is why it is a list. */
.rs-actions form {
  display: contents;
}

.t a {
  color: inherit;
  text-decoration: none;
}

.t a:hover {
  text-decoration: underline;
  text-decoration-color: var(--ink-3);
}

.task-form {
  display: flex;
  flex-direction: column;
  gap: 14px;
  max-width: 560px;
}

/* A wrapper that groups fields (the repeat + custom-rule pair) is a single
   flex child of .task-form, so it has to repeat the form's own gap —
   otherwise the label inside it sits flush against the control above. */
.field-stack {
  display: flex;
  flex-direction: column;
  gap: 14px;
}

/* A field that groups several controls is a <fieldset>, so the group has an
   accessible name — but it must not look like one: the UA draws a border and
   its own padding, and `min-width: min-content` on a fieldset would stop the
   row shrinking. Reset all three; the legend keeps the app's caption style. */
fieldset.field {
  min-width: 0;
  margin: 0;
  padding: 0;
  border: 0;
}

fieldset.field > legend {
  padding: 0;
  margin-bottom: 6px;
}

/* The day picker on a weekly repeat. Django renders CheckboxSelectMultiple as
   a div of <label><input> pairs, and copies the widget's attrs onto every
   input as well as the wrapper (option_inherits_attrs), so every rule here is
   scoped to `div.day-toggle` or to the label — never the bare class.

   `.pick` is the same chip wearing a RADIO: the area chips on a stepped form
   (core/partials/_area_chips.html). One look for "a choice you tap", so the
   two share every rule below rather than drifting apart as copies. */
.pick-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

/* Seven days, one row, at every width: as wrapping chips "Sun" was left
   alone on a second line at 375. Seven equal columns of a 44px-tall chip
   (2026-10-03); the labels give up their side padding and minimum width to
   fit, never their height. */
div.day-toggle {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 4px;
}

.day-toggle div {
  display: contents;
}

.day-toggle label,
.pick {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  min-width: 46px;
  min-height: 44px; /* the DESIGN touch target */
  padding: 0 10px;
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: 8px; /* the .chip radius — this IS a chip, wearing a checkbox */
  color: var(--ink-2);
  font-size: 13px;
  cursor: pointer;
}

/* Baseline, and it has to stay usable on its own: a real checkbox inside a
   chip-shaped label. Everything below hides that checkbox and paints the
   state with :has() — which, unlike the usual :has() slip, would not merely
   look wrong without support but leave a control with NO visible selection
   and no focus ring. So the polish is gated on the feature itself.
   The resets matter only there: `.field input` gives every input a 100%
   width and a field's padding, which would stretch a bare radio across its
   chip. */
.day-toggle input,
.pick input {
  accent-color: var(--accent);
  width: auto;
  height: auto;
  margin: 0;
  padding: 0;
}

@supports selector(:has(*)) {
  /* The positioning context for its input. Without it the input was absolute
     against nothing, and `.field input`'s `width: 100%` made each one the
     width of the whole page from wherever its chip sat — TH, FR and SA hung
     up to 130px past a 375px screen and the edit page panned sideways. */
  .day-toggle label,
  .pick {
    position: relative;
  }

  .day-toggle input,
  .pick input {
    /* Transparent and non-interactive — NOT display:none, which would take it
       out of the tab order and the a11y tree. The label activates it. Laid
       exactly over its chip, so a keyboard focus scrolls to the chip. */
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    margin: 0;
    opacity: 0;
    pointer-events: none;
  }

  .day-toggle label:has(input:checked),
  .pick:has(input:checked) {
    color: var(--ink);
    border-color: var(--accent); /* .chip-on, same as every other "this one is on" */
  }

  /* Keyboard focus only, and in ink: an amber outline read as a second,
     heavier "checked" next to the 1px amber edge that IS checked. */
  .day-toggle label:has(input:focus-visible),
  .pick:has(input:focus-visible) {
    outline: 2px solid var(--ink-2);
    outline-offset: 2px;
  }
}

/* After the shared chip rule, which it narrows: a column is the width. */
.day-toggle label {
  min-width: 0;
  padding: 0;
}

.day-toggle label:hover,
.pick:hover {
  border-color: color-mix(in srgb, var(--accent) 45%, var(--hairline));
}

/* Seven areas on two lines at 375 (DESIGN): at the day picker's 8px gap and
   10px sides the first line came to 334px of a 335px column, so "Personal"
   wrapped and the row took three. A step tighter here leaves ~15px. */
.pick-chips {
  gap: 6px;
}

.pick-chips .pick {
  padding: 0 9px;
}

/* RRULE examples: rule on the left, plain English on the right, collapsing
   to stacked pairs when there isn't room. */
.rrule-help {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: 4px 14px;
  margin: 10px 0 0;
  align-items: baseline;
}

/* Same chip as `.md code` — mono here is a code literal, not a label, and
   inventing a third size for it would just be a third size. */
.rrule-help dt {
  font-family: var(--mono);
  font-size: 12.5px;
  color: var(--ink-2);
  background: var(--raised);
  border-radius: 4px;
  padding: 1px 5px;
  justify-self: start;
  /* A longer example must wrap rather than give the page a sideways
     scrollbar; today's longest clears a 320px viewport, but only just. */
  overflow-wrap: anywhere;
}

/* Help copy, so it wears the help voice (DESIGN: serif italic, --ink-3) —
   it sits two lines under a `.note hint` that already does. */
.rrule-help dd {
  margin: 0;
  font-family: var(--serif);
  font-style: italic;
  font-size: 13px;
  color: var(--ink-3);
}

@media (max-width: 520px) {
  .rrule-help {
    grid-template-columns: 1fr;
    gap: 2px;
  }

  .rrule-help dd {
    margin-bottom: 6px;
  }
}

.form-note {
  margin-bottom: 12px;
}

.field-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  margin-bottom: 6px;
}

.tabs {
  display: flex;
  gap: 2px;
  background: var(--raised);
  border: 1px solid var(--hairline);
  border-radius: 8px;
  padding: 2px;
}

.tab {
  background: none;
  border: 0;
  border-radius: 6px;
  padding: 3px 10px;
  font-size: 12px;
  color: var(--ink-3);
  cursor: pointer;
}

.tab.on {
  background: var(--surface);
  color: var(--ink);
}

/* write | preview are 20px tall. An invisible 44px area would reach over
   the field's caption above and the textarea below — both closer than
   12px — so on touch the tabs simply ARE tall: height is free in a form
   (DESIGN, "Touch targets"). Found by the 44px probe on the stepped task
   form, where notes became an extra. */
@media (pointer: coarse) {
  .tab {
    min-height: 44px;
    padding: 0 12px;
  }
}

.md-preview {
  min-height: 120px;
  background: var(--raised);
  border: 1px solid var(--hairline);
  border-radius: 9px;
  padding: 10px 12px;
}

.hint {
  font-size: 12px;
  margin-top: 6px;
}

.errorlist {
  list-style: none;
  color: var(--danger);
  font-size: 12.5px;
  margin: 4px 0 0;
  padding: 0;
}

/* Out of sight, still in the accessibility tree and still a `for` target:
   the label of a control whose <summary> already names it on screen. */
.vh {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* ── Stepped forms (DESIGN, "Stepped forms") ───────────────────────────
   `stepform` (app.js) puts .stepping on the root on a phone and moves
   .current between the sections; the mechanism rules are here, the phone
   presentation (the decision screen, the shelf's slots, the recap) in the
   fenced SLICE A section at the end of this file. Without .stepping — a
   wide screen, or no JS — the sections stack into the one-screen form, and
   every control that only means something while stepping stays hidden. */

/* A section is a column of fields with the form's own rhythm. */
.stepform .step {
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.stepform .step:focus {
  outline: none; /* focused only to give a screen reader the new step */
}

.stepform.stepping .step:not(.current) {
  display: none; /* not just out of sight: out of the tab order and the a11y tree */
}

/* A step slides in from the side it comes from. `.moved` keeps the page's
   first step still: it is arriving, not moving. */
@keyframes step-in {
  from {
    opacity: 0;
    transform: translateX(var(--step-from, 16px));
  }
}

.stepform.stepping.moved .step.current {
  animation: step-in 0.22s cubic-bezier(0.32, 0.72, 0.33, 1);
}

.stepform.backward {
  --step-from: -16px;
}

@media (prefers-reduced-motion: reduce) {
  .stepform.stepping.moved .step.current {
    animation: none;
  }
}

/* "Arc · archived": the name is the answer, the mark a footnote to it. */
.pick-archived {
  color: var(--ink-3);
}

.recap-sep {
  color: var(--ink-3);
}

/* Extras: a native disclosure per optional group — works without JS and
   under CSP, and the browser keeps its open state and its semantics. */
.extra {
  border-top: 1px solid var(--hairline);
}

.extra:last-of-type {
  border-bottom: 1px solid var(--hairline);
}

.extra > summary {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 44px;
  list-style: none;
  cursor: pointer;
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-2);
}

.extra > summary::-webkit-details-marker {
  display: none;
}

/* A drawn plus that turns into a minus: two bars, the upright one folding
   away. Glyph characters sit on a baseline (DESIGN), these sit on centre. */
.extra > summary::before {
  content: "";
  width: 11px;
  height: 11px;
  background:
    linear-gradient(var(--ink-3), var(--ink-3)) center / 11px 1.6px no-repeat,
    linear-gradient(var(--ink-3), var(--ink-3)) center / 1.6px 11px no-repeat;
}

.extra[open] > summary::before {
  background: linear-gradient(var(--ink-3), var(--ink-3)) center / 11px 1.6px no-repeat;
}

.extra > summary:hover {
  color: var(--ink);
}

.extra > summary:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 4px;
}

.extra > :not(summary) {
  margin-bottom: 16px;
}

/* The project picker: a control-sized chip naming the project, which opens
   the sheet of radios. */
.pick-chip {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  align-self: flex-start;
  min-height: 44px;
  max-width: 100%;
  padding: 10px 12px;
  background: var(--raised);
  border: 1px solid var(--hairline);
  border-radius: 9px;
  color: var(--ink);
  font: inherit;
  font-size: 15px;
  cursor: pointer;
}

.pick-face {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.pick-more {
  color: var(--ink-3);
}

.pick-chip:focus-visible {
  outline: 2px solid var(--accent);
}

/* The radio list. Without JS it is simply a list in the form; with JS
   picksheet adds `.sheet`, and the list becomes the bottom sheet. */
.pick-list {
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: 12px;
  padding: 0 14px;
}

/* `.sheet` comes earlier in the file at the same specificity, so the
   list's own look above would win — the sheet's is restated here. */
.pick-list.sheet {
  background: var(--raised);
  border-radius: 22px 22px 0 0;
  border-width: 1px 0 0;
  padding: 10px 20px calc(20px + env(safe-area-inset-bottom, 0px));
}

/* With a mouse on a wide screen the picker is a popover under its chip, not
   a sheet rising from the bottom edge of a 1440px window, far from the chip
   that opened it (2026-10-03). Same rows and grouping; the scrim stays, but
   clear, so a click anywhere else closes it (and Escape does, and focus goes
   back to the chip — picksheet, unchanged). A phone keeps the sheet. After
   the sheet rule above: same specificity, so the later one wins. */
@media (pointer: fine) and (min-width: 641px) {
  .project-pick {
    position: relative;
  }

  .pick-list.sheet {
    position: absolute;
    top: calc(100% + 6px);
    bottom: auto;
    left: 0;
    transform: none;
    width: min(360px, 100%);
    max-height: 320px;
    overflow-y: auto;
    padding: 4px 14px;
    border: 1px solid var(--hairline);
    border-radius: 12px;
    box-shadow: 0 18px 50px rgba(0, 0, 0, 0.5);
  }

  /* No room below the chip (picksheet.place): open upward instead. */
  .pick-list.sheet.pick-up {
    top: auto;
    bottom: calc(100% + 6px);
  }

  .pick-list.sheet .grabber {
    display: none;
  }

  .project-pick .scrim {
    background: transparent;
  }
}

.pick-row {
  display: flex;
  align-items: center;
  gap: 12px;
  min-height: 50px;
  border-bottom: 1px solid var(--hairline);
  color: var(--ink-2);
  font-size: 16px;
  cursor: pointer;
}

.pick-row:has(input:checked) {
  color: var(--ink);
}

/* A real radio, visible: the list is native in both lives, and the amber
   dot is the one mark of "this one" — a hand-drawn check would be a second
   control painted over the first. The resets undo `.field input`. */
.field .pick-row input {
  flex: none;
  width: 18px;
  height: 18px;
  margin: 0;
  padding: 0;
  accent-color: var(--accent);
}

/* The sheet moves focus onto the chosen radio as it opens (so Tab and the
   arrows start there). `.field input:focus` would draw the form's square
   field ring round it on every tap; only keyboard focus keeps a ring. */
.field .pick-row input:focus {
  outline: none;
}

/* In ink, as on the chips: amber is the checked dot. The fallback rings
   the radio itself — square, since a native radio's outline (and shadow)
   ignores border-radius — and where :has() works the ring goes round the
   whole row instead, as a chip's goes round its label. */
.field .pick-row input:focus-visible {
  outline: 2px solid var(--ink-2);
  outline-offset: 2px;
}

@supports selector(:has(*)) {
  .field .pick-row:has(input:focus-visible) {
    outline: 2px solid var(--ink-2);
    outline-offset: 4px;
    border-radius: 8px;
  }

  .field .pick-row input:focus-visible {
    outline: none;
  }
}

.pick-row .pick-name {
  flex: 1;
  min-width: 0; /* a long project name ellipsises instead of pushing the row */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.pick-meta {
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-3);
}

/* Under the chosen area's own heading, every row names that area. */
.pick-mine .pick-area {
  display: none;
}

/* The project form's "when" follows the kind chosen a step back: the date
   for a finite project, the open-ended line for a retainer. Without :has
   both show — both are true. A date sent back with an error stays, so it
   can be cleared. */
@supports selector(:has(*)) {
  .stepform:has(input[name="kind"][value="finite"]:checked) [data-for-kind="retainer"],
  .stepform:has(input[name="kind"][value="retainer"]:checked) [data-for-kind="finite"]:not(.has-error) {
    display: none;
  }
}

/* A finite project must have a date: "clear" is no answer it can take. */
[data-for-kind="finite"] .quick-row .chip-clear {
  display: none;
}

.pick-head {
  padding: 14px 0 4px;
}

/* `[hidden]` loses to any author `display`, and `.field .label` sets one —
   so without this both group heads showed, "other areas" over a list that
   had no first group. Named classes enough to win outright. */
.field .pick-head[hidden] {
  display: none;
}

/* Projects */
.project-row {
  display: block;
  padding: 10px 0;
  border-bottom: 1px solid var(--hairline);
  color: inherit;
  text-decoration: none;
}

.project-row:hover .t {
  text-decoration: underline;
  text-decoration-color: var(--ink-3);
}

.project-row.quiet .t {
  color: var(--ink-2);
}

.kind-chip {
  font-family: var(--mono);
  font-size: 9.5px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-3);
  border: 1px solid var(--hairline);
  border-radius: 5px;
  padding: 1px 6px;
  margin-left: 6px;
  vertical-align: 2px;
}

.static-value {
  color: var(--ink-2);
  padding: 10px 0;
}

/* Checklist */
.check-item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 7px 0;
  border-bottom: 1px solid var(--hairline);
}

.check-item .ct {
  flex: 1;
}

.check-item.done .ct {
  color: var(--ink-3);
  text-decoration: line-through;
  text-decoration-color: var(--ink-3);
}

.check-del {
  opacity: 0;
  transition: opacity 0.12s;
}

.check-item:hover .check-del,
.check-del:focus-within {
  opacity: 1;
}

@media (hover: none) {
  .check-del {
    opacity: 1; /* no hover on touch — the × stays visible */
  }
}

/* A checklist row is 37px apart, so 44px targets cannot fit inside it
   without overlapping the next row's — and the later row paints on top and
   takes the overlap. On touch the row is padded to a 44px pitch instead
   (a layout change, like the routine rows'): the box (.cb) and the × keep
   their size and grow invisible hit areas.

   The × is destructive and has no confirm, so its hit area grows AWAY from
   the text: 10px left (the row's gap — it stops at the last letter) and the
   rest right, across the panel's padding and ~9px past its border into the
   page gutter, where nothing else lives. */
@media (pointer: coarse) {
  .check-item {
    padding: 11px 0;
  }

  .check-del .btn-quiet {
    position: relative;
  }

  .check-del .btn-quiet::before {
    content: "";
    position: absolute;
    top: calc(50% - 22px);
    left: -10px;
    width: 44px;
    height: 44px;
  }
}

.check-add input {
  width: 100%;
  background: none;
  border: 0;
  border-bottom: 1px dashed var(--hairline);
  padding: 9px 2px;
  color: var(--ink);
  font: inherit;
}

.check-add input:focus {
  outline: none;
  border-bottom-color: var(--accent);
}

.check-add input::placeholder {
  color: var(--ink-3);
}

/* ⌘K search overlay */
.scrim-search {
  z-index: 69; /* directly under the panel — above sheets and bottombar */
}

.ksearch {
  position: fixed;
  top: 14vh;
  left: 50%;
  width: min(560px, calc(100vw - 32px));
  background: var(--raised);
  border: 1px solid var(--hairline);
  border-radius: 14px;
  box-shadow: 0 24px 80px rgba(0, 0, 0, 0.6);
  z-index: 70;
  overflow: hidden;
  transition:
    transform 0.16s ease-out,
    opacity 0.16s ease-out;
}

.ksearch.away {
  transform: translateX(-50%) translateY(-8px);
  opacity: 0;
  pointer-events: none;
}

.ksearch.in {
  transform: translateX(-50%) translateY(0);
  opacity: 1;
}

.ksearch input {
  width: 100%;
  background: none;
  border: 0;
  border-bottom: 1px solid var(--hairline);
  padding: 14px 16px;
  color: var(--ink);
  font: inherit;
  font-size: 15px;
}

.ksearch input:focus {
  outline: none;
}

.ksearch-results {
  max-height: 46vh;
  overflow-y: auto;
}

.ks-row {
  display: flex;
  align-items: baseline;
  gap: 10px;
  padding: 10px 16px;
  color: var(--ink);
  text-decoration: none;
  border-bottom: 1px solid var(--hairline);
}

.ks-row:last-child {
  border-bottom: 0;
}

.ks-row.sel,
.ks-row:hover {
  background: color-mix(in srgb, var(--accent) 9%, transparent);
}

.ks-kind {
  font-family: var(--mono);
  font-size: 9px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-3);
  border: 1px solid var(--hairline);
  border-radius: 5px;
  padding: 1px 5px;
  flex: none;
}

.ks-title {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Done results: exactly what a finished row wears in the task list
   (.row.done .t), so "finished" reads the same everywhere. */
.ks-row.done .ks-title {
  color: var(--ink-3);
  text-decoration: line-through;
}

/* The word too — strike-through is thin at 13px, and a long ellipsised title
   can hide the struck tail entirely. Mono smallcaps, like every other
   informative caption. */
.ks-done {
  flex: none;
  font-family: var(--mono);
  font-size: 9px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-3);
}

.ks-empty {
  padding: 12px 16px;
}

/* Up Next (Today) */
.upnext-day {
  margin-bottom: 10px;
}

.day-label {
  margin: 6px 0 2px;
}

.ev {
  display: flex;
  gap: 12px;
  align-items: baseline;
  padding: 7px 0;
  border-bottom: 1px solid var(--hairline);
}

/* Calendar jump exists on Mac only (see app.js) — affordance follows */
.mac .ev[data-cal-link] {
  cursor: pointer;
}

.mac .ev[data-cal-link]:hover .ev-title {
  text-decoration: underline;
  text-decoration-color: var(--ink-3);
}

.ev-time {
  font-family: var(--mono);
  font-size: 11.5px;
  color: var(--ink-2);
  min-width: 46px;
  flex: none;
}

.ev-time.ev-allday {
  color: var(--ink-3);
}

.ev-title {
  flex: 1;
}

.chip-src,
.chip-kind {
  font-family: var(--mono);
  font-size: 9px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-3);
  border: 1px solid var(--hairline);
  border-radius: 5px;
  padding: 1px 5px;
}

.stale-note {
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: 0.06em;
  color: var(--ink-3);
  margin-top: 6px;
}

/* Stale is quiet; disconnected is trouble — the one state here that earns
   the reserved red (DESIGN: auth failure), because nothing fixes it but him. */
.stale-note.is-trouble,
.stale-note.is-trouble a {
  color: var(--danger);
}

/* Resurfacing card (M1): a quiet daily quote — serif like display type,
   never louder than the work above it. It was: at 17px this was the largest
   text on Today after the date heading, against 13.5px task rows, so the one
   panel carrying nothing urgent read as the loudest thing on the page
   (Jaroslav, 2026-08-25). 15px keeps it a quote and not a headline. */
.resurface .rs-text {
  font-family: var(--serif);
  font-size: 15px;
  line-height: 1.55;
  /* Prose measure. Inert in the rail, which is narrower than 62ch at every
     width it takes — but live in the .strip-only copy between 1024 and
     1279px, where the content column is 860px wide. */
  max-width: 62ch;
  overflow-wrap: anywhere;
  margin: 0;
}

.card.resurface {
  min-width: 0;
  overflow-wrap: anywhere;
  /* Scoped, so the base .card keeps its 24px everywhere else: a quote needs
     less room around it than a panel of work does. */
  padding: 16px;
}

.resurface .rs-text a {
  color: inherit;
}

.resurface .rs-src {
  display: flex;
  align-items: center;
  /* A flex item's min-width is `auto`, so a long book title refuses to
     shrink and pushes the card past the viewport — which is what ran the
     quote off the right edge. Wrap, and let the title break if it must. */
  flex-wrap: wrap;
  min-width: 0;
  overflow-wrap: anywhere;
  gap: 8px;
  margin-top: 10px;
  font-size: 12.5px;
  color: var(--ink-2);
}

.rs-actions {
  margin-top: 12px;
  align-items: center;
}

/* Pull-to-refresh affordance (standalone PWA only, driven from app.js). */
/* A chip, not a bare glyph: against a page of text an unbacked 18px
   character is easy to miss, which is exactly what happened. */
.ptr-dot {
  position: fixed;
  top: calc(8px + env(safe-area-inset-top, 0px));
  left: 50%;
  translate: -50%;
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  background: var(--surface);
  border: 1px solid var(--hairline);
  color: var(--ink-2);
  opacity: 0;
  pointer-events: none;
  z-index: 50;
}

/* Past the threshold: releasing now reloads. Amber is the app's "this is
   live" colour, and saying so beats making him guess. */
.ptr-dot.armed {
  color: var(--accent);
  border-color: color-mix(in srgb, var(--accent) 45%, var(--hairline));
}

.pager {
  display: flex;
  padding-top: 12px;
}

.pager-older {
  margin-left: auto;
}

.rs-kept {
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-3);
}

.check-label {
  display: flex;
  align-items: center;
  gap: 8px;
  cursor: pointer;
}

.check-label input[type="checkbox"]:not(.switch) {
  accent-color: var(--accent);
  width: 15px;
  height: 15px;
}

/* The label is the target — tapping its text toggles the box — so on touch
   the row itself is 44px tall rather than the 15px box growing. */
@media (pointer: coarse) {
  .check-label {
    min-height: 44px;
  }
}

.form-error {
  color: var(--danger);
  font-size: 13px;
  margin: 6px 0;
}

/* flatpickr themed to tokens (vendor css loads before app.css, so these
   same-specificity overrides win by cascade order) */
.flatpickr-calendar {
  background: var(--raised);
  border: 1px solid var(--hairline);
  border-radius: 12px;
  box-shadow: 0 18px 50px rgba(0, 0, 0, 0.55);
  font-family: var(--sans);
}

.flatpickr-calendar.arrowTop::before,
.flatpickr-calendar.arrowTop::after,
.flatpickr-calendar.arrowBottom::before,
.flatpickr-calendar.arrowBottom::after {
  display: none;
}

.flatpickr-months .flatpickr-month,
.flatpickr-current-month .flatpickr-monthDropdown-months,
.flatpickr-current-month input.cur-year {
  color: var(--ink);
  fill: var(--ink);
}

.flatpickr-current-month .flatpickr-monthDropdown-months {
  background: var(--raised);
}

.flatpickr-months .flatpickr-prev-month,
.flatpickr-months .flatpickr-next-month {
  color: var(--ink-2);
  fill: var(--ink-2);
}

.flatpickr-months .flatpickr-prev-month:hover svg,
.flatpickr-months .flatpickr-next-month:hover svg {
  fill: var(--accent);
}

span.flatpickr-weekday {
  color: var(--ink-3);
}

.flatpickr-day {
  color: var(--ink);
}

.flatpickr-day.prevMonthDay,
.flatpickr-day.nextMonthDay,
.flatpickr-day.flatpickr-disabled {
  color: var(--ink-3);
}

.flatpickr-day:hover,
.flatpickr-day.prevMonthDay:hover,
.flatpickr-day.nextMonthDay:hover {
  background: var(--surface);
  border-color: var(--surface);
}

.flatpickr-day.today {
  border-color: var(--accent);
}

.flatpickr-day.today:hover {
  background: var(--surface);
  color: var(--ink);
}

.flatpickr-day.selected,
.flatpickr-day.selected:hover {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-ink);
}

/* Rendered markdown (already sanitized by nh3 upstream) */
.md {
  line-height: 1.55;
  overflow-wrap: break-word;
}

.md h1,
.md h2,
.md h3 {
  font-size: 15px;
  font-weight: 600;
  margin: 14px 0 6px;
}

.md p,
.md ul,
.md ol {
  margin: 8px 0;
}

.md ul,
.md ol {
  padding-left: 22px;
}

.md a {
  color: var(--accent);
  text-decoration: underline;
  text-decoration-color: color-mix(in srgb, var(--accent) 40%, transparent);
}

.md code {
  font-family: var(--mono);
  font-size: 12.5px;
  background: var(--raised);
  border-radius: 4px;
  padding: 1px 5px;
}

.md pre {
  background: var(--raised);
  border: 1px solid var(--hairline);
  border-radius: 9px;
  padding: 10px 12px;
  overflow-x: auto;
}

.md pre code {
  background: none;
  padding: 0;
}

.md blockquote {
  border-left: 2px solid var(--hairline);
  margin: 8px 0;
  padding-left: 12px;
  color: var(--ink-2);
}

/* ── Routines (design/today-desktop.html) ──────────────────────────────── */

/* The streak, per design/today-desktop.html: mono, a step quieter than the
   row it annotates, with the RUN itself in amber (the token rule reserves
   amber for CTAs, stars and streaks). `flex: none` because this is a flex
   item in .r-item — without it the flame shrinks before the name does; and
   `font-style: normal` because the run sits in an <em> for colour, not for
   emphasis. This used to be a delta on a shared `.streak, .flame` base
   rule; that base went with the mobile strip, so it is spelled out in full
   here rather than silently inheriting body sans at 15.5px. */
.flame {
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: 0.05em;
  color: var(--ink-3);
  flex: none;
}

.flame em {
  color: var(--accent);
  font-style: normal;
}

/* The streak's mirror: a run of misses. A tool left untouched on a bench
   gathers cobwebs — the register is dust, not failure, which is the same
   register Slipping uses ("quiet 12d", never "overdue!"). Identical metrics
   to .flame because they share one slot in the row and swapping between
   them must not shift anything. `--danger` is the reserved trouble colour
   and DESIGN names "missed" in that reservation, so this is its one use on
   a routine — a single 10.5px digit, with the emoji carrying the meaning. */
.cobweb {
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: 0.05em;
  color: var(--ink-3);
  flex: none;
}

.cobweb em {
  color: var(--danger);
  font-style: normal;
}

/* Today's main column + /routines/: a checkable row per routine, by slot. */
.routines-card {
  padding: 6px 16px 10px;
}

.slot-label {
  display: block;
  margin: 8px 0 2px;
}

.slot-label:first-child {
  margin-top: 4px;
}

.r-item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 7px 0;
}

.r-item .t {
  flex: 1;
  min-width: 0;
  font-size: 13.5px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.r-item.done .t {
  color: var(--ink-3);
  text-decoration: line-through;
}

.r-cb {
  flex: none;
  width: 18px;
  height: 18px;
  border: 1.5px solid color-mix(in srgb, var(--ink) 18%, var(--bg));
  border-radius: 6px;
  background: transparent;
  cursor: pointer;
  position: relative;
  padding: 0;
}

/* Neutral like .cb.done, per design/today-desktop.html: amber is for CTAs,
   stars, streaks and today — not for something already finished. */
.r-cb.done {
  background: color-mix(in srgb, var(--ink) 10%, var(--bg));
  border-color: transparent;
}

/* The visible box stays 18px (design), but the TAP target is 44px: an
   inset pseudo-element costs no pixel. It does cost layout here, on touch:
   routine rows were ~34px apart, so each 44px area reached 5px into the
   next row's and — the later row painting on top — a tap just under one
   box checked off the routine BELOW it, with its streak. On touch a row is
   therefore pitched at 44px (min-height, so a taller row isn't padded
   further), and the areas meet exactly. Touch only, like every other hit
   area: a mouse hits an 18px box without help. */
@media (pointer: coarse) {
  .r-item:not(.r-static) {
    min-height: 44px;
  }

  .r-cb::before {
    content: "";
    position: absolute;
    inset: -14.5px; /* from the 15px padding box (18 less its 1.5 border): 44 */
  }
}

.r-cb.done::after {
  content: "✓";
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  color: var(--ink-3);
  font-size: 11px;
}

.routine-row.paused .t a {
  color: var(--ink-3);
}

/* A rhythm that comes round TOMORROW wears the accent, so /routines/ answers
   "what's on me next" at a glance (Jaroslav, 2026-08-10). Amber's meaning is
   already "attention/energy, and today"; tomorrow is the next step along that
   same axis, and the label is a fact about time rather than an area or a
   status, so it doesn't tread on the reserved colours. Scoped to .routine-row
   so it can only ever paint the management list's own label. */
.routine-row .repeat.due-tomorrow {
  color: var(--accent);
}

/* The picked day's routines on /tasks/: the check-off row minus the checkbox,
   because that section is the record of a day, not a place to work it. */
.day-routines-head {
  margin-top: 12px;
  padding-top: 10px;
  border-top: 1px solid var(--hairline);
}

.r-item.r-static {
  padding: 5px 0;
}

/* Mono smallcaps, the same metrics as .label — these are informative
   captions (MORNING, DONE), which is exactly what mono is reserved for. */
.r-state {
  flex: none;
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-3);
}

.r-state.trouble {
  color: var(--danger);
}

/* ── Slipping (design mockups: the one place red is allowed to speak) ───── */

.label.trouble {
  color: var(--danger);
}

.slip {
  padding: 4px 16px;
}

.slip .item {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 10px;
  padding: 9px 0;
  border-bottom: 1px solid var(--hairline);
}

.slip .item:last-child {
  border-bottom: none;
}

.slip-body {
  min-width: 0;
  flex: 1;
}

.slip .t {
  font-size: 14px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.slip .t a {
  color: var(--ink);
  text-decoration: none;
}

.slip .t a:hover {
  color: var(--accent);
}

.slip .meta {
  margin-top: 3px;
}

.slip .age {
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.08em;
  color: var(--danger);
  flex: none;
}

.rail .slip .age {
  font-size: 9.5px;
  letter-spacing: 0.06em;
}

/* Deliberately quiet: "later" is an escape hatch, not a call to action. */
/* Sans, not mono: mono is reserved for capitalized informative labels, and
   this is an interactive word. --ink-3 on --surface is only 3.7:1. */
.slip-later {
  font-size: 12px;
  color: var(--ink-2);
  flex: none;
  padding: 2px 0 2px 6px;
}

.slip-later:hover {
  color: var(--ink);
}

/* Push status: the help voice is for help, not for outcomes. */
.note.ok {
  color: var(--ink-2);
  font-style: normal;
}

.note.bad {
  color: var(--danger);
  font-style: normal;
}

/* The morning brief: quiet, serif-free, gone by noon. */
.brief {
  padding: 16px 18px;
  font-size: 14px;
  color: var(--ink-2);
}

.brief strong {
  color: var(--ink);
  font-weight: 600;
}

.brief ul {
  margin: 6px 0 0;
  padding-left: 18px;
}

.brief > :first-child,
.brief p:first-child {
  margin-top: 0;
}

.brief p:last-child {
  margin-bottom: 0;
}

/* ── Thumb zone ────────────────────────────────────────────────────────
   Ponk is used one-handed, right-handed, on an iPhone 13 mini (DESIGN).
   The reachable arc runs bottom-right; the top-left corner is the stretch.

   So on a phone every row of controls hugs the RIGHT edge, and templates
   order each row destructive → secondary → primary. The primary then lands
   under the thumb and the destructive action sits furthest from it, which
   is exactly where a Delete button belongs. Wide screens keep the ordinary
   left edge, where reading order matters more than reach. */
@media (max-width: 640px) {
  .route-actions,
  .rs-actions,
  .pager {
    justify-content: flex-end;
  }

  /* NOT .quick-row, and NOT the date/time controls (Jaroslav, 2026-08-14):
     a form field reads down its left edge, and pushing the controls and their
     chips to the far right left every label stranded above a gap. Reach wins
     on an ACTION row — those are the last thing you touch on a screen — and
     loses inside a form, where alignment is what makes the fields scannable.
     The date and time controls, their captions and their chips all stay left
     now, which is why the two rules that moved them are gone rather than
     inverted. */

  .pager .pager-older {
    margin-left: 0;
  }
}

/* Date and time fields (see the block comment in app.js). One model: a date
   is day · month · year, a time is hour : minute, and a datetime is simply
   both — so every field is built from the same two pieces.

   Each wheel is sized to its CONTENT — the values are entirely predictable
   (two digits each), so a stretched box would
   only be empty space. `minmax(0, max-content)` is the important part: it
   fits the content when there's room and may still yield if there ever
   isn't, which is what keeps this from repeating the bug it replaced —
   under flex, Safari refused to shrink the time pair below its options'
   width, squeezing "day" to "d" and pushing the row off the screen. */
.time-wheels {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, max-content));
  gap: 8px;
}

.time-wheels .wheel {
  /* auto, not the 100% `.field select` hands every other control: the track
     is already the width of the widest option. */
  width: auto;
  height: 44px;
  background: var(--raised);
  border: 1px solid var(--hairline);
  border-radius: 9px;
  padding: 10px 8px;
  font-family: var(--sans);
  font-size: 15px;
  color: var(--ink);
  min-width: 0;
}

/* No focus rule here on purpose: `.field select:focus` (0,2,1) already wins
   and gives every control in the app the same accent outline. A local
   override was inert dead code. */

/* A datetime is its date part and its time part, on ONE row at every width
   since the date became a chip (2026-08-14) — the five wheels that never fit
   a phone line are gone. Only the edge they pack against changes. */
.datetime-parts {
  display: grid;
  /* Both halves content-sized, at every width. A `1fr` first track stretched
     the date control across the whole column wherever the phone query did not
     match — an iPad, or an iPhone turned sideways (812px). The deleted
     .date-wheels rule guarded this with an UNCONDITIONAL justify-content;
     losing that guard is what let the box grow again (2026-08-14). */
  /* minmax(0, …), not bare max-content: the comment on .time-wheels promises
     these tracks can still yield, and a bare max-content cannot. The widest
     face ("Sat, 31 Dec 2033" — reachable exactly as dateChipLabel describes)
     plus the time pair fits 375px today with room to spare, but a track that
     cannot compress would overflow rather than shrink. */
  grid-template-columns: minmax(0, max-content) minmax(0, max-content);
  justify-content: start;
  gap: 8px;
  align-items: start;
}

/* No phone override any more (2026-08-14): the pair packs LEFT at every
   width, like every other control in a form. It used to flip to flex-end
   below 640px for thumb reach, which left the caption stranded above a gap
   and the row reading right-to-left against the fields around it. */

/* The native time field on desktop — themed to sit level with the calendar
   input beside it rather than wearing the UA's chrome.

   Scoped to .datetime-parts deliberately: the field-wide `.field input {
   width: 100% }` is (0,1,1) and would otherwise win, giving the time input a
   full-width line of its own and pushing it below the date. */
.datetime-parts > .dt-time {
  width: auto;
  min-width: 116px;
  height: 44px;
  background: var(--raised);
  border: 1px solid var(--hairline);
  border-radius: 9px;
  padding: 10px 12px;
  font-family: var(--sans);
  font-size: 15px;
  color: var(--ink);
}

.datetime-parts > .dt-time::-webkit-calendar-picker-indicator {
  filter: invert(1);
  opacity: 0.35;
  cursor: pointer;
}

/* Presets: the three shapes a reminder actually takes, plus an escape. */
.quick-row {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 10px;
}

.quick-row .chip {
  cursor: pointer;
}

.quick-row .chip:hover {
  color: var(--ink);
  border-color: color-mix(in srgb, var(--accent) 45%, var(--hairline));
}

/* On touch a preset chip is a real 44px button, not a padded hit area: the
   field already owns a whole line of the form (DESIGN), so the height costs
   nothing, and on a phone the chips are the main way to set the value.
   Narrower sides and gap pay for the height in width: the reminder row
   (this evening · in an hour · tomorrow 9:00 · clear) needed 350px of the
   335 and wrapped `clear` onto a line of its own; this way it needs ~328.
   It still WRAPS rather than scrolls when it must — after ~21:50 the chip
   renames to "tomorrow evening" and the row outgrows a phone — because a
   row that wraps hides nothing, and a scroller would hide `clear`. */
@media (pointer: coarse) {
  .quick-row {
    gap: 6px;
  }

  .quick-row .chip {
    min-height: 44px;
    padding: 0 8px;
  }
}

/* Follows the presets inline, like any other chip in the row (Jaroslav,
   2026-08-14). It used to be pushed to the far right with `margin-left: auto`
   to read as an escape hatch rather than a fourth preset — but that took two
   exceptions to keep working (a phone override and a form-row override),
   still wrapped `clear` onto a line of its own in a narrow column, and the
   distinction it bought was never worth a row that reads ragged. The quieter
   ink already says it isn't a preset. */
.chip-clear {
  color: var(--ink-3);
}

/* ═══════════════════════════════════════════════════════════════════════
   ONE-HANDED PRIMITIVES (DESIGN, "One-handed layout (phone)" › Primitives)
   ═══════════════════════════════════════════════════════════════════════
   The building blocks every phone screen is converted onto. Pages don't
   restyle them here — page CSS goes in its slice's section below, AFTER
   this one, so a page rule that must override a primitive wins by order
   as well as by naming its page. Nothing in this section names a page.

   "Phone" is max-width 640px, the stepped forms' STEP_QUERY. Phone-scoped:
   the shelf's fixed stack, .stage/.q/.ctx, .choice-rows, .tiles, .trail,
   .foot-link. Every width: the tokens, .glyph, .seg, .list-head, the
   shelf's row and its buttons (#34's order holds on desktop too), and the
   .phone-only/.wide-only utilities. */

:root {
  /* Spacing scale: 4 · 8 · 12 · 16 · 20 (the gutter) · 24 · 34 · 48 */
  --sp-4: 4px;
  --sp-8: 8px;
  --sp-12: 12px;
  --sp-16: 16px;
  --sp-20: 20px;
  --sp-24: 24px;
  --sp-34: 34px;
  --sp-48: 48px;
  --gutter: var(--sp-20);

  /* Sizes: 44 is the floor; three tiers above it. */
  --tap: 44px;
  --row-list: 52px; /* menu, settings, picker/filter sheets, search, foot links */
  --row-choice: 56px; /* the answers on a decision screen */
  --row-task: 62px; /* title + meta, two-line clamp */
  --tile-h: 56px;
  --seg-h: 48px; /* 44 inside a 2px inset */

  /* The bottom stack, bottom-up: safe area → bar (--bar-h, by .bottombar)
     → shelf → content. The shelf is 1 hairline + 9 + 44 + 10. */
  --shelf-h: 64px;
  --stack-h: calc(var(--bar-h) + var(--shelf-h));
  /* What the stack takes from the bottom right now: all of it at rest; the
     shelf alone while a field has focus (the bar stands down and the
     keyboard is outside the visible viewport). */
  --stack-now: var(--stack-h);
  --content-top: calc(22px + env(safe-area-inset-top, 0px));

  /* Phone type scale */
  --fs-display: 32px; /* Today's date */
  --fs-title: 28px; /* question, page title, record title */
  --fs-choice: 17px; /* a choice row */
  --fs-row: 16px; /* body and row titles */
  --fs-value: 15.5px; /* a value in a row */
  --fs-ctx: 15px; /* the context line */
  --fs-label: 10.5px; /* mono labels and meta */
}

/* A page with no bottom bar (logged out: login) stacks the shelf on the
   home-indicator inset instead. Custom properties resolve where they are
   declared, so the sums are restated here, not inherited from :root. */
@supports selector(:has(*)) {
  body:not(:has(.bottombar)) {
    --bar-h: 0px;
    --stack-h: calc(var(--shelf-h) + env(safe-area-inset-bottom, 0px));
    --stack-now: var(--stack-h);
    --shelf-pad-b: calc(10px + env(safe-area-inset-bottom, 0px));
  }
}

/* While a field has focus — the same allowlist and gates as the bar
   standing down (core/tests/test_entry_fields.py holds them equal) — the
   stack changes shape: the shelf drops to the bottom edge and rides the
   keyboard by --kb, giving the home-indicator inset back as the keyboard
   rises over it, and a decision screen counts only the shelf below it.
   Written as switches on <body> so every primitive that cares reads them,
   instead of each carrying its own copy of the field list.

   Not for a field in a layer (a sheet, ⌘K): those type over a scrim, and
   switching the stack under them dropped .content's padding by ~104px — a
   page scrolled to its foot was clamped and stayed moved after the layer
   closed — and lifted a stepped question 12px behind the scrim. */
@supports selector(:has(*)) {
  @media (pointer: coarse) {
    body:has(
        :is(
            input:not([type]),
            input[type="text"],
            input[type="search"],
            input[type="email"],
            input[type="url"],
            input[type="tel"],
            input[type="number"],
            input[type="password"],
            input[type="date"],
            input[type="datetime-local"],
            input[type="time"],
            textarea,
            select
          ):focus:not(:is(.sheet, .ksearch) *)
      ) {
      --shelf-bottom: 0px;
      --shelf-pad-b: max(10px, calc(env(safe-area-inset-bottom, 0px) + 10px - var(--kb, 0px)));
      --stack-now: var(--shelf-h);
      --q-top: 22px;
    }
  }
}

/* Visibility at the phone breakpoint. `!important` because a utility that
   loses to any author `display` (the [hidden] trap) is no utility. */
@media (max-width: 640px) {
  .wide-only {
    display: none !important;
  }
}

@media (min-width: 641px) {
  .phone-only {
    display: none !important;
  }
}

/* ── glyphs ─────────────────────────────────────────────────────────────
   One sprite (core/partials/_glyphs.html), drawn by _glyph.html. The
   stroke lives here, once: inherited through <use> into every symbol. */
.glyph-sprite {
  position: absolute;
  width: 0;
  height: 0;
  overflow: hidden;
}

.glyph {
  display: inline-block;
  flex: none;
  width: 20px;
  height: 20px;
  vertical-align: middle;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.glyph-18 {
  width: 18px;
  height: 18px;
}

/* "Move up" in every reorder mode (routines, areas): the sprite's `arrow`
   (it points right) turned to point up. ONE rule — the routines once drew
   `back` here under a second, opposite rule, and the later one turned it
   to point down. */
.glyph-up {
  transform: rotate(-90deg);
}

/* ── the shelf ──────────────────────────────────────────────────────────
   The page's own actions (DESIGN: Action rows split in two). Markup:
     <div class="shelf">
       way back (optional)           ← _back.html, Cancel, Back, a filter
       <div class="shelf-end">       ← trash glyph · secondary · primary
     </div>
   A per-action <form> inside it flattens (display: contents), so its
   button is the flex item. On a wide screen it is a plain row where the
   template puts it; on the phone it stands on the bar (below). */
.shelf {
  display: flex;
  align-items: center;
  gap: var(--sp-8);
}

.shelf-end {
  display: flex;
  align-items: center;
  gap: var(--sp-8);
  margin-left: auto;
}

.shelf form {
  display: contents;
}

.shelf .btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  flex: none;
  width: auto;
  height: var(--tap);
  min-height: var(--tap);
  padding: 0 18px;
  white-space: nowrap;
}

.shelf .btn-ghost {
  font-weight: 500;
}

/* A quiet escape (Cancel, Dismiss) is still a 44px target. */
.shelf .btn-quiet {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap);
  padding: 0 var(--sp-4);
  font-size: 15px;
}

/* Where the trash sits directly before the primary (a form: 🗑 Save; a
   note: 🗑 Edit), 16px instead of 8 keeps them apart (DESIGN). */
.shelf .apart {
  margin-right: var(--sp-8);
}

/* A glyph-only button is 44×44; the primary one (the done check) 52×44.
   Every width — the trash glyph replaces Delete on desktop too (#34). */
.btn.btn-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: var(--tap);
  min-width: var(--tap);
  height: var(--tap);
  min-height: var(--tap);
  padding: 0;
}

.btn.btn-icon:not(.btn-ghost) {
  width: 52px;
}

/* `‹ Tasks`: the chevron sits tight to the list's name. */
.btn.btn-back {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  width: auto;
  padding: 0 14px 0 8px;
  color: var(--ink);
}

@media (max-width: 640px) {
  /* Fixed on the bar, never over it (z 40: over the page and the bar's 30,
     under a sheet's scrim at 50). The keyboard ride is the stepped
     footer's, generalised: lifted by --kb, and while typing dropped to the
     bottom edge by the switches on <body> above. */
  .shelf {
    position: fixed;
    left: 0;
    right: 0;
    bottom: var(--shelf-bottom, var(--bar-h));
    z-index: 40;
    margin: 0;
    padding: 9px var(--gutter) var(--shelf-pad-b, 10px);
    background: var(--bg);
    border-top: 1px solid var(--hairline);
    transform: translateY(calc(0px - var(--kb, 0px)));
    transition: transform 0.25s cubic-bezier(0.32, 0.72, 0.33, 1);
  }

  /* The page clears the whole stack; the bar's fade turns solid under a
     shelf so the two read as one block. */
  body:has(.shelf) .content {
    padding-bottom: calc(var(--stack-now) + var(--sp-24));
  }

  body:has(.shelf) .bottombar {
    background: var(--bg);
  }
}

@media (prefers-reduced-motion: reduce) {
  .shelf {
    transition: none;
  }
}

/* ── decision screens ───────────────────────────────────────────────────
   One question, its answers on the thumb:
     <div class="stage">
       head (count, cancel)
       <h2 class="q">When is it due?</h2>
       <p class="ctx">Fix the brakes on the cargo bike</p>
       <div class="stage-answers"> rows / tiles / a docked field </div>
     </div>
   The stage fills what the visible viewport leaves between the page's top
   padding and the stack, so the answers stand on the shelf and the slack
   goes above them. In a sheet the sheet anchors itself; the stage is only
   the column there. */
@media (max-width: 640px) {
  .stage {
    display: flex;
    flex-direction: column;
    min-height: calc(
      var(--vvh, 100svh) - var(--content-top) - var(--stack-now) - var(--sp-24)
    );
  }

  .sheet .stage {
    min-height: 0;
  }

  .stage-answers {
    margin-top: auto;
    padding-top: var(--sp-24);
  }

  .stage-answers > * + * {
    margin-top: var(--sp-12);
  }

  .q {
    margin: var(--q-top, var(--sp-34)) 0 0;
    font-family: var(--serif);
    font-weight: 600;
    font-size: var(--fs-title);
    line-height: 1.15;
    letter-spacing: -0.01em;
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 3;
    line-clamp: 3;
    overflow: hidden;
  }

  .ctx {
    margin: var(--sp-8) 0 0;
    font-size: var(--fs-ctx);
    line-height: 1.4;
    color: var(--ink-3);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
}

/* ── choice rows ────────────────────────────────────────────────────────
   Full-width rows in one card, each a <button>, <a> or radio <label>:
     <div class="choice-rows">
       <button class="choice-row" type="button">
         <span class="choice-label">Tomorrow</span>
         <span class="choice-val">Sun 4</span>
         {% include "core/partials/_glyph.html" with g="check" glyph_class="choice-tick" %}
       </button>
   A trailing `chevron` glyph instead of the tick for a row that opens
   something. Chosen = .on, aria-pressed/aria-checked="true", aria-current,
   or a checked radio inside: --raised ground, the tick in amber, the value
   still visible. `.list-rows` on the card makes them 52px (sheets, menu,
   settings); `.bare` drops the card for rows straight on a sheet. */
@media (max-width: 640px) {
  .choice-rows {
    display: flex;
    flex-direction: column;
    background: var(--surface);
    border: 1px solid var(--hairline);
    border-radius: 16px;
    overflow: clip; /* rounds the rows; unlike `hidden`, leaves sticky alone */
  }

  .choice-rows.bare {
    background: none;
    border: 0;
    border-radius: 0;
  }

  .choice-row {
    position: relative;
    display: flex;
    align-items: center;
    gap: var(--sp-12);
    width: 100%;
    min-height: var(--row-choice);
    margin: 0;
    padding: 0 14px 0 18px;
    background: none;
    border: 0;
    border-top: 1px solid var(--hairline);
    border-radius: 0;
    color: var(--ink);
    font: inherit;
    font-size: var(--fs-choice);
    letter-spacing: -0.01em;
    text-align: left;
    text-decoration: none;
    cursor: pointer;
  }

  .choice-row:first-child {
    border-top: 0;
  }

  .choice-row > .dot {
    margin-right: 0; /* the row's gap spaces it */
  }

  .choice-rows.list-rows .choice-row {
    min-height: var(--row-list);
  }

  .choice-rows.bare .choice-row {
    padding: 0 var(--sp-4);
  }

  .choice-label {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .choice-val {
    flex: none;
    font-size: var(--fs-value);
    color: var(--ink-2);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
  }

  .choice-val.is-today {
    color: var(--accent);
  }

  .choice-row .glyph {
    color: var(--ink-3);
  }

  .choice-row .choice-tick {
    color: transparent; /* keeps its column, so values never shift */
  }

  .choice-row.quiet {
    color: var(--ink-2);
  }

  .choice-row.on,
  .choice-row[aria-pressed="true"],
  .choice-row[aria-checked="true"],
  .choice-row[aria-current]:not([aria-current="false"]) {
    background: var(--raised);
  }

  .choice-row.on .choice-tick,
  .choice-row[aria-pressed="true"] .choice-tick,
  .choice-row[aria-checked="true"] .choice-tick,
  .choice-row[aria-current]:not([aria-current="false"]) .choice-tick {
    color: var(--accent);
  }

  .choice-row:focus-visible {
    outline: 2px solid var(--ink-2);
    outline-offset: -2px;
  }

  /* The answer that is always there, pinned at a sheet's foot above a
     stronger hairline (No project, All areas) — never above a scroll. */
  .sheet-pin {
    position: sticky;
    bottom: 0;
    background: var(--raised);
    border-top: 1px solid rgba(255, 255, 255, 0.16);
  }

  .choice-row.sheet-pin {
    border-top: 1px solid rgba(255, 255, 255, 0.16);
  }
}

/* ── area tiles ─────────────────────────────────────────────────────────
   The same .pick radios _area_chips.html renders, as a 2-column grid of
   56px tiles: {% include "core/partials/_area_chips.html" with field=…
   tiles=True %}. An odd last tile spans the row. Chips on a wide screen. */
.pick-tick {
  display: none;
}

@media (max-width: 640px) {
  .pick-chips.tiles {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--sp-8);
  }

  .tiles .pick {
    justify-content: flex-start;
    gap: 11px;
    min-width: 0;
    height: var(--tile-h);
    min-height: var(--tile-h);
    padding: 0 14px 0 16px;
    border-radius: 14px;
    color: var(--ink);
    font-size: 16.5px;
    letter-spacing: -0.01em;
  }

  .tiles .pick:last-child:nth-child(odd) {
    grid-column: 1 / -1;
  }

  .tiles .pick-name {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .tiles .pick .dot {
    width: 9px;
    height: 9px;
    margin-right: 11px;
  }

  .tiles .pick-tick {
    display: inline-block;
    margin-left: auto;
    color: transparent;
  }
}

/* ── radios inside rows and segments ────────────────────────────────────
   The .pick technique: the radio stays real (POST, keyboard, a11y) and
   lies invisible over its label, and the label paints its state. Gated on
   :has(), like the chips, because without it the selection would be
   invisible rather than merely plain; ungated, the native radio shows. */
.choice-row input[type="radio"],
.seg-opt input {
  accent-color: var(--accent);
  width: auto;
  height: auto;
  margin: 0;
  padding: 0;
}

@supports selector(:has(*)) {
  .choice-row input[type="radio"],
  .seg-opt input {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    opacity: 0;
    pointer-events: none;
  }

  @media (max-width: 640px) {
    /* A chosen row keeps its hairline: the chips' "on" edge
       (.pick:has(input:checked) — the repeat rows are .pick too) would
       otherwise colour the divider above it amber. */
    .choice-row:has(input:checked) {
      background: var(--raised);
      border-top-color: var(--hairline);
    }

    .choice-row:has(input:checked) .choice-tick,
    .tiles .pick:has(input:checked) .pick-tick {
      color: var(--accent);
    }

    .tiles .pick:has(input:checked) {
      background: var(--raised);
    }

    .choice-row:has(input:focus-visible) {
      outline: 2px solid var(--ink-2);
      outline-offset: -2px;
    }
  }

  .seg-opt:has(input:checked) {
    background: var(--raised);
    color: var(--ink);
    box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent) 75%, transparent);
  }

  .seg-opt:has(input:focus-visible) {
    outline: 2px solid var(--ink-2);
    outline-offset: 2px;
  }
}

/* ── segmented control ──────────────────────────────────────────────────
   2–3 equal segments for a kind that qualifies the field beside it:
   _seg.html over a RadioSelect, or links for navigation
   (<a class="seg-opt" aria-current="page">). No JS. Every width. */
.seg-field {
  min-width: 0;
  padding: 0;
  border: 0;
}

.seg {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(0, 1fr);
  gap: 2px;
  min-height: var(--seg-h);
  padding: 1px;
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: 14px;
}

.seg-opt {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  min-width: 0;
  min-height: var(--tap);
  border-radius: 11px;
  color: var(--ink-2);
  font-size: var(--fs-value);
  text-decoration: none;
  cursor: pointer;
}

.seg-opt[aria-current]:not([aria-current="false"]) {
  background: var(--raised);
  color: var(--ink);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent) 75%, transparent);
}

/* ── list heading ───────────────────────────────────────────────────────
   Every list gets a serif title and a mono count, at every width:
     <header class="list-head">
       <h1 class="list-title">Tasks</h1>
       <p class="label list-count"><span class="n">12</span> open · all areas</p>
     </header>
   `.record-title` is the same type for a detail page's title. */
.list-head {
  margin-bottom: var(--sp-16);
}

.list-title,
.record-title {
  margin: 0;
  font-family: var(--serif);
  font-weight: 600;
  font-size: var(--fs-title);
  line-height: 1.15;
  letter-spacing: -0.01em;
}

.list-count {
  display: block;
  margin-top: 6px;
}

/* ── section-foot link ──────────────────────────────────────────────────
   A section's "all →" as a 52px row at its END on the phone:
     <a class="foot-link" href="…">All 12 open tasks {% include … g="chevron" %}</a>
   The head link stays for a wide screen — give it `.wide-only`. */
.foot-link {
  display: none;
}

@media (max-width: 640px) {
  .foot-link {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-12);
    min-height: var(--row-list);
    margin-top: -1px; /* on the hairline of a row above it, never doubling it */
    border-top: 1px solid var(--hairline);
    color: var(--ink-2);
    font-size: var(--fs-value);
    text-decoration: none;
  }

  .foot-link .glyph {
    color: var(--ink-3);
  }
}

/* ── trailing checks ────────────────────────────────────────────────────
   `.trail` on a row (.row task rows, .r-item routine rows, .check-item
   checklist steps) moves its checkbox to the trailing edge on the phone,
   the star just inside it — one template, CSS `order`. The box, whether
   it is the row's child or inside its per-action <form>, goes last. A
   destructive control never shares a trailing row with a check: the
   checklist's × leaves the row first (an edit mode, slice C). */
@media (max-width: 640px) {
  .trail > .cb,
  .trail > .r-cb {
    order: 3;
  }

  .trail > .star {
    order: 2;
  }
}

/* Apart from the rule above: an unsupported :has() drops its whole
   selector list, and the direct children must not go with it. */
@supports selector(:has(*)) {
  @media (max-width: 640px) {
    .trail > :has(> .cb),
    .trail > :has(> .r-cb) {
      order: 3;
    }

    .trail > :has(> .star) {
      order: 2;
    }
  }
}

/* ── switch ─────────────────────────────────────────────────────────────
   ONE switch, every width (DESIGN, Settings): 51×31, iOS-shaped, a white
   knob; on is a light-grey track (--ink-2), never amber — on isn't a call to
   action (Jaroslav, 2026-10-03). Drawn as backgrounds, so it is the same
   picture on a <span> inside a role="switch" row (settings: the row is the
   control and the target) and on a real checkbox (a routine's Active) —
   nothing rests on pseudo-elements, which an <input> doesn't take.
   Element-qualified to (0,1,1), level with `.field input` — whose
   `width: 100%` it must beat (by order: the primitives come later). */
span.switch,
input.switch {
  appearance: none;
  flex: none;
  width: 51px;
  height: 31px;
  margin: 0 0 0 auto;
  padding: 0;
  border: 0;
  border-radius: 16px;
  background-color: var(--raised);
  background-image: radial-gradient(circle closest-side, #fff 0 calc(100% - 0.5px), transparent);
  background-size: 27px 27px;
  background-position: 2px 50%;
  background-repeat: no-repeat;
  box-shadow: inset 0 0 0 1px var(--hairline);
  cursor: pointer;
  transition:
    background-color 0.2s ease,
    background-position 0.2s ease;
}

.switch:checked,
[aria-checked="true"] > .switch {
  background-color: var(--ink-2);
  background-position: 22px 50%;
  box-shadow: none;
}

/* Beside its own label (a routine's Active) the row lays it out: no auto
   margin throwing it to the far side on a wide screen. */
.check-label > .switch {
  margin: 0;
}

.switch:focus-visible {
  outline: 2px solid var(--ink-2);
  outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
  .switch {
    transition: none;
  }
}

/* ── a head's edit toggle ───────────────────────────────────────────────
   `edit` / `done` at the right of a section head (the checklist, All
   routines, Settings' Areas): ONE look, the quiet lowercase link, whether
   it is a link (?edit=1, ?reorder=areas) or a button (the checklist's
   mode). Scoped to the heads so it outranks `.sec-head a` (0,1,1). The
   44px touch area hangs upward, as a head link's does, so it never meets
   the first row below; side padding takes the short word past 44 wide. */
:is(.sec-head, .set-head) .head-toggle {
  position: relative;
  padding: 0 var(--sp-4);
  background: none;
  border: 0;
  font-family: var(--sans);
  font-size: 13.5px;
  letter-spacing: normal;
  text-transform: none;
  color: var(--ink-2);
  text-decoration: none;
  cursor: pointer;
}

:is(.sec-head, .set-head) .head-toggle[aria-pressed="true"] {
  color: var(--ink);
}

@media (pointer: coarse) {
  :is(.sec-head, .set-head) .head-toggle::before {
    content: "";
    position: absolute;
    top: auto;
    left: -8px;
    right: -8px;
    bottom: -2px;
    height: 44px;
  }
}

/* No resize grip on touch: a finger can't drag it, and in the capture sheet
   and the Adjust panel it sat in the corner of the box being typed in. Same
   selectors as the rules that grant it, so these win by order. */
@media (pointer: coarse) {
  .field textarea,
  .composer textarea,
  .input-wrap textarea {
    resize: none;
  }
}

/* ═══ end of ONE-HANDED PRIMITIVES ═════════════════════════════════════ */

/* ═══════════════════════════════════════════════════════════════════════
   SLICE A — stepped forms (task, routine, project, note; project picker)
   Page CSS for the forms' phone conversion goes here, on the primitives.
   ═══════════════════════════════════════════════════════════════════════
   DESIGN, One-handed layout › Stepped forms. On the phone each step is a
   decision screen (the root is the `.stage`; the current step its column:
   question, context, answers standing on the shelf); the footer is the
   `.shelf`; the extras are side steps off the recap. Stacked — a wide
   screen, or no JS — nothing here shows but the shelf as a plain row, and
   the form reads as it always did: labels, chips, <details>.

   Specificity is deliberate: the shelf's slots beat `.shelf .btn` and
   `.btn.btn-back` (0,2,0) by naming three classes, never by file order. */

/* ── what only stepping shows ───────────────────────────────────────── */
.stepform .step-count,
.stepform .step-only,
.stepform .step-rail,
.stepform .q,
.stepform .ctx,
.stepform .recap,
.stepform .shelf .step-back,
.stepform .shelf .step-next,
.stepform .shelf .md-seg {
  display: none;
}

.stepform .shelf {
  margin-top: var(--sp-8); /* stacked: the form's footer row */
}

.stepform.stepping .step-count {
  display: inline;
  color: var(--ink-2);
}

/* "← back" (an edit, stacked) would read as a second Back. */
.stepform.stepping .sec-head .step-off {
  display: none;
}

/* The head's cancel: whenever the shelf's left slot isn't Cancel already —
   one tap out, never offered twice. */
.stepform.stepping:not(.way-cancel) .step-only {
  display: inline;
}

/* The question replaces a field's caption on screen; the caption stays for
   its `for` and a screen reader. */
.stepform.stepping .step-label {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* ── the shelf's slots (DESIGN: the way back left, the actions right) ── */
.stepform.stepping .shelf .step-cancel {
  display: none;
}

.stepform.stepping.way-cancel .shelf .step-cancel,
.stepform.stepping.way-back .shelf .step-back,
.stepform.stepping:not(.at-last) .shelf .step-next {
  display: inline-flex;
}

/* Save (and an edit's trash glyph before it) on the recap only; a jumped
   or side step reads Done alone. */
.stepform.stepping:not(.at-last) .shelf .step-save,
.stepform.stepping:not(.at-last) .shelf .step-trash {
  display: none;
}

/* write | preview, between the way back and Done (frame 1b): on a markdown
   step only, mono like the field's own tabs. */
.stepform.stepping.on-md .shelf .md-seg {
  display: grid;
}

.stepform .md-seg {
  flex: 0 1 152px;
  min-width: 0;
  min-height: var(--tap);
  margin: 0 auto;
  padding: 0;
  border: 0;
  box-shadow: inset 0 0 0 1px var(--hairline); /* the frame, inside the 44 */
}

/* No way back in the left slot (a jumped or side step — notes always is):
   the segments take that slot, on the gutter, instead of floating ~34px
   off it in the middle of an empty row. */
.stepform.jumped .md-seg {
  margin-left: 0;
}

.stepform .md-seg .seg-opt {
  min-height: var(--tap);
  padding: 0;
  background: none;
  border: 0;
  font-family: var(--mono);
  font-size: var(--fs-label);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-3);
}

.stepform .md-seg .seg-opt[aria-pressed="true"] {
  background: var(--raised);
  color: var(--ink);
}

/* The field's own tabs stand down while the shelf carries them. */
.stepform.stepping .field-head .tabs {
  display: none;
}

.stepform.stepping .field-head {
  margin: 0;
}

/* A one-line answer (OneLineField) is a textarea that grows rather than a
   box to resize: app.js grow() sets its height, three lines at most. */
textarea[data-oneline] {
  resize: none;
  overflow: hidden;
}

/* ── the decision screen (phone) ──────────────────────────────────────
   The root is the stage (the primitive: the visible viewport less the page
   top and the stack); the form, the current step — or the extras around a
   side step — are its column, so `.stage-answers` (margin-top: auto)
   stands on the shelf and the slack goes above it. */
@media (max-width: 640px) {
  .stepform.stepping > form {
    flex: 1;
    display: flex;
    flex-direction: column;
    gap: 0;
  }

  .stepform.stepping .extras {
    display: contents;
  }

  .stepform.stepping .step.current,
  .stepform.stepping .kind-stage {
    flex: 1;
    display: flex;
    flex-direction: column;
    gap: 0;
  }

  .stepform.stepping .sec-head {
    margin-bottom: var(--sp-12);
  }

  .stepform.stepping .step-rail {
    display: flex;
    gap: var(--sp-4);
  }

  .step-rail > span {
    flex: 1;
    height: 3px;
    border-radius: 2px;
    background: var(--hairline);
  }

  .step-rail > span.on {
    background: var(--ink-2);
  }

  .stepform.stepping .q {
    display: -webkit-box;
  }

  .stepform.stepping .ctx {
    display: block;
  }

  /* A text step: the one-line field docked on the shelf — 20px, 56 tall,
     growing to three lines (app.js grow()). Its font-size is the one
     deliberate exception to the 16px touch floor's !important, and is
     larger than it (core/tests/test_mobile_keyboard.py). */
  .stepform.stepping textarea[data-oneline] {
    min-height: 56px;
    padding: 14px 16px;
    background: var(--surface);
    border-radius: 14px;
    font-size: 20px !important;
    line-height: 1.3;
    letter-spacing: -0.01em;
  }

  /* A fill step (a note's body, the notes side step): the box runs from
     under the question to the shelf, so the line being written is always
     just above the keys. Sized from the stage's own sum rather than by
     flex, because a <details> (the notes extra) can't be a flex column on
     every engine the phone runs: the stage less what stands over the box —
     the head and rail (34), the question's top margin and its one line
     (--q-top + 32), the context line where there is one (8 + 21), and the
     box's own 16 of air. */
  .stepform.stepping .fill {
    margin-top: var(--sp-16);
    padding-top: 0;
  }

  .stepform.stepping .step:has(> .ctx) {
    --ctx-h: 29px;
  }

  .stepform.stepping .fill textarea,
  .stepform.stepping .fill .md-preview {
    height: calc(
      var(--vvh, 100svh) - var(--content-top) - var(--stack-now) - var(--sp-24) - 34px -
        var(--q-top, var(--sp-34)) - 32px - var(--ctx-h, 0px) - var(--sp-16)
    );
    min-height: 120px;
    padding: 14px 16px;
    background: var(--surface);
    border-radius: 14px;
    line-height: 1.5;
    resize: none;
    overflow-y: auto;
  }

  /* Rows and fields on a decision screen: a field's label sits 20 under
     the answers above it (AREA tiles, then PROJECT), and separate cards
     stack 12 apart (the recap's two). */
  .stepform .stage-answers > .field + .field,
  .stepform .stage-answers > .on-row + .field {
    margin-top: var(--sp-20);
  }

  .stepform .stage-answers > * + .recap {
    margin-top: var(--sp-12);
  }

  /* The head's way out reads as the head does: mono smallcaps. */
  .stepform.stepping .sec-head .step-only {
    font-family: var(--mono);
    font-size: var(--fs-label);
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--ink-2);
  }

  /* A side step: its <details> is simply open (stepform opens it), with no
     disclosure of its own — the recap row was the way in. */
  .stepform.stepping .step.side .extra {
    border: 0;
  }

  .stepform.stepping .step.side .extra > summary {
    display: none;
  }

  .stepform.stepping .step.side .extra > :not(summary) {
    margin-bottom: 0;
  }

  /* Rows of the step's choices (the slot, the repeat): the chip rule's
     gap and side padding give way to the row's. */
  .stepform .pick-chips.choice-rows {
    gap: 0;
  }

  .stepform .choice-rows .pick.choice-row {
    min-width: 0;
    padding: 0 14px 0 18px;
  }

  /* A row's word reads as a sentence on the phone ("Custom rule…"); the
     server, the chips and the recap keep it lowercase. */
  .stepform .choice-row .choice-label::first-letter {
    text-transform: uppercase;
  }

  /* Weekly's seven days open inside its card, right under it, on the
     chosen row's ground (DESIGN, Task › repeat). */
  .stepform .choice-rows .repeat-days {
    padding: 2px 12px 12px;
    background: var(--raised);
  }

  /* Seven columns in the card's 333, inset 12 so the days line up under the
     row text's side of the card: 12 + 7 × 42.4 + 6 × 2 + 12. Each day's
     touch area reaches 1px into the gaps either side — 44.4, abutting its
     neighbours', never overlapping. */
  .stepform .choice-rows .repeat-days div.day-toggle {
    gap: 2px;
  }
}

@media (max-width: 640px) and (pointer: coarse) {
  .stepform .choice-rows .repeat-days .day-toggle label::before {
    content: "";
    position: absolute;
    inset: 0 -2px; /* from inside the 1px border: to the box edge, then 1px of gap */
  }
}

@media (max-width: 640px) {

  /* Custom rule… turns the step into a text step: the rule docked on the
     shelf, its examples above it. */
  .stepform.stepping .repeat-rule {
    display: flex;
    flex-direction: column;
  }

  .stepform.stepping .repeat-rule .rrule-help {
    order: -1;
    margin: 0 0 var(--sp-12);
  }

  /* Active (a routine edit): a switch row in a card of its own. */
  .stepform.stepping .active-field .check-label {
    justify-content: space-between;
    min-height: var(--row-choice);
    padding: 0 14px 0 18px;
    background: var(--surface);
    border: 1px solid var(--hairline);
    border-radius: 16px;
    font-size: var(--fs-row);
  }

  .stepform.stepping .active-field .check-label > span {
    order: -1;
  }

  .stepform.stepping .active-field .hint {
    margin: var(--sp-8) 4px 0;
  }
}

/* While the rule is being typed the step IS a text step: the rows fold
   away (still in the form, still posting custom) so the rule docks on the
   shelf over the keyboard, and the examples scroll in what is left above
   it — the stage less the head (34), the question and its context line,
   and the rule's own caption, box and note (130). Back at rest the rows
   return, so another repeat is one tap away. Gated: without :has() the
   page simply scrolls. */
@supports selector(:has(*)) {
  @media (max-width: 640px) {
    .stepform.stepping .repeat-rows:has(input[name="rrule"]:focus) > fieldset {
      display: none;
    }

    .stepform.stepping .repeat-rows:has(input[name="rrule"]:focus) .rrule-help {
      max-height: calc(
        var(--vvh, 100svh) - var(--content-top) - var(--stack-now) - var(--sp-24) - 34px -
          var(--q-top, var(--sp-34)) - 32px - 29px - var(--sp-12) - 90px
      );
      overflow-y: auto;
    }

    /* The examples say what the note says; while typing, room goes to them. */
    .stepform.stepping .repeat-rows:has(input[name="rrule"]:focus) .repeat-rule .hint {
      display: none;
    }
  }
}

/* ── the date presets as rows ─────────────────────────────────────────
   One set of buttons (app.js buildQuickRow in a stepped form): the chip's
   word on a wide screen, the row's words, date and tick on the phone. */
.quick-rows .choice-label,
.quick-rows .choice-val,
.quick-rows .glyph {
  display: none;
}

/* Touch on a wide screen (an iPad): Pick a date… holds the date input,
   so it must show — as a chip saying what it is. */
.quick-rows .pick-date .choice-label {
  display: inline;
}

@media (max-width: 640px) {
  .stepform .quick-rows {
    gap: 0;
    margin-top: 0;
  }

  .stepform .quick-rows .chip-word {
    display: none;
  }

  .stepform .quick-rows .choice-label {
    display: block;
  }

  .stepform .quick-rows .choice-val {
    display: inline;
  }

  .stepform .quick-rows .glyph {
    display: inline-block;
  }

  .stepform .quick-row .choice-row {
    min-height: var(--row-choice);
    padding: 0 14px 0 18px;
  }

  /* Pick a date… is the date input's own box (.date-field): the real
     control lies over the whole row. Its ring is the rows' ink, not the
     field's amber, and its border is the row's hairline. */
  .stepform .quick-rows .pick-date:focus-within {
    outline: 2px solid var(--ink-2);
    outline-offset: -2px;
    border-color: var(--hairline);
  }
}

/* ── the recap ────────────────────────────────────────────────────────
   The title as the serif headline, a button back to "what", its pen in
   --ink-3; then a card of the steps and a card of the extras, 56px rows of
   a 62px mono key, the value and a chevron. */
/* The headline is a 44px target even on one line: a block of its own that
   carries the question's three-line clamp itself. */
.stepform.stepping .q.recap-title {
  display: block;
}

.recap-head {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
  line-clamp: 3;
  overflow: hidden;
  min-height: var(--tap);
  padding: 6px 0;
  text-wrap: pretty; /* the pen never sits on a line of its own */
  background: none;
  border: 0;
  color: var(--ink);
  font: inherit;
  letter-spacing: inherit;
  text-align: left;
  cursor: pointer;
}

.recap-head .recap-pen {
  margin-left: 6px;
  color: var(--ink-3);
  vertical-align: 0;
}

.recap {
  list-style: none;
  margin: 0;
  padding: 0;
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: 16px;
  overflow: clip;
}

.stepform.stepping .recap {
  display: block;
}

.recap > li + li {
  border-top: 1px solid var(--hairline);
}

.recap-row,
.recap-on {
  display: grid;
  grid-template-columns: 62px minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--sp-12);
  width: 100%;
  min-height: var(--row-choice);
  padding: 0 14px 0 18px;
  background: none;
  border: 0;
  color: var(--ink);
  font: inherit;
  font-size: var(--fs-row);
  text-align: left;
  cursor: pointer;
}

.recap-on {
  cursor: default;
}

.recap-row .glyph {
  color: var(--ink-3);
}

.recap-row:focus-visible {
  outline: 2px solid var(--ink-2);
  outline-offset: -2px;
}

.recap-value {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The extras' values are the quieter card; an empty one invites. */
.recap + .recap .recap-value {
  color: var(--ink-2);
}

.recap-value.is-empty[data-empty="add"] {
  color: var(--ink-3);
}

/* The headline's value is the title itself: it wraps (three lines, the
   question's clamp), where a row's value keeps to one. */
.recap-head .recap-value {
  overflow: visible;
  white-space: normal;
  overflow-wrap: anywhere;
}

/* A project's dot is the picker face's; in the recap the area says it. */
.recap .proj-dot {
  display: none;
}

/* ── the project picker on the phone (frame 4) ────────────────────────
   The way in is a 56px row under the tiles; the sheet is 52px rows under a
   PROJECT … esc head, *No project* pinned at its foot. */
.pick-row {
  position: relative; /* its radio lies over it (the choice-row technique) */
}

/* The tick marks the chosen project in the popover too: the radio is laid
   invisibly over its row at every width now. */
.pick-list .choice-tick {
  margin-left: auto;
  color: transparent;
}

@supports selector(:has(*)) {
  .pick-list .pick-row:has(input:checked) .choice-tick {
    color: var(--accent);
  }
}

.pick-sheet-head {
  align-items: center;
  justify-content: space-between;
}

.pick-esc {
  display: inline-flex;
  align-items: center;
  justify-content: flex-end;
  min-width: var(--tap);
  min-height: var(--tap);
  padding: 0;
  background: none;
  border: 0;
  color: var(--ink-2);
  cursor: pointer;
}

/* A row from another area trails its area tag; the tick's empty slot after
   it stopped the tags ~36px short of the edge the rows above end on. The
   tick takes its slot back only on the chosen row. */
@supports selector(:has(*)) {
  .pick-rest .pick-row:not(:has(input:checked)) .choice-tick {
    display: none;
  }
}

.pick-list .proj-dot {
  display: none;
}

.pick-area .dot {
  margin-right: 5px;
}

@media (min-width: 641px) {
  .stepform .choice-tick {
    display: none;
  }

  .pick-list .choice-tick {
    display: inline-block;
  }
}

@media (max-width: 640px) {
  .stepform .pick-chip {
    display: flex;
    align-items: center;
    gap: var(--sp-12);
    width: 100%;
    min-height: var(--row-choice);
    padding: 0 14px 0 18px;
    background: var(--surface);
    border: 1px solid var(--hairline);
    border-radius: 16px;
    font-size: var(--fs-choice);
    letter-spacing: -0.01em;
  }

  .stepform .pick-chip .pick-face {
    flex: 1;
    min-width: 0;
    text-align: left;
  }

  .stepform .pick-chip .proj-dot {
    width: 9px;
    height: 9px;
    margin-right: 12px;
  }

  .stepform .pick-chip .pick-chevron {
    color: var(--ink-3);
  }

  .stepform .pick-chip:has(.pick-empty) {
    color: var(--ink-2);
  }

  .pick-list.sheet {
    max-height: calc(var(--vvh, 100dvh) * 0.75);
    padding: 10px 20px 0;
  }

  /* A long list scrolls inside the sheet; a flex column that overflows
     shrinks its children first, and the grabber (5px, no content) went to
     nothing. Nothing in the sheet gives up its height. */
  .pick-list.sheet > * {
    flex-shrink: 0;
  }

  .pick-sheet-head {
    display: flex;
    margin-bottom: var(--sp-4);
  }

  .pick-list .pick-row.choice-row {
    min-height: var(--row-list);
    padding: 0 4px;
    border-bottom: 0;
  }

  .pick-list .pick-head + .pick-row,
  .pick-list .pick-head[hidden] + .pick-row {
    border-top: 0;
  }

  /* Keyboard focus rings the row inside the sheet, as a choice row's does. */
  .field .pick-list .pick-row:has(input:focus-visible) {
    outline-offset: -2px;
    border-radius: 0;
  }

  .pick-list .pick-head {
    padding: 18px 4px 6px;
  }

  .pick-list .pick-meta {
    font-size: var(--fs-label);
  }

  /* No project: last, whatever the list — pinned, above the safe area. */
  .pick-list .pick-row.sheet-pin {
    order: 1;
    position: sticky; /* restated: .pick-row's relative comes later */
    bottom: 0;
    z-index: 1;
    width: auto;
    min-height: calc(var(--row-list) + env(safe-area-inset-bottom, 0px));
    margin: 0 -20px;
    padding: 0 24px env(safe-area-inset-bottom, 0px);
  }
}

/* ── the note's ON row (#31) ──────────────────────────────────────────
   Where a note from "+ note" lands, above its area tiles; the × detaches. */
.on-row {
  display: flex;
  align-items: center;
  gap: var(--sp-12);
  min-height: var(--row-choice);
  padding: 0 6px 0 18px;
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: 16px;
}

.on-row .on-title {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: var(--fs-row);
}

.on-detach {
  color: var(--ink-2);
}

/* `[hidden]` loses to any author display: say it. */
[data-on-row][hidden] {
  display: none;
}

/* ── stacked (wide, or no JS) ─────────────────────────────────────────
   A step's answers keep the form's own rhythm: 14 between fields. */
.stepform:not(.stepping) .stage-answers {
  display: flex;
  flex-direction: column;
  gap: 14px;
}

/* Weekly's days take a line of their own after the repeat chips. */
@media (min-width: 641px) {
  .pick-chips > .repeat-days {
    order: 1;
    flex-basis: 100%;
    margin-top: var(--sp-8);
  }
}

/* Each extra is its own section now: one hairline between neighbours. */
.extras .step + .step .extra {
  border-top: 0;
}

/* Remind me (flag on): the date and time controls above the preset rows
   are the *pick a time* path, so they read as the when step's
   "Pick a date…" does — full width, 56 tall, on the card's ground, 12 above
   the rows — not three 44px chips flush on the card. */
@media (max-width: 640px) {
  .stepform.stepping [data-step="remind"] .datetime-parts {
    grid-template-columns: minmax(0, 1fr) max-content;
    gap: var(--sp-8);
    margin-bottom: var(--sp-12);
  }

  .stepform.stepping [data-step="remind"] .date-field,
  .stepform.stepping [data-step="remind"] .time-wheels .wheel {
    min-height: var(--row-choice);
    height: var(--row-choice);
    background: var(--surface);
    border-radius: 16px;
    font-size: var(--fs-choice);
  }

  .stepform.stepping [data-step="remind"] .date-field {
    padding: 0 18px;
  }

  .stepform.stepping [data-step="remind"] .time-wheels .wheel {
    padding: 0 12px;
  }

  .stepform.stepping [data-step="remind"] .date-face.empty::after {
    content: "Pick a day…";
    color: var(--ink);
  }
}

/* ═══ end of SLICE A ═══════════════════════════════════════════════════ */

/* ═══════════════════════════════════════════════════════════════════════
   SLICE B — Today, lists (tasks, projects, routines, notes, log), menu
   sheet and sidebar glyphs, filter sheet
   ═══════════════════════════════════════════════════════════════════════ */

/* ── the menu sheet (#35) ───────────────────────────────────────────────
   52px glyph rows (.choice-rows.list-rows.bare), Log out in the head. The
   sheet shows below 1024px, so these hold beside the phone-scoped choice
   rows rather than inside their query: on a 641–1023 tablet the same
   rows still need to be rows. */
.menu-head {
  min-height: var(--tap);
  margin-bottom: var(--sp-4);
}

.menu-head .wordmark {
  margin-left: var(--sp-4);
  font-size: 17px;
}

.menu-head form {
  display: contents;
}

/* Quiet, in the stretch on purpose: the one item never wanted by accident. */
.menu-logout {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-8);
  min-height: var(--tap);
  padding: 0 var(--sp-4) 0 var(--sp-12);
  font-size: 15px;
  color: var(--ink-3);
}

.menu-nav {
  display: flex;
  flex-direction: column;
}

.menu-nav .choice-row {
  display: flex;
  align-items: center;
  gap: 14px;
  min-height: var(--row-list);
  padding: 0 var(--sp-4);
  border-top: 1px solid var(--hairline);
  color: var(--ink);
  font-size: var(--fs-choice);
  letter-spacing: -0.01em;
  text-decoration: none;
}

.menu-nav .choice-row .glyph {
  color: var(--ink-3);
}

.menu-nav .choice-row[aria-current="page"] .glyph {
  color: var(--ink);
}

.menu-count {
  margin-left: auto;
  font-family: var(--mono);
  font-size: 12px;
  color: var(--ink-2);
  font-variant-numeric: tabular-nums;
}

/* The inbox's count is the amber one, as on the sidebar: waiting captures
   are a call to act, the others are tallies. */
.menu-count.hot {
  padding: 1px 8px;
  border-radius: 20px;
  background: var(--accent);
  color: var(--accent-ink);
  font-size: 11.5px;
  font-weight: 600;
}

/* ── the desktop sidebar's glyphs (#35) ─────────────────────────────────
   Subtle and monochrome: --ink-3 at rest, --ink on the current page. */
.sidenav a {
  justify-content: flex-start;
  gap: 10px;
}

.sidenav .glyph {
  color: var(--ink-3);
}

.sidenav a.on .glyph {
  color: var(--ink);
}

.sidenav .badge {
  margin-left: auto;
}

.side-logout {
  display: inline-flex;
  align-items: center;
  gap: 10px;
}

.side-logout .glyph {
  color: var(--ink-3);
}

/* ── list heads ─────────────────────────────────────────────────────────
   The .list-head primitive, with a wide screen's `+ new` at its right,
   level with the title (the head-link idiom; the phone's is on the shelf). */
.list-head {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  column-gap: var(--sp-16);
  align-items: baseline;
}

.list-head > * {
  grid-column: 1;
}

.list-head .list-new {
  grid-column: 2;
  grid-row: 1;
  font-size: 13px;
  color: var(--ink-3);
}

.list-head .list-new:hover {
  color: var(--ink-2);
}

@media (pointer: coarse) {
  .list-head .list-new {
    position: relative;
  }

  .list-head .list-new::before {
    content: "";
    position: absolute;
    top: calc(50% - 22px);
    bottom: calc(50% - 22px);
    left: -8px;
    right: -8px;
  }
}

/* ── empty lists ────────────────────────────────────────────────────────
   On the phone an empty list's line sits in the lower middle, the easy
   band just above the shelf (DESIGN: Empty states) — the page is a column
   as tall as what the stack leaves, and the line takes the slack above it.
   Only an empty page becomes a column: in a flex column the sections'
   margins would stop collapsing and every gap would grow. Any wrapper on
   the way down to the line (the inbox's two stacks) grows with it, so the
   line lands at the same height on every empty page — and, sized on --vvh
   and --stack-now, it stays above a keyboard. */
@supports selector(:has(*)) {
  @media (max-width: 640px) {
    .list-page:has(.list-empty) {
      display: flex;
      flex-direction: column;
      min-height: calc(
        var(--vvh, 100svh) - var(--content-top) - var(--stack-now) - var(--sp-24)
      );
    }

    .list-page:has(.list-empty) :has(> .list-empty),
    .list-page:has(.list-empty) :has(> * > .list-empty) {
      display: flex;
      flex: 1 0 auto;
      flex-direction: column;
    }

    .list-page .list-empty {
      margin: auto 0 var(--sp-48);
      text-align: center;
    }
  }
}

/* An empty state never ends on a lone word. */
.list-empty {
  text-wrap: balance;
}

/* ── the shelf on a list ────────────────────────────────────────────────
   Filter left, New right — both ghost: the amber in the stack is capture's. */
.shelf .shelf-filter {
  min-width: 0;
  padding: 0 14px 0 12px;
  gap: var(--sp-8);
}

.shelf-filter-val {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}

.shelf-filter .shelf-filter-end {
  color: var(--ink-3);
}

.shelf .shelf-new {
  padding: 0 16px 0 12px;
  color: var(--ink);
}

.shelf .shelf-older {
  gap: 2px;
  padding: 0 8px 0 14px;
  color: var(--ink);
}

/* Three kinds fit the shelf as a segmented control. Its 2px inset rides out
   into the shelf's own padding (margin -2px), so the shelf keeps its 64px
   and the segments their 44. */
.shelf .shelf-seg {
  flex: 1 1 auto;
  min-width: 0;
  max-width: 220px;
  margin: -2px 0;
}

/* A list's last row needs no rule under it; nor does a picked day's last
   task above the day's routines, whose head draws one of its own. */
.project-row:last-child {
  border-bottom: 0;
}

@supports selector(:has(*)) {
  .day-sec .row:has(+ .day-routines-head) {
    border-bottom: 0;
  }
}

/* A notes row says its kind once on the phone — the leading glyph — so the
   boxed kind chip goes, with the "·" opening the item after it. */
@media (max-width: 640px) {
  .note-row .chip-kind,
  .note-row .chip-kind + .mi > .sep {
    display: none;
  }
}

/* ── the filter sheet ───────────────────────────────────────────────────
   52px link rows (area dot · name · open count · tick), All areas pinned at
   the foot; ≤ 75% of what is visible, scrolling inside. */
.filter-sheet {
  max-height: min(75vh, calc(var(--vvh, 100dvh) * 0.75));
}

.filter-sheet .sheet-head {
  margin-bottom: var(--sp-4);
}

.filter-sheet .choice-row .dot {
  width: 9px;
  height: 9px;
}

/* All areas has no colour, but its label lines up with the areas' names. */
.dot-spacer {
  visibility: hidden;
}

/* A picked day's due tasks: the raised card is a wide screen's. On the phone
   its 16px padding put their checks 17px in from every other row's, so the
   section sits on the page like the rest, its own head saying what it is. */
@media (max-width: 640px) {
  .day-sec {
    padding: 0;
    background: none;
    border: 0;
    border-radius: 0;
  }
}

/* ── task rows ──────────────────────────────────────────────────────────
   The star is a glyph now; a wide screen keeps it at the row's text size. */
.star .glyph {
  display: block;
  width: 16px;
  height: 16px;
}

@media (max-width: 640px) {
  /* ≥ 62px, title on two lines at most, and on the thumb's side the star
     (44 square) and the check, whose 44px area begins exactly where the
     star's box ends — neighbours abut, never overlap (DESIGN, Touch). */
  .row.trail {
    align-items: center;
    gap: 0;
    min-height: var(--row-task);
    padding: 9px 0;
  }

  .row.trail > form:not(.nudge-form) {
    display: flex;
    align-items: center;
  }

  .row.trail .body {
    padding-right: var(--sp-8);
  }

  .row.trail .t {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    overflow: hidden;
    font-size: var(--fs-row);
  }

  .row.trail .meta,
  .note-row .meta,
  .project-row .meta,
  .routine-row .meta {
    font-size: var(--fs-label);
  }

  .row.trail .star {
    display: grid;
    place-items: center;
    width: var(--tap);
    height: var(--tap);
    margin: 0;
    padding: 0;
  }

  .row.trail .star .glyph {
    width: 20px;
    height: 20px;
  }

  .row.trail .star::before {
    content: none;
  }

  .row.trail .cb {
    margin: 0 0 0 11px;
  }

  /* The title link takes the whole text block — title and meta, to the
     row's edges — so a thumb landing anywhere left of the star opens the
     task, as a list row does. The block is stretched to the row's height
     and positions the link's area; the star and check are its siblings,
     so the area never reaches them. */
  .row.trail .body,
  .note-row .body,
  .routine-row .body {
    position: relative;
    align-self: stretch;
    display: flex;
    flex-direction: column;
    justify-content: center;
  }

  .row.trail .t a::before,
  .note-row .t a::before,
  .routine-row .t a::before {
    content: "";
    position: absolute;
    inset: -9px 0;
  }

  /* The rows that open something rather than check off. */
  .note-row,
  .routine-row {
    align-items: center;
    min-height: var(--row-task);
    padding: 9px 0;
  }

  .project-row {
    display: flex;
    flex-direction: column;
    justify-content: center;
    min-height: var(--row-task);
    padding: 9px 0;
  }

  .note-row .t,
  .routine-row .t,
  .project-row .t {
    font-size: var(--fs-row);
  }

  .note-row .t {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    overflow: hidden;
  }

  .note-kind {
    color: var(--ink-3);
  }
}

/* ── routines ───────────────────────────────────────────────────────────
   On the phone a routine row is 48 with the flame (or cobweb) before the
   trailing box; the box is the task's 22, one check, one size — and its
   44px area (inset -13 → 48) meets the next row's exactly. */
@media (max-width: 640px) {
  .r-item.trail {
    min-height: 48px;
    gap: var(--sp-12);
    padding: 0;
  }

  .r-item.trail .t {
    font-size: var(--fs-row);
  }

  .r-item.trail .r-cb {
    width: 22px;
    height: 22px;
    border-radius: 7px;
  }

  .r-item.trail .r-cb.done::after {
    font-size: 13px;
  }

  .r-item.trail .flame,
  .r-item.trail .cobweb {
    font-size: 11px;
  }

  .routines-card {
    padding: 6px 16px 8px;
  }

  .slot-label {
    margin: 10px 0 0;
  }
}

/* Reordering, behind `edit` (?edit=1): the move-up posts stay with JS on,
   as 44px squares; a wide screen drags and only sees them without JS. The
   up mark is the primitives' `.glyph-up`. */
.nudge .glyph {
  width: 16px;
  height: 16px;
}

.js .reordering .nudge-form {
  display: flex;
}

.reordering .nudge {
  display: grid;
  place-items: center;
  width: var(--tap);
  height: var(--tap);
  margin-left: var(--sp-8);
  padding: 0;
  border: 1px solid var(--hairline);
  border-radius: 12px;
  color: var(--ink-2);
}

.reordering .nudge .glyph {
  width: 20px;
  height: 20px;
}

/* The first routine has nowhere to go; its square keeps its column so the
   flames stay in line. */
.reordering .routine-row:first-child .nudge-form {
  visibility: hidden;
}

/* ── Today ──────────────────────────────────────────────────────────────
   A foot link under a card stands clear of its rounded edge rather than
   drawing a hairline along it. */
@media (max-width: 640px) {
  .card + .foot-link {
    margin-top: var(--sp-4);
    border-top: 0;
  }

  /* Slipping (and the reminders, which share its card): rows a thumb can
     take, and *later* a visible 36px button whose touch area is 44. */
  .slip .item {
    min-height: 56px;
    padding: 6px 0;
  }

  .slip .t {
    font-size: var(--fs-value);
  }

  /* As on a task row, the title's link takes its whole text block. */
  .slip-body {
    position: relative;
    align-self: stretch;
    display: flex;
    flex-direction: column;
    justify-content: center;
  }

  .slip .t a::before {
    content: "";
    position: absolute;
    inset: -6px 0;
  }

  .rs-text a {
    display: block;
    padding: 2px 0;
  }

  .slip .age {
    font-size: var(--fs-label);
  }

  .slip-later {
    position: relative;
    display: inline-grid;
    place-items: center;
    height: 36px;
    padding: 0 12px;
    border: 1px solid var(--hairline);
    border-radius: 10px;
    font-size: 13.5px;
  }

  .slip .item.trail .r-cb {
    width: 22px;
    height: 22px;
    border-radius: 7px;
  }
}

/* Touch areas measured from the PADDING box — an absolute ::before is placed
   inside its host's border, so a 1px border costs 2px of reach unless the
   inset pays for it. */
@media (max-width: 640px) and (pointer: coarse) {
  .slip-later::before {
    content: "";
    position: absolute;
    top: -5px;
    bottom: -5px;
    left: 0;
    right: 0;
  }

  /* The trailing check keeps the shared .cb area (-12.5 from its padding
     box: 44), whose left edge lands exactly where the star's 44px box ends
     (11px of margin + the 1.5 border). A routine's box is the task's 22px
     here, so its area is the same -12.5, not the 18px box's -14.5. */
  .r-item.trail .r-cb::before,
  .slip .item.trail .r-cb::before {
    inset: -12.5px;
  }

  /* Resurfaced's quiet Skip: its word is 27px wide; the target isn't. */
  .rs-actions .btn-quiet {
    min-width: var(--tap);
  }
}

/* ── the log ────────────────────────────────────────────────────────────
   On the phone the time stands over its line — 335px can't hold both side
   by side without folding every summary into a narrow column. */
@media (max-width: 640px) {
  .log-row {
    flex-direction: column;
    gap: 3px;
    padding: 11px 0;
    font-size: 15px;
  }

  .log-meta {
    padding-top: 0;
    font-size: var(--fs-label);
  }

  /* An entry that links takes the whole row, time and all. */
  .log-row {
    position: relative;
  }

  .log-link::before {
    content: "";
    position: absolute;
    inset: 0;
  }
}

/* ═══ end of SLICE B ═══════════════════════════════════════════════════ */

/* ═══════════════════════════════════════════════════════════════════════
   SLICE C — detail pages (task, project, note, idea), inbox, capture sheet
   ═══════════════════════════════════════════════════════════════════════ */

/* ── detail pages: task, project, note, idea (#34, #31, #30) ───────────
   Eyebrow · 28px serif title (.record-title) · meta · body · sections, then
   the shelf. On a wide screen the bordered panel stays and the shelf is its
   footer, in place; on the phone the frame drops (its job was anchoring the
   action row, which is the shelf now) and the content runs the full 335. */
.detail-head .record-title {
  margin: 8px 0 0;
}

.detail-head .meta {
  flex-wrap: wrap;
  margin-top: 10px;
}

@media (min-width: 641px) {
  .detail-panel .shelf {
    margin-top: 18px;
    padding-top: 16px;
    border-top: 1px solid var(--hairline);
  }
}

@media (max-width: 640px) {
  .detail-panel {
    padding: 0;
    background: none;
    border: 0;
    border-radius: 0;
  }

  .detail-head .meta {
    gap: 7px;
    font-size: var(--fs-label);
    letter-spacing: 0.1em;
    color: var(--ink-3);
  }

  .detail-body {
    margin-top: 20px;
    font-size: var(--fs-row);
  }

  /* Five controls on a project's shelf (‹ Projects · 🗑 ⏸ Edit ✓, 333px)
     and four on an idea's (‹ Notes · 🗑 Edit → Make task, 332px) have to
     fit 335: the labelled buttons give up side padding, never height. */
  .detail-panel .shelf .btn:not(.btn-icon):not(.btn-back) {
    padding: 0 13px;
  }

  .detail-panel .shelf .btn-back {
    padding: 0 12px 0 6px;
  }

  .detail-panel .shelf .btn-make {
    padding: 0 12px 0 8px;
  }
}

/* The pin toggle by the title: rare, so it may stand in the hard zone. A
   44px target whose glyph lines up with the text edge; it pulls its margins
   in so the eyebrow keeps its height. Pinned: --ink and a filled head. */
.detail-head .eyebrow {
  align-items: center;
  min-height: 16px;
}

.pin-form {
  display: contents;
}

.pin-toggle {
  display: grid;
  place-items: center;
  flex: none;
  width: var(--tap);
  height: var(--tap);
  margin: -14px -12px -14px 0;
  padding: 0;
  background: none;
  border: 0;
  border-radius: 12px;
  color: var(--ink-3);
  cursor: pointer;
}

.pin-toggle:hover {
  color: var(--ink-2);
}

.pin-toggle[aria-pressed="true"] {
  color: var(--ink);
}

.pin-toggle[aria-pressed="true"] .glyph {
  fill: currentColor;
}

.pin-toggle:focus-visible {
  outline: 2px solid var(--ink-2);
  outline-offset: -4px;
}

/* A fact row after the body: a series (`REPEATS mon · thu   Stop`), an
   instance's way to its series (`Part of a series  weekly ›`), what a note
   is on (`ON Bench rebuild ›  detach`). 52px, one hairline card. */
.fact-row {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: var(--row-list);
  margin-top: 22px;
  padding: 0 6px 0 14px;
  border: 1px solid var(--hairline);
  border-radius: 14px;
  color: var(--ink);
  font-size: var(--fs-value);
  text-decoration: none;
}

a.fact-row {
  padding-right: 12px;
}

.fact-row form {
  display: contents;
}

.fact-key,
.fact-aside {
  flex: none;
}

.fact-val {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.fact-row .glyph {
  color: var(--ink-3);
}

/* A repeat rule is told in one face — the mono label — on the series as on
   its instance's `Part of a series  MON · THU`; on the series it is the
   row's value, so it reads a step brighter. */
.fact-rule {
  color: var(--ink-2);
}

a.fact-row:hover .fact-val,
.on-link:hover .fact-val {
  text-decoration: underline;
  text-decoration-color: var(--ink-3);
}

/* The quiet act at a fact row's end (Stop, detach): a 44px target behind a
   hairline, so it reads as apart from the link it shares the card with. */
.fact-act {
  display: inline-flex;
  align-items: center;
  flex: none;
  min-height: var(--tap);
  padding: 0 10px 0 14px;
  border-left: 1px solid var(--hairline);
  border-radius: 0;
  color: var(--ink-2);
  font-size: 15px;
}

.on-link {
  display: flex;
  flex: 1;
  align-items: center;
  gap: 10px;
  min-width: 0;
  min-height: var(--row-list);
  padding-right: 6px;
  color: inherit;
  text-decoration: none;
}

/* Notes on the work (#31): whole-row links, then "Add a note". */
.note-link {
  display: flex;
  align-items: center;
  gap: 12px;
  min-height: 60px;
  padding: 8px 0;
  border-bottom: 1px solid var(--hairline);
  color: var(--ink);
  text-decoration: none;
}

.note-link .note-kind,
.note-link .row-chevron {
  color: var(--ink-3);
}

.note-link-body {
  display: flex;
  flex: 1;
  flex-direction: column;
  min-width: 0;
}

.note-link-title {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 15.5px;
  letter-spacing: -0.01em;
}

.note-link .meta {
  margin-top: 3px;
}

.note-link:hover .note-link-title {
  text-decoration: underline;
  text-decoration-color: var(--ink-3);
}

.add-row {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: var(--row-list);
  color: var(--ink-2);
  font-size: var(--fs-value);
  text-decoration: none;
}

.add-row .glyph {
  flex: none;
  color: var(--ink-3);
}

a.add-row:hover {
  color: var(--ink);
}

/* The checklist's "Add a step" is a field in the same row shape, its plus
   the label; the dashed rule marks the end of the list it adds to. */
.check-add.add-row {
  border-bottom: 1px dashed rgba(255, 255, 255, 0.12);
}

.check-add.add-row:focus-within {
  border-bottom-color: var(--accent);
}

.check-add .add-row-label {
  display: flex;
  flex: none;
}

.check-add.add-row input {
  flex: 1;
  min-width: 0;
  height: var(--row-list);
  padding: 0;
  border: 0;
}

.check-add.add-row input::placeholder {
  color: var(--ink-2);
}

@media (max-width: 640px) {
  .attached .note-link-title {
    font-size: var(--fs-row);
  }

  /* Steps are 52px list rows with the check trailing (.trail). The remove
     control is in every row but shows only in edit mode, where it leads —
     a delete without a confirm never beside the check. Without JS there is
     no mode to enter, so it stays (the `.js` gate). */
  .check-item {
    min-height: var(--row-list);
    gap: 12px;
    padding: 0;
  }

  .check-item .ct {
    font-size: var(--fs-row);
    letter-spacing: -0.01em;
  }

  /* The box's hit area hangs off its padding box (19px inside the 1.5px
     border). A step's row is 52 tall with nothing beside the trailing box:
     13px reaches 45. */
  .checklist .cb::before {
    inset: -13px;
  }

  .js .checklist:not(.editing) .check-del {
    display: none;
  }

  .checklist .check-del {
    order: -1;
    opacity: 1;
  }

  .checklist .check-del .btn-quiet {
    display: grid;
    place-items: center;
    width: var(--tap);
    height: var(--tap);
    margin: 0 -8px 0 -12px;
    color: var(--ink-2);
  }

  .checklist .check-del .btn-quiet::before {
    content: none; /* the button is the 44px target itself */
  }
}

/* ── inbox (frame 12) ───────────────────────────────────────────────────
   A list head, then the cards; the composer is a wide screen's only. Each
   card ends in its own action row under a hairline — Dismiss, the escape,
   far left; Adjust · Add right, Add last. */
.route-escape {
  margin-right: auto;
}

.capture-card.card .route-actions {
  margin-top: 14px;
  padding-top: 12px;
  border-top: 1px solid var(--hairline);
}

.inbox-empty {
  margin-top: 26px;
}

/* In the capture sheet the card's action row stays in reach: with Adjust
   open the sheet scrolls, and Add task — the primary — sat below its fold.
   Pinned to the sheet's foot on the sheet's own ground, so the panel
   scrolls under it. (A short card never reaches the fold; nothing moves.)
   It pins below the sheet's bottom padding (the sticky edge is inside it),
   so it reaches down through that padding — its own padding, cancelled by a
   negative margin so the sheet's scroll height is unchanged. */
.sheet .capture-card .route-actions {
  --sheet-pad-b: max(20px, calc(20px + env(safe-area-inset-bottom, 0px) - var(--kb, 0px)));
  position: sticky;
  bottom: calc(0px - var(--sheet-pad-b));
  z-index: 1;
  margin-bottom: calc(0px - var(--sheet-pad-b));
  padding: 12px 0 calc(12px + var(--sheet-pad-b));
  background: var(--raised);
  box-shadow: 0 -1px 0 var(--hairline);
}

@media (max-width: 640px) {
  .capture-card.card > p:first-child,
  .capture-card.card .route-parse {
    font-size: var(--fs-row);
    line-height: 1.4;
  }

  .capture-card.card .route-escape {
    color: var(--ink-2);
    font-size: 15px;
  }
}

/* ── capture sheet ──────────────────────────────────────────────────────
   textarea · notes line · type (segmented) · mic · Capture — the primary
   last, and with the keyboard up the type and Capture ride on the keys. The
   type buttons keep `.type`/`.on` (setKindChip) and take the segment's
   look; inside a raised sheet the chosen one lifts a step above it. */
.types.seg {
  margin: 12px 0 0;
}

.types.seg .type {
  padding: 0;
  background: none;
  border: 0;
  border-radius: 11px;
  font-family: inherit;
  font-size: var(--fs-value);
  font-weight: 400;
  letter-spacing: normal;
  text-transform: none;
  color: var(--ink-2);
}

.types.seg .type.on {
  background: color-mix(in srgb, var(--ink) 9%, var(--raised));
  color: var(--ink);
  font-weight: 500;
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent) 75%, transparent);
}

.types.seg .type:focus-visible {
  outline: 2px solid var(--ink-2);
  outline-offset: -2px;
}

@media (pointer: coarse) {
  .types.seg .type::before {
    content: none; /* 44px already */
  }
}

.sheet .micbar {
  margin-top: 12px;
}

.sheet .mic {
  width: var(--tap);
  height: var(--tap);
}

.sheet .send {
  height: var(--tap);
}

/* ═══ end of SLICE C ═══════════════════════════════════════════════════ */

/* ═══════════════════════════════════════════════════════════════════════
   SLICE D — settings, search (⌘K sheet), login
   ═══════════════════════════════════════════════════════════════════════ */

/* ── settings: groups of rows ───────────────────────────────────────────
   DESIGN, One-handed layout › Settings: a mono label, then a card of 52px
   rows (14px radius) — the .choice-rows.list-rows primitive. The page is
   the same composition at every width: the primitive's rows are phone-
   scoped, so the wide-screen block at the end of this part restates the
   few rules a wide screen needs. A per-action <form> in a card flattens,
   so its button is the row; the first row of a card has no hairline above
   it however it is wrapped. */
.settings-page .set-group {
  margin-top: 26px;
}

.settings-page .list-head {
  margin-bottom: 0;
}

.set-label {
  display: block;
  margin: 0 0 10px; /* on the gutter, as every page's labels are */
}

.set-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  margin: 0 0 10px;
}

.set-head .set-label {
  margin: 0;
}

.settings-page .set-card {
  border-radius: 14px;
}

.set-card form {
  display: contents;
}

.set-card > form > .choice-row {
  border-top: 1px solid var(--hairline);
}

.set-card > form:first-child > .choice-row {
  border-top: 0;
}

.settings-page .set-note {
  margin: 8px 0 0;
}

.settings-page .form-error {
  margin: 0 0 10px;
}

/* A row that only reads (Home, Connected): no pointer, no press. */
.choice-row.set-static {
  cursor: default;
}

.choice-val.set-none {
  color: var(--ink-3);
}

/* The page's one red: Google's grant died — trouble, and only he can fix it. */
.set-trouble .choice-label,
.set-trouble .choice-val {
  color: var(--danger);
}

.set-card .choice-row.quiet .glyph {
  color: var(--ink-3);
}

/* The chosen-row ground is for choices; a switch row stays flat, on or off. */
.settings-page .choice-row.set-switch {
  background: none;
}

.choice-row.set-switch:disabled {
  opacity: 0.6;
  cursor: progress;
}

/* ── settings sheets ────────────────────────────────────────────────────
   The capture sheet's .sheet (bottom, raised, grabber, rides --kb, ≤ the
   visible height), capped at 75% of what is visible (DESIGN: Sheets
   anchor to the bottom). Without JS a row's link targets its sheet, and
   :target puts it up with a shadow for a scrim. */
.set-sheet {
  max-height: calc(var(--vvh, 100dvh) * 0.75);
}

.js .set-nojs {
  display: none;
}

/* The sheet takes focus when it has no field to give it to (a screen
   reader starts there); that is not a keyboard position to ring. */
.set-sheet:focus {
  outline: none;
}

html:not(.js) .set-sheet:target {
  transform: translateX(-50%);
  pointer-events: auto;
  box-shadow: 0 0 0 100vmax rgba(5, 6, 8, 0.55);
}

.set-sheet .field + .field {
  margin-top: var(--sp-16);
}

/* Fields on the raised sheet sit in the page's ground, as the search
   field does, or they would vanish into it. */
.set-sheet .field input {
  background: var(--bg);
}

.set-actions {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--sp-8);
  margin-top: var(--sp-24);
}

.set-actions .btn:not(.btn-icon) {
  width: auto;
  min-height: var(--tap);
  padding: 0 22px;
}

.set-actions .apart {
  margin-right: var(--sp-8);
}

/* Colour: the palette as seven 44px swatches, one line at 375. The radio
   lies invisible over its swatch (the .pick technique); chosen = an amber
   ring and a check in the page ground. */
.set-colours {
  min-width: 0;
  margin: var(--sp-16) 0 0;
  padding: 0;
  border: 0;
}

.set-colours legend {
  margin-bottom: 10px;
  padding: 0;
}

.swatches {
  display: grid;
  grid-template-columns: repeat(7, 44px);
  column-gap: clamp(2px, calc((100% - 308px) / 6), 12px);
}

.swatch {
  position: relative;
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  cursor: pointer;
}

.swatch-tick {
  color: transparent;
}

@supports selector(:has(*)) {
  .swatch input {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    margin: 0;
    opacity: 0;
    cursor: pointer;
  }

  /* Inset, so the ring stays inside the swatch's 44 and never eats the
     few pixels between swatches at 375. */
  .swatch:has(input:checked) {
    box-shadow:
      inset 0 0 0 2.5px var(--accent),
      inset 0 0 0 5px var(--raised);
  }

  .swatch:has(input:checked) .swatch-tick {
    color: var(--bg);
  }

  .swatch:has(input:focus-visible) {
    outline: 2px solid var(--ink-2);
    outline-offset: 6px;
  }
}

.set-quiet-row {
  display: flex;
  align-items: center;
  gap: var(--sp-12);
}

.set-sheet .set-quiet-row input {
  flex: none;
  width: 84px;
}

.set-quiet-note {
  font-size: 14px;
  color: var(--ink-3);
}

/* Check rows (calendars): several on at once, so no chosen ground — the
   amber check alone says which. */
@supports selector(:has(*)) {
  .set-checks .choice-row input[type="checkbox"] {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    margin: 0;
    opacity: 0;
    cursor: pointer;
  }

  .set-checks .choice-row:has(input:checked) {
    background: none;
  }

  .set-checks .choice-row:has(input:checked) .choice-tick {
    color: var(--accent);
  }

  .set-checks .choice-row:has(input:focus-visible) {
    outline: 2px solid var(--ink-2);
    outline-offset: -2px;
  }
}

/* ── the remote zone's sheet: a search docked at its foot ───────────────
   Matches stack UP from the field (column-reverse), best lowest, on
   --raised — the row Enter takes. The list is the page's ground, as the
   field is, so both read as one instrument on the raised sheet. */
/* The sheet itself never scrolls here — only the list does — so the field
   stays on the keyboard however many zones match. */
.set-sheet.tz-sheet {
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.tz-sheet .grabber,
.tz-sheet .sheet-head {
  flex: none;
}

.tz-form {
  display: flex;
  flex-direction: column;
  flex: 0 1 auto;
  min-height: 0;
}

.tz-form .set-search,
.tz-form .tz-hint {
  flex: none;
}

.settings-page .set-sheet .tz-list {
  flex-direction: column-reverse;
  flex: 0 1 auto;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  background: var(--bg);
  border: 1px solid var(--hairline);
  border-radius: 14px;
  margin-bottom: 10px;
}

.tz-list:empty {
  display: none;
}

/* Upward, so a row's hairline is on its top and the topmost (the DOM's
   last) has none. */
.set-sheet .tz-list .choice-row {
  border-top: 1px solid var(--hairline);
}

.set-sheet .tz-list .choice-row:last-child {
  border-top: 0;
}

.set-sheet .tz-list .choice-row.sel {
  background: var(--raised);
}

.tz-list .is-current .choice-tick {
  color: var(--accent);
}

.tz-list .choice-val {
  max-width: 45%;
  overflow: hidden;
  text-overflow: ellipsis;
}

.tz-hint {
  margin: 0 4px 12px;
}

.set-search {
  display: flex;
  align-items: center;
  gap: 10px;
  height: 52px;
  padding: 0 4px 0 14px;
  background: var(--bg);
  border: 1.5px solid var(--hairline);
  border-radius: 14px;
}

.set-search:focus-within {
  border-color: var(--accent);
}

.set-search .glyph {
  color: var(--ink-3);
}

.set-search input {
  flex: 1;
  min-width: 0;
  height: 100%;
  padding: 0;
  background: none;
  border: 0;
  color: var(--ink);
  font: inherit;
  font-size: var(--fs-choice);
}

.set-search input:focus {
  outline: none;
}

.set-clear,
.ks-clear {
  display: inline-grid;
  place-items: center;
  flex: none;
  width: var(--tap);
  height: var(--tap);
  padding: 0;
  background: none;
  border: 0;
  border-radius: 10px;
  color: var(--ink-3);
  cursor: pointer;
}

/* ── areas, "edit" mode: rows that move ─────────────────────────────── */
.area-row {
  cursor: grab;
}

.area-up {
  display: contents;
}

.area-up .btn {
  margin-left: auto;
}

/* ── settings on a wide screen ──────────────────────────────────────────
   The same rows and sheets, in a 560px measure (System Settings' shape).
   The primitive's rows are phone-only; these are the rules they need here. */
@media (min-width: 641px) {
  .settings-page {
    max-width: 560px;
  }

  .settings-page .choice-rows {
    display: flex;
    flex-direction: column;
    background: var(--surface);
    border: 1px solid var(--hairline);
    border-radius: 14px;
    overflow: clip;
  }

  .settings-page .choice-row {
    position: relative;
    display: flex;
    align-items: center;
    gap: var(--sp-12);
    width: 100%;
    min-height: var(--row-list);
    margin: 0;
    padding: 0 14px 0 18px;
    background: none;
    border: 0;
    border-top: 1px solid var(--hairline);
    border-radius: 0;
    color: var(--ink);
    font: inherit;
    font-size: var(--fs-row);
    text-align: left;
    text-decoration: none;
    cursor: pointer;
  }

  .settings-page .choice-row:first-child,
  .settings-page .set-card > form:first-child > .choice-row {
    border-top: 0;
  }

  .settings-page .choice-row.quiet {
    color: var(--ink-2);
  }

  .settings-page .choice-row > .dot {
    margin-right: 0; /* the row's gap spaces it */
  }

  .settings-page .choice-row.set-static {
    cursor: default;
  }

  .settings-page a.choice-row:hover,
  .settings-page button.choice-row:hover,
  .settings-page label.choice-row:hover {
    background: color-mix(in srgb, var(--ink) 4%, transparent);
  }

  .settings-page .choice-row:focus-visible {
    outline: 2px solid var(--ink-2);
    outline-offset: -2px;
  }

  .settings-page .choice-label {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .settings-page .choice-val {
    flex: none;
    font-size: 14.5px;
    color: var(--ink-2);
  }

  .settings-page .choice-row .glyph {
    color: var(--ink-3);
  }

  .settings-page .choice-tick {
    color: transparent;
  }

  .set-sheet {
    padding-left: 28px;
    padding-right: 28px;
  }
}

/* ── search: a sheet riding the keyboard (phone) ────────────────────────
   DESIGN, One-handed layout › Search. The field at the sheet's foot, the
   results stacking up from it (column-reverse: best match lowest, on
   --raised, the row Go opens before any arrow key moves). The DOM keeps
   the wide screen's order, so ⌘K's centred panel is unchanged there, and
   the sheet's own order is set with `order`. */
.ks-grab,
.ks-field .glyph,
.ks-clear,
.ks-go,
.ks-hint {
  display: none;
}

@media (min-width: 641px) {
  .ks-field {
    display: contents;
  }
}

@media (max-width: 640px) {
  .ksearch {
    top: auto;
    bottom: 0;
    left: 50%;
    display: flex;
    flex-direction: column;
    width: 100%;
    max-height: calc(var(--vvh, 100dvh) * 0.75);
    padding: 10px var(--gutter) max(12px, calc(12px + env(safe-area-inset-bottom, 0px) - var(--kb, 0px)));
    border: 0;
    border-top: 1px solid var(--hairline);
    border-radius: 22px 22px 0 0;
    box-shadow: 0 -18px 60px rgba(0, 0, 0, 0.6);
    transition: transform 0.32s cubic-bezier(0.32, 0.72, 0.33, 1);
  }

  .ksearch.away {
    opacity: 1;
    transform: translateX(-50%) translateY(112%);
    box-shadow: none;
  }

  .ksearch.in {
    transform: translateX(-50%) translateY(calc(0px - var(--kb, 0px)));
  }

  .ksearch.dragging {
    transition: none;
  }

  .ks-grab {
    display: block;
    flex: none;
    order: 0;
  }

  .ks-hint {
    order: 1;
    margin: 0 4px 12px;
  }

  /* While the search sheet is up the bar stands down: a layer's field no
     longer hides it (the page under a layer keeps its shape), and iOS keeps
     a fixed bottom bar above the keyboard, where it would show, dimmed,
     between the results and the keys. */
  @supports selector(:has(*)) {
    body:has(.ksearch.in) .bottombar {
      display: none;
    }
  }

  /* A sibling, not .ksearch:has(… :has()) — :has() doesn't nest. */
  .ksearch-results:not(:has(*)) ~ .ks-hint {
    display: block;
  }

  .ksearch-results {
    order: 2;
    display: flex;
    flex-direction: column-reverse;
    flex: 0 1 auto;
    min-height: 0;
    max-height: none;
    background: var(--bg);
    border: 1px solid var(--hairline);
    border-radius: 14px;
  }

  .ksearch-results:not(:has(*)) {
    display: none;
  }

  .ks-field {
    order: 3;
    display: flex;
    flex: none;
    align-items: center;
    gap: 10px;
    height: 52px;
    margin-top: 10px;
    padding: 0 4px 0 14px;
    background: var(--bg);
    border: 1.5px solid var(--hairline);
    border-radius: 14px;
  }

  .ks-field:focus-within {
    border-color: var(--accent);
  }

  .ks-field .glyph,
  .ks-clear {
    display: inline-grid;
    color: var(--ink-3);
  }

  .ksearch .ks-field input {
    flex: 1;
    min-width: 0;
    height: 100%;
    padding: 0;
    border: 0;
    font-size: var(--fs-choice);
  }

  .ksearch .ks-field input::-webkit-search-cancel-button {
    -webkit-appearance: none;
    appearance: none;
  }

  /* Upward: a row's hairline is on its top, the topmost row has none. */
  .ksearch .ks-row {
    align-items: center;
    gap: var(--sp-12);
    min-height: var(--row-list);
    padding: 0 12px;
    border-bottom: 0;
    border-top: 1px solid var(--hairline);
  }

  .ksearch .ks-row:last-child {
    border-top: 0;
  }

  .ksearch .ks-row:hover {
    background: none;
  }

  .ksearch .ks-row.sel,
  .ksearch-results:not(:has(.sel)) .ks-row:first-child {
    background: var(--raised);
  }

  .ksearch .ks-row.sel .ks-go,
  .ksearch-results:not(:has(.sel)) .ks-row:first-child .ks-go {
    display: inline-block;
    color: var(--ink-3);
  }

  .ksearch .ks-kind {
    flex: none;
    width: 64px;
    padding: 0;
    border: 0;
    font-size: var(--fs-label);
    letter-spacing: 0.14em;
  }

  .ksearch .ks-title {
    font-size: var(--fs-row);
  }

  .ksearch .ks-done {
    font-size: var(--fs-label);
    letter-spacing: 0.14em;
  }

  .ksearch .ks-go {
    margin-left: auto;
  }

  .ksearch .ks-done + .ks-go {
    margin-left: 0;
  }

  .ksearch .ks-empty {
    margin: 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  .ksearch {
    transition: none;
  }
}

/* ── login: a decision screen (phone), the centred card (wide) ─────────
   DESIGN, One-handed layout › Login / TOTP: the wordmark and tagline in
   the top third, the fields and a full-width 52px Log in at the bottom,
   on the keyboard while typing (the screen is the visible viewport, --vvh,
   as a decision screen is). Errors in --danger between the fields and the
   button. Lockout reads from the top, with nothing to press. */
.auth {
  align-content: center;
}

.auth-head {
  text-align: center;
  margin-bottom: 18px;
}

.auth-head .wordmark {
  margin-bottom: 0;
}

.auth-tagline {
  margin: 8px 0 0;
  font-family: var(--mono);
  font-size: 9.5px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-3);
}

.auth-form {
  width: 100%;
}

.auth .auth-error {
  margin: 0;
  color: var(--danger);
}

@media (max-width: 640px) {
  .auth {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    min-height: var(--vvh, 100svh);
    padding: calc(var(--q-top, var(--sp-48)) + env(safe-area-inset-top, 0px)) var(--gutter)
      max(20px, calc(20px + env(safe-area-inset-bottom, 0px) - var(--kb, 0px)));
  }

  .auth-head {
    margin: 9vh 0 0;
  }

  .auth .auth-head .wordmark {
    font-size: 26px;
  }

  .auth-tagline {
    margin-top: 12px;
    font-size: var(--fs-label);
  }

  .auth .auth-form,
  .auth .auth-message {
    max-width: none;
    padding: 0;
    background: none;
    border: 0;
    border-radius: 0;
  }

  .auth-form {
    margin-top: auto;
    padding-top: var(--sp-24);
  }

  .auth-submit {
    min-height: 52px;
    font-size: var(--fs-choice);
  }

  .auth-lockout .auth-head {
    margin-top: var(--sp-24);
  }
}

/* With the keyboard up the visible screen is ~430pt: the fields and Log in
   need all of it, so the wordmark steps aside while it is up (the form is
   anchored to the bottom, so nothing under the thumb moves). Keyed on the
   keyboard (.kb-up, app.js), not on focus: the username field is
   autofocused, and iOS raises no keyboard for that. */
@media (max-width: 640px) {
  .kb-up .auth-login .auth-head {
    display: none;
  }
}

@media (max-width: 640px) {
  .auth-message {
    margin-top: var(--sp-48);
    text-align: center;
  }
}

/* ═══ end of SLICE D ═══════════════════════════════════════════════════ */
