/* ============================================================================
   DAILY MENU v2 — component CSS
   Lifted/adapted from docs/mockups/v2-mockup.html + docs/DESIGN.md.
   ========================================================================== */

.frame {
  max-width: 1100px; margin: 0 auto; padding: 26px 16px 80px;
  min-height: 100vh; position: relative;
}

/* ============================================================================
   FOCUS (DESIGN §8 quality floor, CX m6): visible on every interactive
   element — rows, chips, checkboxes, picker rows, config inputs, buttons.
   ========================================================================== */
:focus-visible {
  outline: 2px solid var(--now); outline-offset: 2px;
}

/* CX nit: native checkbox accents render OS-default cold blue — the one cold
   color in an otherwise warm UI (login "Remember", feed "enabled", "Person 2
   enabled"). Tint them with the warm ember instead. */
input[type="checkbox"] { accent-color: var(--now); }
/* CX P2 (config tap targets audit): a bare `<input type=checkbox>` is ~14x14px
   tall — the whole row (checkbox + "Enabled" text) is already one native
   click target via the wrapping <label> (e.g. Config's "Person 2 enabled"),
   but was only ~19px tall, short of --tap-min vertically. `.chkwrap` grows an
   invisible ::before to the label's own full width, matching the same
   technique the rest of the app uses (349621f) rather than just the
   checkbox glyph's tiny box. Its own `.field` caption label sits directly
   above with only a 5px real gap (`.field { gap: 5px }`) — not enough room
   to grow symmetrically to --tap-min without the zone's *top* edge reading
   as overlapping that caption, so it grows mostly downward (into the
   field's own trailing whitespace, which is genuinely free) and only as
   far up as the real gap allows. */
.chkwrap { position: relative; display: inline-flex; align-items: center; cursor: pointer; }
.chkwrap::before {
  content: ""; position: absolute; top: -4px; left: 0; right: 0; bottom: -21px;
  border-radius: var(--r-input);
}
/* Unlike a bare-button's own ::before (where the pseudo IS the button being
   clicked), this pseudo belongs to the <label> and — being `position:
   absolute` — paints in front of the real, un-positioned `<input
   type=checkbox>` nested inside it, so it silently intercepted clicks aimed
   directly at the checkbox itself (e.g. Playwright's `.check()`, or a mouse
   user who's used to clicking the exact glyph). Lift the checkbox's own
   stacking above the pseudo so direct clicks reach it unchanged, while the
   invisible zone still catches clicks in the surrounding dead space (the
   "Enabled" text, and the padding this fix adds) exactly as intended. */
.chkwrap input[type=checkbox] { position: relative; z-index: 1; }
@media (hover: hover) and (pointer: fine) {
  .chkwrap:hover::before { background: color-mix(in srgb, currentColor 9%, transparent); }
}

/* ============================================================================
   HEADER (day view)
   ========================================================================== */
.daybar { display: flex; align-items: center; gap: var(--sp-3); padding: 4px 4px 14px; flex-wrap: wrap; }
/* `.navrow`/`.iconrow` (Index.cshtml) exist purely as CSS hooks for the
   narrow-width regrouping below — at the default (wide) layout they must be
   fully transparent to the existing `.daybar` flex layout, so their children
   participate in `.daybar`'s own flex formatting context exactly as if the
   wrappers weren't there. */
.daybar .navrow, .daybar .iconrow, .daybar .navcore { display: contents; }
/* CX P1 (day-header, phone 375w / tablet 768w): plain flex-wrap let the nav
   cluster (prev arrow / date / clock / next arrow) split across two visually
   disjointed rows with the arrows landing far apart, and the icon cluster
   below it misaligned (centered nav row vs. flush-left icon row). Below
   ~820px (the same breakpoint the hour grid already collapses at), regroup
   into an explicit 2-row layout: row 1 is the nav unit (prev/date/clock/next
   kept together as one visual group), row 2 is the icon cluster (person
   toggle + home/week/stats/settings/kiosk-hide), consistently left-aligned
   under row 1. `.navrow`/`.iconrow` switch from `display:contents` to a real
   flex row here, so this is a plain layout swap rather than fighting `order`
   across many individually-targeted children. */
@media (max-width: 820px) {
  .daybar { flex-direction: column; align-items: stretch; flex-wrap: nowrap; }
  .daybar .navrow, .daybar .iconrow {
    /* `--sp-3` (not `--sp-2`) matches `.daybar`'s own original gap at wide
       widths — tap-targets.spec.ts asserts an 8px minimum real gap between
       the prev-day arrow and the date title; `--sp-2` (7px) undercut that. */
    display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap; width: 100%;
  }
  /* `#save` reserves a 90px min-width even while `visibility:hidden` (avoids
     desktop reflow as its text changes length) — at narrow widths that ate
     into the room the prev-arrow/date/next-arrow/clock nav unit needs to
     stay on one line, pushing prevDay onto its own wrapped row (the exact
     "arrows land on different rows" bug this fix exists to prevent). Give it
     `flex-basis: 100%` here so it always breaks onto its own line, ahead of
     the actual nav unit, instead of competing with it for width. */
  .daybar .navrow #save { flex-basis: 100%; }
  /* CX P1: the inseparable prev-arrow/date/next-arrow unit. A long weekday
     name plus the day-type-override chevron and clock don't always all fit
     on one 375px-wide line — flex-wrap:wrap on `.navrow` lets the chevron
     and clock spill onto their own line, but `.navcore` itself never breaks
     internally, so prevDay/date/nextDay always stay adjacent, on the same
     line, in that order (the literal "arrows end up on different rows on
     opposite sides" bug this fix targets). */
  .daybar .navcore {
    display: flex; align-items: center; gap: var(--sp-3); flex-wrap: nowrap; flex: none;
  }
  .daybar .spacer { display: none; } /* the flex spacer trick only makes sense in the old single-row layout */
}
/* CX round-2 P2-6: at 360px the day view's icon row is six 44px icons whose
   five 12px gaps total 324px against ~320px of frame width — a 4px overshoot
   that wrapped the last (kiosk Hide) icon alone onto a third toolbar row.
   Tightening only the gap below ~370px (6x44 + 5x8 = 304px) keeps every
   icon's full --tap-min hit area while fitting the row; 375px+ is unaffected. */
@media (max-width: 370px) {
  .daybar .iconrow { gap: 8px; }
}
.daybar .nav {
  position: relative; font-size: calc(var(--t-date) + 4px); color: var(--ink-dim); background: none; border: none;
  cursor: pointer; padding: 8px 10px; min-width: calc(var(--tap-min) + 4px); min-height: calc(var(--tap-min) + 4px);
  line-height: 1; border-radius: var(--r-input); z-index: 0;
  display: inline-flex; align-items: center; justify-content: center;
}
.daybar .nav:hover { color: var(--ink); }
.daybar .nav svg { width: 1.3em; height: 1.3em; }
/* Round hover halo behind the arrow glyph (owner feedback, day-header pass):
   CSS-only, uses skin ink/now tokens via color-mix so it reads correctly in
   every skin and both theme variants without hardcoded colors. Pointer-only
   (`hover: hover`) so it never sticks/flashes on a touch tap-and-release. */
@media (hover: hover) {
  .daybar .nav::before {
    content: ""; position: absolute; inset: 0; margin: auto; width: 40px; height: 40px;
    border-radius: 999px; background: color-mix(in srgb, var(--now) 16%, transparent);
    opacity: 0; transform: scale(.7); transition: opacity .15s ease, transform .15s ease; z-index: -1;
  }
  .daybar .nav:hover::before { opacity: 1; transform: scale(1); }
}
.daybar .datewrap { display: flex; flex-direction: column; align-items: flex-start; }
.daybar .date { font-size: var(--t-date); font-weight: 300; letter-spacing: .04em; white-space: nowrap; }
.daybar .date b { font-weight: 500; color: var(--ink); }
.daybar .date .wk { color: var(--ink-dim); }
/* Sub-heading under the date (owner feedback: effective-day-aware past/
   upcoming note, quiet + secondary). Uses a `.collapsed` class rather than
   the `hidden` attribute — tokens.css forces `[hidden]{display:none!important}`
   globally, which would fight the height transition. Reserves space via a
   max-height transition instead of popping in/out, so navigating between
   today and another day doesn't jump the rest of the header. */
.daysub {
  font-size: var(--t-badge); color: var(--ink-dim); letter-spacing: .02em; white-space: nowrap;
  overflow: hidden; max-height: 1.4em; opacity: 1; transition: max-height .18s ease, opacity .18s ease;
}
.daysub.collapsed { max-height: 0; opacity: 0; }
.savestate {
  position: relative; font: inherit; font-size: var(--t-label); color: var(--ink-dim); letter-spacing: .04em;
  display: inline-flex; align-items: center; gap: 6px; min-width: 90px;
  /* now a <button> (CX P3: stuck-outbox retry) — reset native button chrome
     so "Saved"/"Saving…" keep looking exactly like the old plain <span>. */
  background: none; border: none; padding: 0; text-align: left; cursor: default;
}
.savestate .dot { width: 7px; height: 7px; border-radius: 999px; background: currentColor; }
.savestate.saving { color: var(--ink-dim); }
.savestate.saved { color: var(--ok); }
.savestate.offline { color: var(--now); }
.savestate.hidden { visibility: hidden; }
/* CX P3 (stuck-outbox retry affordance): once app.js marks the pill
   `.retry-able` (offline/stuck with a non-empty queue), it becomes a real tap
   target — cursor + the same invisible --tap-min hit-zone technique as every
   other small control (349621f), since the visible pill is only ~90px wide
   and its own text height, well short of --tap-min tall. */
.savestate.retry-able { cursor: pointer; }
.savestate.retry-able::before {
  content: ""; position: absolute; top: 50%; left: 0; transform: translateY(-50%);
  width: 100%; height: var(--tap-min); border-radius: var(--r-input);
}
@media (hover: hover) and (pointer: fine) {
  .savestate.retry-able:hover::before { background: color-mix(in srgb, currentColor 9%, transparent); }
}
.daybar .spacer { flex: 1; }
.daybar .clock {
  font-size: var(--t-clock); font-weight: 300; color: var(--ink);
  font-variant-numeric: tabular-nums; letter-spacing: .02em;
}
.daybar .icon {
  font-size: 18px; color: var(--ink-dim); background: none; border: none; cursor: pointer;
  padding: 8px; min-width: var(--tap-min); min-height: var(--tap-min); border-radius: var(--r-input); line-height: 1;
  text-decoration: none; display: inline-flex; align-items: center; justify-content: center;
}
.daybar .icon:hover { color: var(--ink); }
.daybar .icon svg { width: 1.3em; height: 1.3em; }
.daybar .icon.spin svg { animation: spin .6s linear; }
.daybar .icon.stuck { color: var(--now); }
@keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }

/* ============================================================================
   HOVER HIT-AREA INDICATOR (spec-ui-polish.md task 4): on hover-capable,
   fine-pointer devices only, hovering a tappable control shows a subtle
   rounded-rect tint over its FULL effective hit area — including the
   invisible ::before/::after hit-zone padding added by the tap-min audit
   (349621f), not just the visible glyph/box. Reuses that same pseudo-element
   (a control never gets a second one) since it's already sized/positioned to
   the effective hit rect; here we just paint it on hover instead of leaving
   it fully invisible. `color-mix(in srgb, currentColor N%, transparent)`
   picks up each control's own ink color, so it reads correctly in every SKIN
   without a hardcoded color, and stays a no-op on touch (`hover: hover` +
   `pointer: fine` — a touch tap-and-release never sees `:hover`, and the
   global prefers-reduced-motion rule already kills any transition).
   Background-only (no border/shadow), so it never shifts layout.
   ========================================================================== */
@media (hover: hover) and (pointer: fine) {
  .daybar .icon:hover {
    background: color-mix(in srgb, currentColor 9%, transparent);
  }
}

/* ============================================================================
   KIOSK MODE (SPEC §11 stretch #6, ?kiosk=1): chrome-less day view for a
   Home Assistant iframe card / Fully Kiosk tile — no header nav/gear/stats
   links. Date label, clock, grid, and the person A/B/Both control stay
   (DESIGN §3.2's header layout is otherwise unchanged, just pared down).
   ========================================================================== */
html.kiosk .kiosk-hide { display: none !important; }
.newday, .copypill {
  font-size: var(--t-label); color: var(--now); letter-spacing: .04em;
  background: color-mix(in srgb, var(--now) 12%, transparent); border-radius: var(--r-chip); padding: 4px 12px;
}
/* Copy-to-journal feedback pill (same recipe as the New-day pill above; shown
   for ~2.5s by app.js). Failure stays unobtrusive: muted ink, not a hot
   alarm color — the action is retryable and nothing was lost. */
.copypill.fail { color: var(--ink-dim); background: color-mix(in srgb, currentColor 12%, transparent); }
/* Quiet marker + button (reused by the day view's "Go to today" and the week
   view's "This week" affordances). Shown whenever the viewed range differs
   from effective today/this-week. The day view's "· pinned" label itself was
   removed (owner feedback) — the sub-heading under the date (.daysub) now
   carries the past/upcoming messaging instead. */
.pinnote {
  font-size: var(--t-label); color: var(--ink-faint); letter-spacing: .02em;
  display: inline-flex; align-items: center; gap: 8px;
}
.pinnote button {
  position: relative; font: inherit; font-size: var(--t-label); color: var(--ink-dim); background: none;
  border: 1px solid var(--line); border-radius: var(--r-chip); padding: 3px 10px; cursor: pointer;
}
.pinnote button:hover { color: var(--ink); border-color: var(--line-strong); }
/* CX audit: a lone quiet pill (~19px tall) — grow the hit area only, via an
   invisible ::before. It sits with 8px+ real gaps to its siblings (`.pinnote`
   flex gap / `.daybar` flex gap), so a full symmetric 44px zone fits without
   overlapping anything. */
.pinnote button::before {
  content: ""; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
  width: var(--tap-min); height: var(--tap-min); border-radius: var(--r-input);
}
@media (hover: hover) and (pointer: fine) {
  .pinnote button:hover::before { background: color-mix(in srgb, currentColor 9%, transparent); }
}

/* person segmented control */
.seg { display: inline-flex; border: 1px solid var(--line); border-radius: var(--r-chip); overflow: hidden; }
.seg button {
  font: inherit; font-size: var(--t-label); letter-spacing: .04em; color: var(--ink-dim);
  background: none; border: none; cursor: pointer; padding: 0 var(--sp-4); min-height: var(--tap-min); min-width: 56px;
}
.seg button + button { border-left: 1px solid var(--line); }
.seg button.on { color: var(--frame); font-weight: 500; }
.seg button.on.a { background: var(--pA); }
.seg button.on.b { background: var(--pB); }
/* CX M7: a hard 50/50 split at this pill's small size read as a rendering
   glitch (the seam sat astride the label with no transition). A soft blend
   still shows "both lights" per DESIGN §4.5 but reads as an intentional
   gradient, and the text shadow keeps the label legible over either half. */
.seg button.on.both {
  background: linear-gradient(90deg, var(--pA) 0%, var(--pA) 38%, var(--pB) 62%, var(--pB) 100%);
  color: var(--frame); text-shadow: 0 1px 1px color-mix(in srgb, var(--ink) 25%, transparent);
}
@media (hover: hover) and (pointer: fine) {
  .seg button:not(.on):hover { background: color-mix(in srgb, currentColor 9%, transparent); }
}

/* ============================================================================
   ALL-DAY STRIP
   ========================================================================== */
.allday { display: flex; align-items: center; gap: var(--sp-3); padding: 0 4px 10px; min-height: 0; }
.allday.empty { display: none; }
/* CX nit: 52px was too narrow for "ALL DAY" at .14em letter-spacing, wrapping
   to a cramped two-line eyebrow at 1280px. Widen + nowrap. */
.allday .lbl { font-size: var(--t-eyebrow); text-transform: uppercase; letter-spacing: .14em; color: var(--ink-faint); flex: none; width: 72px; white-space: nowrap; }
.allday .items { display: flex; gap: var(--sp-2); flex-wrap: wrap; }

/* ============================================================================
   HOUR GRID  (column-major two-column; single col on narrow, DESIGN §3.3)
   Row count is set inline per-page as ceil(hourCount/2) — never a fixed repeat(8,...).
   ========================================================================== */
.grid {
  display: grid; grid-template-columns: 1fr 1fr;
  grid-auto-flow: column;
  gap: var(--sp-2) var(--sp-4);
}
@media (max-width: 820px) {
  .grid { grid-template-columns: 1fr !important; grid-template-rows: none !important; grid-auto-flow: row; }
}

.hourrow {
  position: relative; display: flex; align-items: stretch; gap: var(--sp-3);
  min-height: var(--row-min-h); background: var(--row-empty); border-radius: var(--r-card);
  padding: var(--sp-2) var(--sp-3) var(--sp-2) var(--sp-4);
  cursor: pointer; border: 1px solid transparent;
  user-select: none; /* CX M2: dragging a chip must not smear text selection over rows */
}
/* CX M2: during an active drag (dnd.js toggles this on <body>), suppress
   selection everywhere — belt-and-suspenders alongside the removeAllRanges()
   call at drag start, so a fast mouse drag can never paint a lingering
   selection anywhere on the page. */
body.dragging { user-select: none; }
.hourrow.filled { background: var(--row); }
.hourrow:hover { border-color: var(--line); }
.hourrow::before {
  content: ""; position: absolute; left: 4px; top: 8px; bottom: 8px; width: 3px;
  border-radius: 999px; background: transparent;
}
.hourrow.now { background: var(--now-wash); border-color: color-mix(in srgb, var(--now) 35%, transparent); }
.hourrow.now::before { background: var(--now); box-shadow: 0 0 10px -1px var(--now-glow); animation: breathe 4s var(--ease) infinite; }
@keyframes breathe { 0%,100% { opacity: .6; } 50% { opacity: 1; } }
.hourrow.dropA::before { background: var(--pA); width: 5px; box-shadow: 0 0 12px 0 var(--pA-glow); }
.hourrow.dropB::before { background: var(--pB); width: 5px; box-shadow: 0 0 12px 0 var(--pB-glow); }
.hourrow.dropA, .hourrow.dropB { background: var(--row); }

.hourrow .h {
  width: 52px; flex: none; align-self: center; font-size: var(--t-hour); font-weight: 300;
  color: var(--ink-dim); font-variant-numeric: tabular-nums; letter-spacing: .03em;
}
.hourrow.now .h { color: var(--now); font-weight: 400; }
.chips { display: flex; flex-wrap: wrap; gap: var(--sp-2); flex: 1; align-items: center; align-content: center; }
.hourrow .hint { color: var(--ink-faint); font-size: var(--t-date); font-weight: 300; line-height: 1; }

/* ============================================================================
   ACTIVITY CHIP
   ========================================================================== */
.chip {
  position: relative; display: flex; align-items: center; gap: var(--sp-2);
  background: var(--chip); border-radius: var(--r-chip);
  padding: var(--sp-2) var(--sp-2) var(--sp-2) var(--sp-3);
  font-size: var(--t-chip); line-height: 1.15; overflow: hidden; cursor: grab;
  border: 1px solid transparent; max-width: 100%;
  /* Touch DnD (SPEC §3.4): without this, a touch move is claimed by the
     browser for page panning before the drag threshold is reached, firing
     pointercancel and aborting every touch drag (tablets are the primary
     kiosk device). Chips are small; row-gutter scrolling is unaffected. */
  touch-action: none;
}
.chip:active { cursor: grabbing; }
.chip--a { --p: var(--pA); --p-tint: var(--pA-tint); --p-tint-hi: var(--pA-tint-hi); --p-glow: var(--pA-glow); }
.chip--b { --p: var(--pB); --p-tint: var(--pB-tint); --p-tint-hi: var(--pB-tint-hi); --p-glow: var(--pB-glow); }
.chip--a, .chip--b { background: var(--p-tint-hi); }
.chip::before {
  content: ""; position: absolute; left: 0; top: 4px; bottom: 4px; width: 2px;
  border-radius: 999px; background: var(--p, transparent);
}
.chip:not(.done)::before { box-shadow: 0 0 8px -1px var(--p-glow, transparent); }
.chip.done { background: var(--p-tint); }
.chip.done .t { color: var(--ink-dim); }

/* Owner feedback: the chip's toggle/remove controls read as too small both
   visually and to tap. `--chip-check-w/h` (tokens.css) were grown for the
   visible control; the hit area is grown further still via an invisible
   ::before/::after (CX audit: --tap-min effective target), positioned with
   an offset that only reaches into genuinely empty space — the row's own
   padding above/below/left, and the wide (~100px) gap to the chip's other
   control across the title text — never into a neighboring chip's zone
   (chips are gapped `--sp-2` apart in `.chips`, but `.box` only grows toward
   the chip's own left/top/bottom, away from any sibling chip to the right).
   `.box`/`.x` clicks are already excluded from drag-start in dnd.js
   (`e.target.closest('.box'/'.x')`), and a click landing in the expanded
   zone still reports `e.target` as the real button, so dragging the chip
   body itself is unaffected. */
.chip .box {
  position: relative; width: var(--chip-check-w); height: var(--chip-check-h); flex: none; border-radius: 999px;
  border: 1.5px solid var(--ink-dim); background: none; cursor: pointer;
  display: grid; place-items: center; color: transparent; font-size: 13px; padding: 0;
}
.chip .box::before {
  content: ""; position: absolute; top: 50%; left: 0; transform: translateY(-50%);
  width: var(--tap-min); height: var(--tap-min); border-radius: var(--r-input);
}
.chip.done .box { background: var(--p); border-color: var(--p); color: var(--frame); }
.chip .t { padding: 0 2px; }
.chip .x {
  position: relative; border: none; background: none; color: var(--ink-faint); cursor: pointer;
  font-size: 16px; padding: 4px 6px; line-height: 1; border-radius: 6px;
}
.chip .x::before {
  content: ""; position: absolute; top: 50%; right: 0; transform: translateY(-50%);
  width: var(--tap-min); height: var(--tap-min); border-radius: var(--r-input);
}
.chip .x:hover { color: var(--over); }
@media (hover: hover) and (pointer: fine) {
  .chip .box:hover::before,
  .chip .x:hover::before {
    background: color-mix(in srgb, currentColor 9%, transparent);
  }
}

/* ============================================================================
   CALENDAR OVERLAY BLOCK
   ========================================================================== */
.calblock {
  --p: var(--pA); position: relative; display: inline-flex; align-items: center; gap: 7px;
  border-radius: var(--r-input); background: var(--pA-tint); border-left: 2px solid var(--p);
  padding: var(--sp-2) var(--sp-3); font-size: var(--t-badge); color: var(--ink); cursor: default; max-width: 100%;
}
.calblock--a { --p: var(--pA); background: var(--pA-tint); }
.calblock--b { --p: var(--pB); background: var(--pB-tint); }
.calblock .ci { opacity: .6; font-size: 12px; }
.calblock .ct { color: var(--ink-dim); font-variant-numeric: tabular-nums; }
.calblock.busy { background-image: repeating-linear-gradient(45deg, transparent 0 6px, var(--busy-hatch) 6px 8px); }
.calblock.free { opacity: .82; }
/* CX M3: `.chips` never grows taller than its content, so `align-self:
   stretch` alone collapsed a continuation-only row to 0 height (nothing
   visible at all). Giving it an explicit min-height (matching the chip
   checkbox rail) makes the "one contiguous light column" DESIGN §4.4 asks
   for actually render on desktop and phone. */
.calcont {
  width: 4px; align-self: stretch; min-height: var(--chip-check-h); border-radius: 999px;
  background: var(--pA); opacity: .6; margin-left: 2px;
}
.calcont--b { background: var(--pB); }
.allday .calblock { border-radius: var(--r-chip); }

/* Read-only calendar-event popover (SPEC §3.5/DESIGN §4.4): title, time,
   location — no editing affordance, matches a calendar block's tap target. */
.calpopover {
  position: fixed; z-index: 40; left: 50%; top: 50%; transform: translate(-50%, -50%);
  background: var(--row); border: 1px solid var(--line); border-radius: var(--r-card);
  padding: var(--sp-3) var(--sp-4); min-width: 220px; max-width: 320px;
  box-shadow: 0 12px 32px rgba(0,0,0,.35);
}
.calpopover .cpTitle { font-size: var(--t-chip); color: var(--ink); margin-bottom: 4px; }
.calpopover .cpTime, .calpopover .cpLoc { font-size: var(--t-badge); color: var(--ink-dim); }
.calpopover .cpFeed { font-size: var(--t-eyebrow); text-transform: uppercase; letter-spacing: .1em; color: var(--ink-faint); margin-top: 6px; }

/* Ad-hoc day-type override affordance (SPEC stretch: ad-hoc day types): a
   small, unobtrusive icon next to the date label — only shown when the day is
   still lazy and today/future (materialized days can't be re-templated).
   Opens a compact popover of the person's day types plus a "Clear" action. */
.daybar .icon.dayTypeOverride { font-size: 11px; padding: 0 2px; }
.daybar .icon.dayTypeOverride.set { color: var(--now); }
.dtopopover {
  position: fixed; z-index: 40; left: 50%; top: 50%; transform: translate(-50%, -50%);
  background: var(--row); border: 1px solid var(--line); border-radius: var(--r-card);
  padding: var(--sp-3) var(--sp-4); min-width: 200px; max-width: 300px;
  box-shadow: 0 12px 32px rgba(0,0,0,.35);
}
.dtopopover .dtoTitle { font-size: var(--t-eyebrow); text-transform: uppercase; letter-spacing: .1em; color: var(--ink-faint); margin-bottom: 8px; }
.dtopopover .dtoList { display: flex; flex-direction: column; gap: 8px; }
.dtopopover button.dtoOpt {
  text-align: left; background: none; border: 1px solid var(--line); border-radius: var(--r-chip);
  padding: 6px 10px; min-height: var(--tap-min); font: inherit; font-size: var(--t-chip); color: var(--ink); cursor: pointer;
  display: flex; align-items: center;
}
.dtopopover button.dtoOpt:hover { border-color: var(--now); }
.dtopopover button.dtoOpt.current { border-color: var(--now); color: var(--now); }
.dtopopover .dtoClear { margin-top: 6px; color: var(--ink-dim); }

/* ============================================================================
   PICKER (bottom sheet on narrow layouts; anchored popover on wide, DESIGN §3.4)
   ========================================================================== */
.veil {
  position: fixed; inset: 0; background: var(--overlay);
  display: none; align-items: flex-end; justify-content: center; z-index: 20;
}
.veil.open { display: flex; }
.picker {
  width: min(560px, 96vw); max-height: 88%; overflow: hidden auto;
  background: var(--sheet); border: 1px solid var(--line); border-bottom: none;
  border-radius: var(--r-sheet) var(--r-sheet) 0 0; padding: 10px 16px 12px; box-shadow: var(--elev-sheet);
}
.grab { width: 36px; height: 4px; border-radius: 999px; background: var(--ink-faint); margin: 4px auto 12px; }

/* Anchored-popover mode (≥820px, matches the .grid two-column collapse
   breakpoint): the picker opens next to the clicked hour row instead of
   rising from the bottom. Owner feedback: "should appear next to the day's
   slot, not clamped to the bottom." The veil no longer dims the page — it
   just captures the outside-click-to-dismiss and Escape stays wired the
   same as the sheet. Position (left/top) is computed in app.js and applied
   as inline styles; this class only switches the presentation shell. */
.veil.popover {
  background: transparent; align-items: flex-start; justify-content: flex-start;
  pointer-events: none; /* let clicks reach the grid; only the popover + its own outside-click layer intercept */
}
.veil.popover .picker {
  pointer-events: auto;
  position: absolute; /* left/top set inline by app.js, clamped to viewport */
  width: min(400px, 92vw); max-height: min(70vh, 520px);
  border: 1px solid var(--line-strong); border-radius: var(--r-card);
  box-shadow: 0 16px 40px rgba(0,0,0,.45);
}
.veil.popover .picker .grab { display: none; }
/* Small arrow / clear visual connection to the row it's anchored to.
   app.js toggles .arrow-left / .arrow-right depending on which side of the
   row the popover landed on (prefers the adjacent column gap first). */
.veil.popover .picker::before {
  content: ""; position: absolute; top: var(--anchor-arrow-top, 24px);
  width: 12px; height: 12px; background: var(--sheet);
  border-left: 1px solid var(--line-strong); border-bottom: 1px solid var(--line-strong);
  transform: rotate(135deg); border-radius: 0 0 0 3px;
}
.veil.popover .picker.arrow-left::before { left: -7px; }
.veil.popover .picker.arrow-right::before { right: -7px; transform: rotate(-45deg); }
.picker .phead { display: flex; align-items: center; gap: var(--sp-3); }
.picker h2 {
  flex: 1; margin: 0; font-size: var(--t-label); font-weight: 400; letter-spacing: .08em;
  color: var(--ink-dim); text-transform: uppercase;
}
.picker h2 b { color: var(--now); font-weight: 500; }
.pswitch { display: inline-flex; gap: 10px; }
/* Two circular glyph buttons sit close together (by design, a compact A/B
   switch) — there isn't 44px of horizontal room for both without their hit
   zones colliding. Each grows to --tap-min vertically (free) and mostly
   OUTWARD horizontally (away from its sibling) via an asymmetric ::after
   hit zone, with only ~1px of inward growth — enough to reach 44px total
   width while the 10px visual gap keeps an 8px+ gap between the two
   effective hit rects (CX audit: no overlapping invisible zones). */
.pswitch button {
  position: relative; width: 30px; height: 30px; border-radius: 999px; border: 1.5px solid var(--line);
  background: none; cursor: pointer; font: inherit; font-size: 12px; color: var(--ink-dim);
}
.pswitch button::after {
  content: ""; position: absolute; top: 50%; transform: translateY(-50%); height: var(--tap-min);
  width: var(--tap-min); border-radius: var(--r-input);
}
.pswitch button:first-child::after { right: -1px; }
.pswitch button:last-child::after { left: -1px; }
.pswitch button.on.a { background: var(--pA); border-color: var(--pA); color: var(--frame); }
.pswitch button.on.b { background: var(--pB); border-color: var(--pB); color: var(--frame); }
@media (hover: hover) and (pointer: fine) {
  .pswitch button:not(.on):hover::after { background: color-mix(in srgb, currentColor 9%, transparent); }
}
.freetype { display: flex; gap: var(--sp-2); margin: 12px 0 6px; }
.freetype input {
  flex: 1; background: var(--row-empty); border: 1px solid var(--line); border-radius: var(--r-input);
  color: var(--ink); font: inherit; font-size: var(--t-body); padding: 12px; min-height: var(--tap-min);
}
.freetype input::placeholder { color: var(--ink-faint); }
.freetype button {
  background: var(--chip); color: var(--ink); border: 1px solid var(--line);
  border-radius: var(--r-input); font: inherit; font-size: var(--t-label); padding: 0 18px; cursor: pointer;
  min-height: var(--tap-min);
}
.plist { list-style: none; padding: 4px 0 8px; margin: 0; }
.plist .sep {
  font-size: var(--t-eyebrow); letter-spacing: .14em; text-transform: uppercase;
  color: var(--ink-faint); padding: 14px 4px 5px;
}
.pitem {
  display: flex; align-items: center; gap: var(--sp-3); width: 100%;
  background: none; border: none; border-top: 1px solid var(--line);
  color: var(--ink); font: inherit; font-size: var(--t-body); text-align: left;
  padding: 13px 6px; cursor: pointer; min-height: 48px;
}
.pitem:hover { background: color-mix(in srgb, var(--ink) 4%, transparent); }
.pitem .pt { width: 50px; flex: none; color: var(--ink-faint); font-size: var(--t-label); font-variant-numeric: tabular-nums; }
.pitem .nm { flex: 1; }
.badge { font-size: var(--t-badge); font-variant-numeric: tabular-nums; letter-spacing: .02em; flex: none; }
.badge.over { color: var(--over); }
.badge.due { color: var(--due); }
.badge.later { color: var(--ink-faint); }

/* Completion-streak circles (owner spec, stretch): a compact row of N dots,
   oldest -> newest left to right, one per cadence-window in the lookback.
   Slightly larger than the .savestate text dot (7px) so a streak reads at a
   glance; filled --ok = completed that window, border-only = empty. Used in
   both the picker (.pitem) and the config menu-items table. */
.streak { display: inline-flex; align-items: center; gap: 4px; flex: none; }
.streakdot {
  width: 9px; height: 9px; border-radius: 999px; flex: none;
  border: 1.5px solid var(--ink-faint); background: none;
}
.streakdot.on { border-color: var(--ok); background: var(--ok); }

/* ============================================================================
   UNDO TOAST (SPEC §11 stretch) — bottom of grid, one at a time, ~5s.
   ========================================================================== */
.undo-toast {
  position: fixed; left: 50%; bottom: var(--sp-6); transform: translate(-50%, 12px);
  display: flex; align-items: center; gap: var(--sp-4);
  background: var(--sheet); border: 1px solid var(--line); border-radius: var(--r-chip);
  box-shadow: var(--elev-sheet); padding: var(--sp-2) var(--sp-2) var(--sp-2) var(--sp-4);
  font-size: var(--t-body); color: var(--ink); z-index: 25;
  opacity: 0; pointer-events: none; transition: opacity var(--dur) var(--ease), transform var(--dur) var(--ease);
}
.undo-toast.show { opacity: 1; transform: translate(-50%, 0); pointer-events: auto; }
.undo-toast .undo-msg { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 60vw; }
.undo-toast .undo-btn {
  font: inherit; font-size: var(--t-label); font-weight: 500; letter-spacing: .04em;
  color: var(--now); background: none; border: none; cursor: pointer;
  padding: 8px 14px; min-height: var(--tap-min); border-radius: var(--r-chip); flex: none;
  display: inline-flex; align-items: center;
}
.undo-toast .undo-btn:hover { background: color-mix(in srgb, var(--now) 12%, transparent); }
@media (max-width: 400px) {
  .undo-toast { width: min(420px, 92vw); }
  .undo-toast .undo-msg { max-width: 100%; }
}

.ghost { transform: scale(1.04) rotate(-1deg); box-shadow: var(--elev-ghost); opacity: .96; }
.placeholder {
  border: 1px dashed var(--line-strong); border-radius: var(--r-chip);
  background: transparent; min-width: 130px;
}

/* ============================================================================
   LOGIN / SETUP
   ========================================================================== */
.authbody { display: flex; align-items: center; justify-content: center; min-height: 100vh; padding: 24px; }
.authwrap { display: flex; gap: var(--sp-6); flex-wrap: wrap; justify-content: center; }
.authcard {
  width: 360px; max-width: 100%; background: var(--frame); border: 1px solid var(--line);
  border-radius: var(--r-sheet); padding: var(--sp-6) var(--sp-5) var(--sp-5); text-align: center;
}
.authclock { font-size: 54px; font-weight: 200; color: var(--ink-dim); font-variant-numeric: tabular-nums; letter-spacing: .02em; margin-bottom: 6px; }
.authcard .wordmark { font-size: var(--t-h1); font-weight: 300; letter-spacing: .08em; color: var(--ink); margin-bottom: var(--sp-5); }
.authcard .helper { font-size: var(--t-badge); color: var(--ink-dim); line-height: 1.5; margin: -6px 0 var(--sp-4); }
.authfield { text-align: left; margin-bottom: var(--sp-3); }
.authfield input {
  width: 100%; background: var(--row-empty); border: 1px solid var(--line); border-radius: var(--r-input);
  color: var(--ink); font: inherit; font-size: var(--t-body); padding: 13px 14px;
}
.authfield input.bad { border-color: var(--over); }
.autherr { color: var(--over); font-size: var(--t-badge); text-align: left; margin: -4px 0 var(--sp-3); }
.authcard .btn { width: 100%; margin-top: 4px; }
.remember { display: flex; align-items: center; gap: 8px; font-size: var(--t-badge); color: var(--ink-dim); margin-top: var(--sp-3); justify-content: center; }
/* Past-day cool wash (DESIGN §6.5): a subtle step-down on ink, no "now"
   glow anywhere (the JS already omits the .now class on non-today views). */
.frame.past-day .hourrow .h { color: var(--ink-ghost); }
.frame.past-day .hourrow.filled { background: var(--surface); }

.btn {
  font: inherit; font-size: var(--t-body); letter-spacing: .04em; padding: 11px 26px; min-height: var(--tap-min);
  border-radius: 999px; cursor: pointer; background: var(--ink); color: var(--frame); border: none; font-weight: 500;
  display: inline-flex; align-items: center; justify-content: center;
}
.btn.ghost { background: none; color: var(--ink-dim); border: 1px solid var(--line); }

/* ============================================================================
   Responsive (DESIGN §7)
   ========================================================================== */
@media (max-width: 400px) {
  .daybar .clock, .daybar .date { font-size: 20px; }
}

/* ============================================================================
   CONFIG / SETTINGS (DESIGN §3.5)
   ========================================================================== */
.cfg { padding: 2px 4px; }

/* PWA install nudge (cx-review-2 parked #3): dismissible row on /config only.
   Reuses the .card surface + skin ink/line variables so it reads correctly in
   every skin; both buttons meet the --tap-min (44px) floor. */
#installNudgeSlot { padding: 2px 4px; }
#installNudgeSlot .install-nudge { display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap; }
.install-nudge-icon { width: 28px; height: 28px; flex: none; }
.install-nudge-copy { flex: 1 1 180px; color: var(--ink); font-size: var(--t-body); }
.install-nudge-btn { padding: 8px 22px; flex: none; }
.install-nudge-dismiss {
  font: inherit; font-size: var(--t-body); background: none; border: none; color: var(--ink-dim);
  cursor: pointer; min-width: var(--tap-min); min-height: var(--tap-min); border-radius: 999px;
  display: inline-flex; align-items: center; justify-content: center; flex: none;
  margin: -8px -10px -8px 0; /* visually tight to the card edge, full 44px hit area kept */
}
.install-nudge-dismiss:hover { color: var(--ink); background: var(--line); }

.card { background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-card); padding: var(--sp-4) var(--sp-5); margin-bottom: var(--sp-4); }
.card > h2 {
  font-size: var(--t-label); font-weight: 400; letter-spacing: .12em; text-transform: uppercase;
  color: var(--ink-dim); margin: 0 0 var(--sp-4);
}
.grid2 { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: var(--sp-3) var(--sp-4); }
/* CX M4: at a 390px phone viewport, two 200px-minmax columns plus card
   padding no longer fit and pushed the whole config page ~15px past the
   viewport width. Drop to a single column once there's no room for two. */
@media (max-width: 480px) {
  .grid2 { grid-template-columns: 1fr; }
}
.field { display: flex; flex-direction: column; gap: 5px; }
.field label { font-size: var(--t-eyebrow); text-transform: uppercase; letter-spacing: .1em; color: var(--ink-faint); }
.cfg input, .cfg select {
  background: var(--row-empty); border: 1px solid var(--line); border-radius: var(--r-input);
  color: var(--ink); font: inherit; font-size: var(--t-body); padding: 10px 12px; width: 100%;
}
/* CX P2 (config tap targets audit): the blanket `.cfg input { width:100% }`
   above also stretched `<input type=checkbox>` to fill its flex container
   (~238px on the Person 2 checkbox) — a checkbox has no visual use for that
   width (the native glyph stays small/left-aligned regardless), and it fights
   `.chkwrap`'s own centered hit-zone treatment below. Keep checkboxes at
   their natural intrinsic size; `.chkwrap::before` is what actually grows
   the tappable area now. */
.cfg input[type=checkbox] { width: auto; }
.cfg input:focus, .cfg select:focus { border-color: var(--line-strong); }
.cfg input.bad, .cfg select.bad { border-color: var(--over); }
.fielderr { color: var(--over); font-size: var(--t-badge); margin-top: -2px; }
.swatches { display: flex; gap: 10px; align-items: center; }
.swatch { width: 34px; height: 34px; border-radius: 999px; border: 2px solid var(--line-strong); cursor: pointer; padding: 0; }
.swatch.a { background: var(--pA); } .swatch.b { background: var(--pB); }
/* CX m8: a bare `<input type=color>` ignores width/border-radius on its
   internal swatch in Chromium/Firefox and renders a ~400px saturated
   rectangle — constrain the native control's own box so it previews as the
   same small round swatch as the rest of the UI (DESIGN §3.5). */
input[type=color].swatch {
  -webkit-appearance: none; appearance: none; background: none; overflow: hidden;
}
input[type=color].swatch::-webkit-color-swatch-wrapper { padding: 0; border-radius: 999px; }
input[type=color].swatch::-webkit-color-swatch { border: none; border-radius: 999px; }
input[type=color].swatch::-moz-color-swatch { border: none; border-radius: 999px; }
.ptabs { display: inline-flex; border: 1px solid var(--line); border-radius: var(--r-chip); overflow: hidden; margin-bottom: var(--sp-4); }
.ptabs button { font: inherit; font-size: var(--t-label); background: none; border: none; color: var(--ink-dim); padding: 8px 18px; min-height: var(--tap-min); cursor: pointer; }
.ptabs button + button { border-left: 1px solid var(--line); }
.ptabs button.on.a { background: var(--pA); color: var(--frame); }
.ptabs button.on.b { background: var(--pB); color: var(--frame); }
@media (hover: hover) and (pointer: fine) {
  .ptabs button:not(.on):hover { background: color-mix(in srgb, currentColor 9%, transparent); }
}
/* CX M4: wide tables (stats cadence table, config tables) scroll inside
   their own container rather than the page body panning sideways. */
/* CX P3: nothing hinted a `.tbl-scroll` container actually had more content
   off to the right — a user could miss whole columns without realizing it
   was scrollable. This is the standard scroll-linked background-gradient
   "edge fade": two gradient layers, one anchored to each end of the
   scrollable content (`background-attachment: local` scrolls them with the
   content) and one anchored to the viewport (`scroll` — stays put), each
   pair positioned so the fade is visible only while there's more content in
   that direction. Pure CSS — no scroll-position JS to maintain, and it
   naturally disappears once a table fits (nothing to scroll = nothing left
   uncovered by the local-attachment layer). Colors reference --surface (the
   `.card` background this sits on) so it reads correctly in every skin. */
.tbl-scroll {
  overflow-x: auto;
  background:
    linear-gradient(to right, var(--surface) 0%, transparent 24px) left / 24px 100% local no-repeat,
    linear-gradient(to left, var(--surface) 0%, transparent 24px) right / 24px 100% local no-repeat,
    linear-gradient(to right, color-mix(in srgb, var(--ink) 18%, transparent), transparent 24px) left / 24px 100% scroll no-repeat,
    linear-gradient(to left, color-mix(in srgb, var(--ink) 18%, transparent), transparent 24px) right / 24px 100% scroll no-repeat;
}
table.tbl { width: 100%; border-collapse: collapse; font-size: var(--t-body); }
.tbl th { text-align: left; font-size: var(--t-eyebrow); font-weight: 400; letter-spacing: .1em; text-transform: uppercase; color: var(--ink-faint); padding: 6px 10px; }
.tbl td { border-top: 1px solid var(--line); padding: 8px 10px; vertical-align: middle; }
/* CX m5: native time inputs render 12-hour "08:00 AM" in some locales/
   browsers; a 112px box clips it to "08:00 A". Widen enough for the full
   12-hour string cross-browser (the field stays visually compact — --t-body
   text, no layout regression at the table's minmax column width). */
.tbl input[type=number] { width: 72px; } .tbl input[type=time] { width: 150px; font-variant-numeric: tabular-nums; }
.tbl select { width: auto; min-width: 130px; }
/* Menu Items' new-row "id (auto)" box (config.js) — a fixed 150px width next
   to the name input on the wide table. Moved from an inline style to this
   class so `.tbl-stack` (below) can shrink it at phone width without
   fighting inline-style specificity. */
.idfield { width: 150px; }

/* ============================================================================
   CX P1 (config page, phone 375/360w): the Menu Items table (Activity/Every/
   Preferred/Category/Streak/Notes) didn't reflow at all — it just extended
   past the viewport, forcing 40px+ of silent horizontal page scroll with only
   the first two columns visible. Below 600px, `.tbl-stack` switches the table
   to a stacked-card layout: each `<tr>` becomes its own bordered block, each
   `<td>` becomes a labeled row (label from its `data-label` attribute, set by
   config.js) instead of a table cell. `<thead>`/header `<tr>` is hidden since
   its labels now render per-field instead. Opt-in via `.tbl-stack` (rather
   than every `.tbl`) so the already-narrow Categories/Calendar-feeds tables
   — which fit their columns fine at this width — are unaffected; only the
   wide Menu Items table (and any other table config.js opts in) reflows.
   ========================================================================== */
@media (max-width: 600px) {
  table.tbl.tbl-stack, table.tbl.tbl-stack tbody, table.tbl.tbl-stack tr, table.tbl.tbl-stack td {
    display: block; width: 100%;
  }
  table.tbl.tbl-stack > tr:first-child { display: none; } /* header row: labels move to each field instead */
  table.tbl.tbl-stack tr {
    border: 1px solid var(--line); border-radius: var(--r-card); padding: var(--sp-3);
    margin-bottom: var(--sp-3);
  }
  table.tbl.tbl-stack td {
    border-top: none; padding: 6px 0; display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3);
  }
  table.tbl.tbl-stack td[data-label]::before {
    content: attr(data-label); flex: none; font-size: var(--t-eyebrow); font-weight: 400; letter-spacing: .1em;
    text-transform: uppercase; color: var(--ink-faint); padding-right: var(--sp-3);
  }
  /* Fields grow to fill the remaining row width instead of their fixed
     table-column widths (which assumed neighboring columns). */
  table.tbl.tbl-stack td input, table.tbl.tbl-stack td select { flex: 1; width: auto; min-width: 0; }
  /* The cadence ("Every") field wraps its <input> in a plain nowrap <span>
     (app.css `.tbl input[type=number]{width:72px}` + a " d" suffix, config.js)
     — `flex:1` on the input above is a no-op there since the span, not the
     td, is its actual flex parent, so the input silently kept a fixed width
     wide enough to overflow the card at phone width. Make that span itself a
     flex row so the width/flex rules cascade onto the real input. */
  table.tbl.tbl-stack td > span { display: flex; align-items: center; gap: 4px; min-width: 0; flex: 1; }
  table.tbl.tbl-stack td > span input[type=number] { flex: 1; width: auto; min-width: 0; }
  table.tbl.tbl-stack td:empty { display: none; } /* e.g. an archived-toggle cell with no label needed */
  /* Same fixed-width-next-to-flex-input problem as the cadence field: the
     new-row Activity cell wraps name+id inputs in a plain <div> (config.js)
     — that div is td's only flex child (not the inputs directly), so it
     never shrank to the cell's actual remaining width (px content width
     127; label + gap + this div's natural ~225px content width added up to
     ~295px, overflowing the ~248px cell). Make the div itself a flex column
     that fills the available space, and let each input inside it size to
     100% of that (rather than the id field's fixed 150px). */
  table.tbl.tbl-stack td[data-label="Activity"] > div {
    display: flex; flex-direction: column; gap: 4px; flex: 1; min-width: 0;
  }
  table.tbl.tbl-stack td[data-label="Activity"] > div input { width: 100%; }
  /* CX P2 (config tap targets): the trailing, unlabeled action-button cell
     (Menu Items' archive/restore ⌫/⤴) had no `data-label` and was only as
     tall as its own content (~35px) — shorter than the button's own 44px
     invisible hit zone, so the zone's top/bottom overflow landed on the
     neighboring `<td>`/`<tr>` instead of the button. min-height fixes the
     vertical reach. Horizontally, right-aligning it flush with
     justify-content pushed the zone's outward (rightward) reach past the
     row's own padding into the card's edge / blank page background — no
     room left to expand into (verified via elementFromPoint + a visible
     marker screenshot). A small padding-right keeps the button shy of that
     edge so the full zone has real room on all sides. */
  table.tbl.tbl-stack td:not([data-label]) {
    min-height: var(--tap-min); justify-content: flex-end; padding-right: 10px;
  }
}
.jtoggle {
  font: inherit; font-size: var(--t-badge); border-radius: 999px; padding: 5px 13px; min-height: var(--tap-min); cursor: pointer;
  background: none; border: 1px solid var(--line); color: var(--ink-dim); letter-spacing: .03em;
  display: inline-flex; align-items: center;
}
.jtoggle.on { color: var(--ok); border-color: rgba(111,191,143,.4); }
@media (hover: hover) and (pointer: fine) {
  .jtoggle:hover { background: color-mix(in srgb, currentColor 9%, transparent); }
}
/* CX audit (tap-min): a bare glyph button with no padding was as small as
   ~16x18px in a table cell — too small to hit reliably and too visually
   heavy to just pad up to 44px in a dense settings table (DESIGN's "quiet"
   aesthetic). Grow the HIT AREA only, via an invisible ::before expanded to
   --tap-min vertically (table rows have slack to absorb this without
   changing row height) and mostly horizontally too — full 44px when a
   button is alone in its cell. Where two land in the same flex container
   (`.icongroup`: up/down reorder arrows, feed refresh+delete), each only
   grows OUTWARD (away from its sibling) via :first-child/:last-child, so
   the 10px real gap between them plus near-zero inward growth keeps an 8px+
   gap between the two effective hit rects (CX audit: no overlapping zones). */
.iconbtn {
  position: relative; background: none; border: none; color: var(--ink-faint); cursor: pointer;
  font-size: 16px; padding: 2px; border-radius: var(--r-input);
}
.iconbtn::before {
  content: ""; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
  width: var(--tap-min); height: var(--tap-min); border-radius: var(--r-input);
}
.iconbtn:hover { color: var(--over); }
.iconbtn:disabled { color: var(--ink-ghost); cursor: default; }
.icongroup { display: flex; align-items: center; gap: 10px; }
.icongroup .iconbtn::before { left: auto; right: auto; transform: translateY(-50%); }
.icongroup .iconbtn:first-child::before { right: -1px; }
.icongroup .iconbtn:last-child::before { left: -1px; }
/* CX P2 (config tap targets audit): `.handle`/`.icongroup` sit inside a
   `.tbl-scroll` container (`overflow-x: auto`) as the table's *first* column
   with no leftward scroll headroom — the first button's ::before hit zone
   used to reach ~31px past the button's own left edge to hit 44px total, but
   that reach landed outside the scroll container's clipped edge and was
   silently cut off (verified via elementFromPoint: those coordinates
   resolved to `.card`/the scroll container, never the button). Rather than
   fight the clip, give the cell itself enough real, un-clipped left padding
   for the zone to fit entirely inside the container — 24px covers the
   ~20-22px of extra reach still needed after the existing 10px cell padding. */
.handle { display: flex; align-items: center; gap: 10px; color: var(--ink-faint); cursor: grab; padding-right: 6px; }
/* `.tbl td { padding: 8px 10px }` (specificity 0,1,1) otherwise beats a bare
   `.handle` padding-left override (0,1,0) — qualify with `td` to win. */
td.handle { padding-left: 24px; }
.handle .iconbtn::before { left: auto; right: auto; transform: translateY(-50%); }
.handle .iconbtn:first-child::before { right: -1px; }
.handle .iconbtn:last-child::before { left: -1px; }
@media (hover: hover) and (pointer: fine) {
  .iconbtn:not(:disabled):hover::before { background: color-mix(in srgb, currentColor 9%, transparent); }
}
.tmpl-tabs { display: flex; gap: var(--sp-2); margin-bottom: 10px; flex-wrap: wrap; }
.tmpl-tabs button { font: inherit; font-size: var(--t-label); background: none; border: 1px solid var(--line); color: var(--ink-dim); border-radius: 999px; padding: 6px 14px; min-height: var(--tap-min); cursor: pointer; }
.tmpl-tabs button.on { color: var(--ink); border-color: var(--line-strong); }
@media (hover: hover) and (pointer: fine) {
  .tmpl-tabs button:not(.on):hover { background: color-mix(in srgb, currentColor 9%, transparent); }
}
.tmpl { display: grid; grid-template-columns: 1fr 1fr; gap: 4px var(--sp-4); }
@media (max-width: 820px) { .tmpl { grid-template-columns: 1fr; } }
.trow { display: flex; align-items: center; gap: 10px; padding: 6px 4px; border-top: 1px solid var(--line); min-height: 42px; flex-wrap: wrap; }
.trow .h { width: 52px; flex: none; color: var(--ink-dim); font-size: var(--t-body); font-variant-numeric: tabular-nums; }
.tentry { display: inline-flex; align-items: center; gap: 6px; background: var(--chip); border-radius: 999px; font-size: var(--t-label); padding: 5px 6px 5px 12px; }
.tentry input.ttitle { background: none; border: none; color: var(--ink); font: inherit; font-size: var(--t-label); padding: 0; width: 110px; }
.tentry select { background: none; border: none; color: var(--ink-dim); font: inherit; font-size: 12px; padding: 0; width: auto; }
/* The entry's remove (x) button sits at the pill's right edge, close to the
   next `.tentry` pill (`.trow` gap: 10px) — grow its hit zone mostly
   rightward/vertically, not leftward into the select control or the next
   pill's own zone (CX audit: 8px min gap between adjacent hit rects). */
.tentry .iconbtn::before { left: auto; transform: translate(0, -50%); right: -4px; width: 34px; }
.tadd { color: var(--ink-faint); background: none; border: 1px dashed var(--line); border-radius: 999px; font: inherit; font-size: var(--t-label); padding: 5px 12px; min-height: var(--tap-min); cursor: pointer; display: inline-flex; align-items: center; }
.savebar { display: flex; justify-content: flex-end; align-items: center; gap: var(--sp-4); padding-top: var(--sp-2); flex-wrap: wrap; }
.savechip { font-size: var(--t-label); }
.savechip.ok { color: var(--ok); } .savechip.err { color: var(--over); }
.feedstatus { font-size: var(--t-badge); color: var(--ink-dim); }
.feedstatus.err { color: var(--over); }

/* ============================================================================
   STATS / HISTORY (SPEC §11.3 stretch) — read-only, quiet, tabular-nums.
   Reuses .card/.tbl/.seg from CONFIG above rather than inventing new chrome.
   ========================================================================== */
.stats { padding: 2px 4px; }
.stats .rangepicker { display: inline-flex; border: 1px solid var(--line); border-radius: var(--r-chip); overflow: hidden; margin-bottom: var(--sp-4); }
.stats .rangepicker button { font: inherit; font-size: var(--t-label); background: none; border: none; color: var(--ink-dim); padding: 8px 18px; min-height: var(--tap-min); cursor: pointer; }
.stats .rangepicker button + button { border-left: 1px solid var(--line); }
.stats .rangepicker button.on { background: var(--chip); color: var(--ink); }
@media (hover: hover) and (pointer: fine) {
  .stats .rangepicker button:not(.on):hover { background: color-mix(in srgb, currentColor 9%, transparent); }
}
.stats .empty { color: var(--ink-faint); font-size: var(--t-body); padding: var(--sp-3) 0; }

.stat-tbl { width: 100%; min-width: 460px; border-collapse: collapse; font-size: var(--t-body); }
.stat-tbl th { text-align: left; font-size: var(--t-eyebrow); font-weight: 400; letter-spacing: .1em; text-transform: uppercase; color: var(--ink-faint); padding: 6px 10px; }
.stat-tbl td { border-top: 1px solid var(--line); padding: 8px 10px; vertical-align: middle; }
.stat-tbl td.num, .stat-tbl th.num { text-align: right; font-variant-numeric: tabular-nums; }
.stat-tbl .cat-eyebrow td { padding-top: var(--sp-3); font-size: var(--t-eyebrow); text-transform: uppercase; letter-spacing: .12em; color: var(--ink-faint); border-top: none; }
.stat-tbl .overdue-over { color: var(--over); }
.stat-tbl .overdue-due { color: var(--due); }
.stat-tbl .overdue-later { color: var(--ink-faint); }
.stat-tbl tr.archived { color: var(--ink-faint); font-style: italic; }
.stat-tbl tr.archived .overdue-over, .stat-tbl tr.archived .overdue-due { color: var(--ink-faint); }

.catbars { display: flex; flex-direction: column; gap: 10px; }
.catbar-row { display: flex; align-items: center; gap: var(--sp-3); }
.catbar-row .label { width: 140px; flex: none; font-size: var(--t-label); color: var(--ink-dim); }
.catbar-row .track { flex: 1; height: 10px; border-radius: 999px; background: var(--row-empty); overflow: hidden; }
.catbar-row .fill { height: 100%; border-radius: 999px; background: var(--pA); }
.catbar-row .count { width: 36px; flex: none; text-align: right; font-variant-numeric: tabular-nums; color: var(--ink-dim); font-size: var(--t-label); }

.heatmap { display: grid; grid-auto-flow: column; grid-template-rows: repeat(7, 14px); gap: 3px; overflow-x: auto; padding-bottom: 4px; }
.heatmap .cell { width: 14px; height: 14px; border-radius: 3px; background: var(--row-empty); }
.heatmap-legend { display: flex; align-items: center; gap: 6px; margin-top: var(--sp-2); font-size: var(--t-badge); color: var(--ink-faint); }
.heatmap-legend .cell { width: 11px; height: 11px; border-radius: 3px; }

/* ============================================================================
   WEEK VIEW (SPEC §11.4 stretch) — 7 quiet, dense columns (Mon..Sun). Reuses
   .chip/.calblock/.allday from the day grid above rather than inventing new
   activity/event chrome; only the column layout itself is new.
   ========================================================================== */
.weekgrid {
  display: grid; grid-template-columns: repeat(7, 1fr); gap: var(--sp-2);
  align-items: start;
}
@media (max-width: 820px) {
  .weekgrid { grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); }
}
@media (max-width: 500px) {
  .weekgrid { grid-template-columns: 1fr; }
}
.weekcol {
  background: var(--row-empty); border-radius: var(--r-card); border: 1px solid transparent;
  padding: var(--sp-2); min-height: 120px; display: flex; flex-direction: column; gap: var(--sp-2);
}
.weekcol.today { background: var(--now-wash); border-color: color-mix(in srgb, var(--now) 35%, transparent); }
.weekcol-head {
  display: flex; align-items: center; gap: 6px; text-decoration: none; color: inherit;
  padding: 2px 4px 6px; min-height: var(--tap-min); border-bottom: 1px solid var(--line); border-radius: var(--r-input);
}
.weekcol-head:hover { background: color-mix(in srgb, var(--ink) 4%, transparent); }
.weekcol-head .wk {
  font-size: var(--t-eyebrow); text-transform: uppercase; letter-spacing: .08em; color: var(--ink-faint);
}
.weekcol-head .dn { font-size: var(--t-h1); font-weight: 300; color: var(--ink); }
.weekcol.today .weekcol-head .wk, .weekcol.today .weekcol-head .dn { color: var(--now); }
.weekcol-events { display: flex; flex-direction: column; gap: 4px; }
.weekcol-events .calblock { font-size: var(--t-eyebrow); padding: 4px 8px; width: 100%; }
.weekcol-items { display: flex; flex-direction: column; gap: 5px; flex: 1; }
.weekcol-empty { color: var(--ink-faint); font-size: var(--t-badge); padding: 6px 4px; }

/* Compact chip variant for the week grid: no checkbox/remove controls (this
   view is read-only), just a done-state dot + title. */
.weekcol .chip {
  cursor: default; padding: 5px 10px; font-size: var(--t-badge); gap: 6px; width: 100%;
}
.weekcol .chip .box {
  width: 8px; height: 8px; border: none; background: var(--ink-faint); flex: none; padding: 0;
}
.weekcol .chip.done .box { background: var(--ok); }
.weekcol .chip .t { padding: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
