/* 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;
  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-row {
  display: flex;
  /* Wrappable so a field can claim a whole line — see the chip rule below.
     Nothing else in a row ever wraps: `.field-row .field` sets min-width: 0,
     so an ordinary field's hypothetical size is 0 and the line always fits.
     Row gap matches .task-form's own 14px, since a wrapped line is a new row
     of fields and should sit the same distance from the one above it. */
  flex-wrap: wrap;
  gap: 14px 10px;
  /* flex-end so a bare checkbox lines up with its neighbour's INPUT rather
     than floating level with that neighbour's caption (settings, remote tz). */
  align-items: flex-end;
}

/* A field that grew preset chips takes a line of its own. Four chips do not
   fit a third of a form: on the project form and the routing card they wrapped
   2 + 2 under the control while the fields beside them stood above a column of
   dead space, and the typed input hung past the form's right edge on the way
   (Jaroslav, 2026-08-15). Given a full line, control and chips read as one
   left-aligned block, exactly like the task form's due and remind-me.

   min-width, NOT flex-basis: the basis is the MAIN axis, and this row turns
   into a column below 520px, where `flex-basis: 100%` would set the field's
   HEIGHT. min-width means the same thing on both axes, so the phone block
   needs no exception to it. */
.field-row .field:has(.quick-row) {
  min-width: 100%;
}

/* Tops align in a chip-carrying row: flex-end would sink the fields on the
   first line to the bottom of the tallest one (2026-08-14 — before the rule
   above, that was DUE growing presets and dragging REPEAT ~150px below its
   own label; now it is the project form's one-line "fixed at creation."
   sitting beside a 44px select). Scoped, because the settings checkbox above
   still wants the bottom. */
.field-row:has(.quick-row) {
  align-items: flex-start;
}

.field-row .field {
  flex: 1;
  min-width: 0; /* flex children default to min-width:auto — date inputs
                   would refuse to shrink and overlap their neighbours */
}

.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%;
}

@media (max-width: 520px) {
  /* The :has() rule above is (0,2,0) — :has() takes the specificity of its
     argument — and @media adds none, so a bare `.field-row` here loses to it
     and every chip-carrying row would stack at shrink-to-fit width instead of
     stretching. Matched selector, matched specificity. Third time this file
     has been bitten by that; see the notes at .quick-row and .datetime-parts. */
  .field-row {
    flex-direction: column;
    align-items: stretch;
    gap: 14px;
  }

  /* A SEPARATE rule, never grouped with the one above: a selector list is not
     forgiving, so on a browser without :has() the whole thing would be
     dropped — and every two-column row would stay side by side on a phone,
     far worse than the alignment this exists to restore. */
  .field-row:has(.quick-row) {
    align-items: stretch;
  }
}

.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 — the `display` value is moot inside `.detail-actions`,
   which blockifies its items anyway. */
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;
}

.area-form {
  display: flex;
  align-items: center;
  gap: 10px;
  flex: 1;
  min-width: 0;
}

.area-form input[name="name"] {
  flex: 1;
  min-width: 0;
  background: transparent;
  border: 0;
  border-radius: 6px;
  padding: 6px 8px;
  color: var(--ink);
  font: inherit;
}

.area-form input[name="name"]:focus {
  outline: 2px solid var(--accent);
  background: var(--raised);
}

.area-form select,
.area-add select {
  font-family: var(--sans); /* interactive text is sans — mono is for labels */
  font-size: 12.5px;
  background: var(--raised);
  color: var(--ink-2);
  border: 1px solid var(--hairline);
  border-radius: 7px;
  padding: 4px 6px;
}

.area-add {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 0 0;
}

.area-add input {
  flex: 1;
  min-width: 0;
  background: var(--raised);
  border: 1px solid var(--hairline);
  border-radius: 8px;
  padding: 7px 10px;
  color: var(--ink);
  font: inherit;
}

/* 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 85vh is still the cap. */
  max-height: min(85vh, 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 color-mix(in srgb, var(--accent) 35%, transparent);
  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: color-mix(in srgb, var(--accent) 35%, transparent);
}

.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-color: color-mix(in srgb, var(--a-rejobs) 40%, 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; the phone rule
   in the Thumb zone block moves it. Dismiss carries `danger-action`, so it
   pins to the far edge by the same mechanism every other row uses. */
.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);
  }

  .content {
    padding: calc(22px + env(safe-area-inset-top, 0px)) 20px
      calc(120px + env(safe-area-inset-bottom, 0px));
    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). No exception for the capture
   sheet: its scrim already covers the bar (and close() blurs whatever field
   it leaves focused, or the bar would stay gone). 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
      )
      .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 */
}

.side-accent {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-ink);
}

.menu-sheet {
  padding-bottom: calc(28px + env(safe-area-inset-bottom, 0px));
}

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

.menu-nav a {
  font-size: 16px;
  color: var(--ink);
  padding: 13px 4px;
  border-bottom: 1px solid var(--hairline);
}

.menu-nav .btn-quiet {
  width: 100%;
  text-align: left;
  font-size: 16px;
  padding: 13px 4px;
  color: var(--ink); /* identical to the other menu items */
}

/* ── 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.
   11px each way fits a task row with room to spare — the row's 12px gap
   stops it 1px short of the title link, its 11px padding keeps it inside
   the row. A checklist row is tighter; see .check-item. ::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: -11px;
  }
}

.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;
}

.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;
}

/* A hairline separates the footer from the record, so the row reads as
   belonging to the panel rather than floating under it. */
.detail-panel .detail-actions {
  margin-top: 18px;
  padding-top: 16px;
  border-top: 1px solid var(--hairline);
}

@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);
}

/* The confirming action sits on the trailing edge at EVERY width: that's
   the macOS/iOS convention this design already follows elsewhere, it's
   where the thumb lands on a phone, and one order across both viewports is
   one muscle memory instead of two. A mouse pays almost nothing for the
   travel, so there's no reason to disagree with the phone. */
.detail-actions {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 18px;
  margin-top: 22px;
}

/* …and the irreversible action is pinned to the OPPOSITE end, so it is
   never adjacent to the button you actually came to press. Distance is the
   cheapest safeguard there is, and it costs a destructive action nothing to
   be far away — you arrive at it deliberately or not at all. */
.danger-action {
  margin-right: auto;
}

/* 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. */
.detail-actions .btn-quiet,
.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. */
.detail-actions form,
.rs-actions form {
  display: contents;
}

.detail-actions .btn {
  width: auto;
  padding: 10px 22px;
}

/* An icon-only action is square-ish rather than pill-shaped — 44px each way
   keeps the touch target while the glyph stays centred (a.btn already
   flexes; buttons centre their content for free). */
.detail-actions .btn:has(> svg) {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 52px;
  padding: 10px 16px;
}

/* On a phone a detail footer has to fit ONE line. A project's (Delete ·
   Pause · Mark done · Edit) needed 385px of the panel's 301 and wrapped
   Edit onto a line of its own, so the row read as two unrelated rows. The
   secondary buttons give up side padding (never height — they stay 44px)
   and sit 8px apart; the primary keeps its width, which is also what keeps
   it the heaviest thing in the row. That makes 275px, and the 26px left
   over all goes to Delete's auto margin — distance is the safeguard, so it
   is the one gap that should be generous. Scoped to the detail PANEL: a
   form's Cancel · Save already fits and keeps its 18px. If a row ever
   outgrows the line anyway it wraps from the left, so whatever ends the
   row — the primary, when there is one — still lands bottom-right. */
@media (max-width: 640px) {
  .detail-panel .detail-actions {
    gap: 8px;
  }

  .detail-panel .detail-actions .btn-ghost {
    padding-left: 10px;
    padding-right: 10px;
  }
}

.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; everything else here is keyed on those
   classes. Without them — a wide screen, or no JS — the sections stack into
   the one-screen form, and every control below that only means something
   while stepping stays hidden.

   Specificity is deliberate throughout. These rules override `.btn`,
   `.detail-actions .btn-quiet` (0,2,0) and the form's own layout, and @media
   adds none — so each one names enough classes to win on its own rather
   than by where it happens to sit in the file. */

/* 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 .step-count,
.stepform .step-only,
.stepform .recap,
.stepform .step-foot .step-back,
.stepform .step-foot .step-next {
  display: none;
}

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

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

.stepform.stepping .step-only {
  display: inline;
}

.stepform.stepping .sec-head .step-off {
  display: none; /* "← back" would read as a second Back; see the template */
}

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

/* Back from the second step on; Next until the last, which has Save. A
   destructive action in the row (Delete, on an edit) waits for the last step
   too: it belongs beside Save, not beside Next on the first screen. */
.stepform.stepping .step-foot .step-cancel,
.stepform.stepping:not(.at-last) .step-foot .step-save,
.stepform.stepping:not(.at-last) .step-foot .danger-action {
  display: none;
}

/* "Delete" is 41px of text — the probe found it under the 44px target at
   the footer's left edge. Centred in a 44px box, like Back. */
.stepform.stepping .step-foot .danger-action {
  justify-content: center;
  min-width: 44px;
}

/* Back is a secondary button (`btn btn-ghost`, DESIGN button tiers): in a
   row with a quiet Delete, a quiet Back would read as a second regret. */
.stepform.stepping:not(.at-first) .step-foot .step-back {
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.stepform.stepping:not(.at-last) .step-foot .step-next {
  display: inline-block;
}

/* Arrived from the recap (`.jumped`): Done is the way back to it, and a Back
   beside it would only go to the same place. Done alone — on every jumped
   step, the first one included. Outweighs the rule above. */
.stepform.stepping.jumped:not(.at-first) .step-foot .step-back {
  display: none;
}

/* The footer stands on the bottom bar, which stays on a stepped page:
   capture is sacred, and a thought that arrives mid-form must be one tap
   from the sheet without abandoning the form (decided 2026-10-03). Fixed,
   directly above the bar (--bar-h), never over it. Lifted by --kb (app.js,
   visualViewport) like the capture sheet — which only ever moves it while
   typing, see below. */
.stepform.stepping .step-foot {
  position: fixed;
  left: 0;
  right: 0;
  bottom: var(--bar-h);
  z-index: 40; /* over the page and the bar (30), under a sheet's scrim (50) */
  margin: 0;
  padding: 10px 20px;
  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);
}

/* While a field has focus the bar stands down (the rule by .bottombar, same
   list, same gates), and the footer takes its place: on the bottom edge,
   riding the keyboard. The home-indicator inset only matters with the
   keyboard down — with it up the keyboard covers that strip — so the
   padding gives it back as the keyboard rises. */
@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
      )
      .stepform.stepping .step-foot {
      bottom: 0;
      padding-bottom: max(10px, calc(env(safe-area-inset-bottom, 0px) + 10px - var(--kb, 0px)));
    }
  }
}

/* Footer and bar are one stack: the bar's own background fades in over its
   top ~36px, which under a solid footer let the page show through between
   the two. */
.stepform-page .bottombar {
  background: var(--bg);
}

/* The page clears both: the bar, the ~65px footer on it, and a breath. */
.stepform-page .content {
  padding-bottom: calc(var(--bar-h) + 90px);
}

/* 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;
  }

  .stepform.stepping .step-foot {
    transition: none;
  }
}

/* The recap: every earlier step as a row that jumps back to it. */
.recap {
  list-style: none;
  margin: 0;
  padding: 0;
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: 14px;
}

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

.recap-row {
  display: grid;
  grid-template-columns: 52px minmax(0, 1fr) auto;
  align-items: center;
  gap: 10px;
  width: 100%;
  min-height: 50px;
  padding: 6px 14px; /* room above and below a two-line title */
  background: none;
  border: 0;
  color: var(--ink);
  font: inherit;
  font-size: 15px;
  text-align: left;
  cursor: pointer;
}

/* The chevron says "this row goes somewhere"; drawn, so it sits on the
   row's centre line rather than a font's baseline. */
.recap-row::after {
  content: "";
  width: 7px;
  height: 7px;
  border-top: 1.6px solid var(--ink-3);
  border-right: 1.6px solid var(--ink-3);
  transform: rotate(45deg);
  margin-right: 3px;
}

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

/* What it IS reads best whole: a long title gets two lines before it is
   cut, where the other rows keep to one. */
.recap-value[data-recap="what"] {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  white-space: normal;
  overflow-wrap: anywhere;
}

/* "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. */
.field[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);
}

.synced-note {
  font-size: 11.5px;
  margin-left: 8px;
}

.sync-now-form {
  margin-top: 12px;
}

/* Settings (functional layer — designed pass comes in P5) */
.check-field {
  display: flex;
  align-items: flex-end;
  padding-bottom: 10px;
}

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

.check-label input[type="checkbox"] {
  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 (a layout
   change: settings' calendar rows grow from 32px to 44 with it).
   .check-field's 10px bottom padding centred a ~22px label on the 44px
   input beside it; a 44px label is already centred, and keeping the
   padding lifted it ~11px above the input's centreline wherever the row
   is side by side (an iPad). */
@media (pointer: coarse) {
  .check-label {
    min-height: 44px;
  }

  .check-field {
    padding-bottom: 0;
  }
}

.cal-list {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.cal-row {
  padding: 7px 0;
  border-bottom: 1px solid var(--hairline);
}

.disconnect-form {
  margin-top: 18px;
}

.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: -13px;
  }
}

.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);
}

/* The per-area slipping fuse: narrow, quiet, placeholder shows the default. */
.quiet-days {
  width: 52px;
  flex: none;
  text-align: center;
  /* Matches .area-form select — it sits right beside one. */
  background: var(--raised);
  border: 1px solid var(--hairline);
  border-radius: 7px;
  padding: 4px 6px;
  font-family: var(--sans);
  font-size: 12.5px;
  color: var(--ink-2);
  appearance: textfield;
}

.quiet-days::-webkit-inner-spin-button,
.quiet-days::-webkit-outer-spin-button {
  appearance: none;
  margin: 0;
}

/* 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 .field-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);
}
