:root{--bg:#08090A;--bg2:#0B0C0D;--card:#141619;--line:#1E2126;--ink:#F4F4F5;--mute:#9EA1AD;--green:#00E37A;--red:#FF5C5C;--sleep:#2A2D33;--amber:#F0B23C;
--font-body:"Plus Jakarta Sans",system-ui,-apple-system,sans-serif;--font-display:"Space Grotesk",system-ui,sans-serif;--font-mono:"IBM Plex Mono",monospace;
--space-8:8px;--space-12:12px;--space-16:16px;--space-20:20px;--space-24:24px;--space-32:32px;--space-40:40px;
--radius-card:18px;--radius-input:16px;--radius-sheet:32px;--radius-pill:999px}
*{box-sizing:border-box}html{background:var(--bg);color:var(--ink);font:16px/1.4 var(--font-body)}
body{margin:0;padding:0 var(--space-16) var(--space-40)}
main{max-width:520px;margin-inline:auto}
.top{max-width:520px;margin-inline:auto;padding:var(--space-16) 0}.wordmark{height:60px}
.eyebrow{font-size:12px;letter-spacing:.14em;text-transform:uppercase;color:var(--mute)}
.big{font-family:var(--font-display);font-weight:600;font-size:44px;letter-spacing:-.03em}
.mono{font-family:var(--font-mono);font-size:13px}
.muted{color:var(--mute)}.drift{color:var(--red)}
.row{display:flex;justify-content:space-between;align-items:baseline}
.bar{height:6px;background:var(--line);border-radius:var(--radius-pill);margin-top:var(--space-8)}.bar span{display:block;height:100%;background:var(--green);border-radius:var(--radius-pill)}
.ringwrap{display:flex;justify-content:center;padding:var(--space-16) 0}.ringwrap svg{width:min(320px,80vw);height:auto}
.now{display:flex;justify-content:space-between;align-items:flex-start;gap:var(--space-12);padding:var(--space-12) 0;border-top:1px solid var(--line);border-bottom:1px solid var(--line)}
/* Idle NOW tile: nothing running yet, so instead of the header-left/
   actions-right row, the two ways to begin (Declare / Focus) sit side by
   side full-width under the header, each with one line of helper text.
   When the Declare sheet opens, its own column takes the full row
   (flex-wrap + :has) so the activity grid gets real width instead of
   cramming into half a column -- browsers without :has just show the
   grid narrower, degraded but working. Stacked outright on narrow
   phones. */
.now.idle{display:block}
.now-actions{display:flex;flex-wrap:wrap;gap:var(--space-12);margin-top:var(--space-12)}
.now-action{flex:1 1 40%;min-width:0}
.now-action:has(details[open]){flex-basis:100%}
.now-action .now-help{margin:var(--space-8) 0 0}
@media (max-width:520px){.now-action{flex-basis:100%}}
.dot{display:inline-block;width:8px;height:8px;border-radius:50%;background:var(--green);margin-inline-end:var(--space-8)}
.nowlabel{font-weight:700;font-size:22px}
.btn{background:var(--card);color:var(--ink);border:1px solid var(--line);border-radius:var(--radius-pill);padding:var(--space-12) var(--space-16);font:inherit;cursor:pointer;list-style:none}
.btn.primary{background:var(--green);color:var(--bg);border-color:var(--green);font-weight:700}
.btn.ghost{background:transparent}
.btn.linklike{background:transparent;border-color:transparent;text-decoration:underline;color:var(--mute);padding:var(--space-8) var(--space-16)}
/* Anchors styled as buttons (View timer, Focus, Start another, the note
   link...) must not keep the browser's default underline -- .linklike
   above is the one deliberate exception and restores its own. */
a.btn{text-decoration:none;color:var(--ink)}
a.link-mute{color:var(--mute);text-decoration:none}
a.link-mute:hover,a.link-mute:focus-visible{text-decoration:underline}
/* The "Today accounted for" block (label/percentage/bar) is a single link
   to Sweep -- same "plain-looking text that's actually a link" convention
   as .link-mute just below it on this page, not new button chrome: no
   colour/underline of its own (color:inherit leaves .eyebrow/.big/.drift's
   real colours untouched), only a subtle hover/focus affordance on the
   label, mirroring .link-mute's own hover/focus underline reveal. block
   display so the label/row/bar stack exactly as they did as bare section
   children. */
a.coverage-link{display:block;color:inherit;text-decoration:none}
a.coverage-link:hover .eyebrow,a.coverage-link:focus-visible .eyebrow{text-decoration:underline}
summary{list-style:none}summary::-webkit-details-marker{display:none}summary::marker{display:none}
.switch>summary::after,.splitgap>summary::after,.splitblock>summary::after,.fillparts>summary::after,.confirm>summary::after,.reclassify>summary::after{content:'+';margin-inline-start:var(--space-8);font-weight:700;display:inline-block}
.switch[open]>summary::after,.splitgap[open]>summary::after,.splitblock[open]>summary::after,.fillparts[open]>summary::after,.confirm[open]>summary::after,.reclassify[open]>summary::after{content:'\2715'}
.switch{position:relative}
/* Once open, the sheet has its own header+close (see _sheet_header.html) --
   the trigger that opened it is no longer needed for anything and used to
   sit awkwardly inside the fixed-position sheet, visually overlapping real
   tile options. visibility (not display) so .actions' flex layout doesn't
   reflow when it disappears. */
.switch[open]>summary{visibility:hidden}
.switch:not([open]) .grid{display:none}
.switch[open] .grid{position:fixed;left:0;right:0;bottom:0;top:auto;width:100%;max-width:520px;margin-inline:auto;margin-top:0;z-index:2;
  background:var(--bg2);padding:var(--space-24) var(--space-16) var(--space-32);border-radius:var(--radius-sheet) var(--radius-sheet) 0 0;
  border:1px solid var(--line);border-bottom:none;box-shadow:0 -8px 32px rgba(0,0,0,.5);max-height:70vh;overflow-y:auto}
.switch[open] .grid::before{content:'';position:absolute;top:10px;left:50%;transform:translateX(-50%);width:36px;height:4px;border-radius:var(--radius-pill);background:var(--line)}
/* Reusable sheet header (see _sheet_header.html): a label plus an explicit
   close control fixed to the sheet's own top-right corner, independent of
   wherever the trigger that opened it happens to sit -- the trigger used
   to double as the only way to dismiss, which put it awkwardly inside the
   opened grid alongside real options. */
.sheet-header{position:relative;display:flex;align-items:center;min-height:24px;margin-bottom:var(--space-16)}
.grid>.sheet-header{grid-column:1/-1}
.sheet-close{position:absolute;top:-8px;right:-8px;background:transparent;border:none;color:var(--mute);font-size:22px;line-height:1;cursor:pointer;padding:var(--space-8)}
.grid{display:grid;grid-template-columns:repeat(4,1fr);gap:var(--space-8);margin-top:var(--space-12)}
.tile{border-radius:var(--radius-card);padding:var(--space-16) var(--space-8);font:inherit;font-size:13px;cursor:pointer;background:#0F1A14;color:var(--green);border:1px solid #1E4D35}
.tile.drift{background:#1A1012;color:var(--red);border-color:#4D2121}
.timeline{margin-top:var(--space-16)}.slot{display:flex;gap:var(--space-12);margin-bottom:var(--space-8)}.slot .t{color:var(--mute);width:44px;padding-top:var(--space-16);flex:none}
.card{flex:1;border-radius:var(--radius-card);padding:var(--space-16);border:1px solid var(--line);background:var(--card);position:relative}
/* Proportional duration bar (annotate_bar_widths() in services.py sets the
   width): lives inside <summary>, not as a direct <details> child, since a
   closed (collapsed) <details> hides every child except its own <summary>
   -- this is meant to be visible on the normal, collapsed card. Positioned
   absolute against .card itself (not <summary>), so it ignores <summary>'s
   own padding and sits flush along the card's real bottom edge; width is
   proportional (not left+right:0), scale is one shared "biggest block of
   the day" baseline across every card on the page, see that function's own
   docstring. transition:width lets the open block's own bar grow smoothly
   as _today_live_script.html's poll updates it, the same live-minutes span
   already does for the text next to it. */
/* Fixed-scale duration tally (Settings' "Duration bars" switch;
   services.annotate_segment_tally()): one filled segment per 15 minutes,
   the same absolute scale on every card every day -- replacing the old
   day-relative proportional bar and its ruler ticks entirely. Only
   filled segments are ever drawn (no empty cells, so nothing reads as
   "remaining"); a partial final segment is proportionally narrower via
   --f set inline by the template. Rows wrap at 24 segments (6 hours,
   services.SEGMENTS_PER_ROW); within a row, every 4th segment gets a
   wider trailing gap so segments group into hours at a glance. 8px
   segments keep a full 24-segment row at ~253px -- comfortably inside a
   phone card's ~329px content width. */
.seg-tally{display:block;margin-top:var(--space-8)}
.seg-row{display:flex;gap:2px;margin-top:2px}
.seg{--f:1;flex:none;width:calc(8px*var(--f));height:4px;border-radius:1px;background:var(--mute);opacity:.75}
.seg:nth-child(4n){margin-right:3px}
.card.chosen .seg{background:var(--green)}
.card.drift .seg{background:var(--red)}
.seg-legend{margin:0 0 var(--space-8)}
/* Ring info sheets (Period view's .period-info, Phase 3; Detail view's
   .entry-info/.start-info popovers, Phase 4): no visible trigger of their
   own (the ring itself is the trigger, see today.html's inline scripts) --
   closed is fully invisible, not just collapsed, since there's nothing to
   show until a tap opens one. Reuses .card's own look once open rather
   than new styling. */
.period-info:not([open]),.entry-info:not([open]),.start-info:not([open]){display:none}
.period-info[open],.entry-info[open],.start-info[open]{border-radius:var(--radius-card);padding:var(--space-16);border:1px solid var(--line);background:var(--card);margin-top:var(--space-12)}
/* Ring selection (Phase 5): the ring's own visual echo of whichever info
   sheet/popover is open (see today.html's toggle listeners) -- a spatial/
   opacity change only, never a fill-colour change, since fill colour is
   load-bearing (the chosen/drift intent ramp) and recolouring it for
   selection would risk misreading as a different ratio than reality.
   [data-period]/[data-entry] pop outward via a per-element --dx/--dy
   (embedded by ring.py from that element's own real angle -- a generic
   CSS rule can't know which direction "outward" is on its own).
   [data-start] (a tick, not a wedge) instead scales longer, anchored at
   its own inner point (transform-origin, also embedded by ring.py) rather
   than the default bounding-box centre, so it grows outward away from the
   ring instead of also eating into the ring band -- scaling doubles as
   "thicker" too, since CSS transform scales the whole rendered stroke. */
[data-period],[data-entry],[data-start]{transition:opacity .15s ease,transform .15s ease}
[data-period].dimmed,[data-entry].dimmed,[data-start].dimmed{opacity:.3}
[data-period].selected,[data-entry].selected{transform:translate(var(--dx,0px),var(--dy,0px))}
[data-start].selected{transform:scale(1.4)}
.card summary{cursor:pointer}.card b{display:block;font-weight:700}.card small{color:var(--mute)}
.card.chosen{background:#0C1A12;border-color:#1E4D35}.card.chosen b{color:var(--green)}
.card.drift{background:#1C0F10;border-color:#4D2121}.card.drift b{color:var(--red)}
.card.sleep{background:var(--card);color:var(--mute)}
.card.verified{border-width:2px}
.card.gap{background:#1A1C20;border:1px dashed var(--line)}.card.gap b{color:var(--mute)}   /* #1A1C20 = ring.py's TRACK, the ring's own unlogged-gray -- was sharing .card.drift's red before, indistinguishable from a real drifted card */
/* Likely-duplicate marker + its one-tap removal, rendered inside the
   collapsed card's own <summary> (see _timeline_card.html). Amber, the
   app's established attention-not-intent colour (row1-badge, long-open):
   a flagged card isn't being judged chosen or drifted, just surfaced. */
.dup-flag{display:block;color:var(--amber);font-size:12px;margin-top:var(--space-8)}
summary .dup-remove{display:inline-block;margin-top:var(--space-8)}
.dup-remove-btn{background:#2A1F0A;color:var(--amber);border-color:#4D3B14}
/* flex-wrap:wrap, not nowrap: a timeline card's own actions row (toggle
   intent, note, remove, and conditionally a Merge button) can genuinely
   need more than one phone-width's worth of buttons -- found overflowing
   the card (and inflating the whole page's scrollWidth) during the same
   phone-width pass that found the nav bug above. Wrapping onto a second
   line is the natural fix for a block-level card that can simply grow
   taller, unlike nav.tabs's own pills, which need to stay a single row. */
.actions{display:flex;gap:var(--space-8);margin-top:var(--space-12);flex-wrap:wrap}
.login{padding:var(--space-40) 0}.login h1{font-weight:700;font-size:32px;letter-spacing:-.03em}
.login input{width:100%;padding:var(--space-12);border-radius:var(--radius-input);border:1px solid var(--line);background:var(--card);color:var(--ink);font:inherit;margin:var(--space-12) 0}
:focus-visible{outline:2px solid var(--green);outline-offset:2px}
@media (prefers-reduced-motion:no-preference){.bar span{transition:width .4s}}
.h1{font-weight:700;font-size:30px;letter-spacing:-.03em;margin:var(--space-8) 0 var(--space-8)}
.h2{display:block;font-weight:700;font-size:22px;margin:var(--space-8) 0 var(--space-8)}
.green{color:var(--green)}.small{font-size:13px}.wide{width:100%;margin-top:var(--space-12);padding:var(--space-16);border-radius:var(--radius-pill)}
.tile.sel{position:relative;display:flex;flex-direction:column;align-items:center;gap:var(--space-8);background:var(--card);color:var(--mute);border-color:var(--line)}
.tile.sel input{position:absolute;opacity:0;inset:0}.tile.sel:has(input:checked){background:#0F1A14;color:var(--green);border-color:var(--green)}
.tile.sel b{font-family:var(--font-display);font-size:22px;color:var(--ink)}.tile.sel:has(input:checked) b{color:var(--green)}
/* Intent-aware selectable tiles (split's segment picker): the base .tile
   intent color applies whether or not selected -- selection highlights
   with a bolder border in that SAME color, never an override to green
   regardless of intent. Plain .tile.sel above (duration picker, focus's
   activity picker) has no intent to preserve and keeps its old behavior. */
.tile.chosen.sel{background:#0F1A14;color:var(--green);border-color:#1E4D35}
.tile.drift.sel{background:#1A1012;color:var(--red);border-color:#4D2121}
.tile.chosen.sel:has(input:checked){border-color:var(--green);border-width:2px}
.tile.drift.sel:has(input:checked){border-color:var(--red);border-width:2px}
.insight{background:#0C1A12;border:1px solid #1E4D35;border-radius:var(--radius-card);padding:var(--space-16);margin:var(--space-16) 0}
.center{text-align:center}.pill{display:inline-block;padding:var(--space-8) var(--space-20);border-radius:var(--radius-pill);background:#0F1A14;color:var(--green);border:1px solid #1E4D35;font-weight:700}
.timer{position:relative;display:inline-block;margin:var(--space-24) 0 var(--space-8)}.timerlabel{position:absolute;inset:0;display:flex;flex-direction:column;justify-content:center}
.actions.center{justify-content:center}.btn.danger{background:#1C0F10;color:var(--red);border-color:#4D2121}
.stack{display:flex;flex-direction:column;gap:var(--space-12)}
.row-btn{text-align:start;padding:var(--space-16);padding-inline-end:var(--space-32);border-radius:var(--radius-card);position:relative}
.row-btn::after{content:'\203A';position:absolute;inset-inline-end:var(--space-16);top:50%;transform:translateY(-50%);color:var(--mute);font-size:20px}
.gapcard{margin-bottom:var(--space-12)}.gapcard b{font-size:18px}.chosen-ink{color:var(--green)}.drift-ink{color:var(--red)}
.in{flex:1;padding:var(--space-12);border-radius:var(--radius-input);border:1px solid var(--line);background:var(--bg2);color:var(--ink);font:inherit}
textarea.in{width:100%;min-height:120px;resize:vertical;display:block;margin-top:var(--space-12)}
.chips{display:flex;gap:var(--space-8);flex-wrap:wrap;margin-top:var(--space-12)}
.chip{position:relative;display:inline-flex;cursor:pointer}
.chip input{position:absolute;opacity:0;inset:0;margin:0}
.chip span{padding:var(--space-8) var(--space-16);border-radius:var(--radius-pill);background:var(--card);color:var(--mute);border:1px solid var(--line)}
.chip:has(input:checked) span{background:#0F1A14;color:var(--green);border-color:var(--green)}
.actions.split>*{flex:1;text-align:center}
.cutrow{display:flex;gap:var(--space-8);align-items:center}
.cutrow .remove-cut{flex:none;padding:var(--space-8) var(--space-12);font-size:18px;line-height:1}
.unaccounted-toggle{grid-column:1/-1;display:flex;align-items:center;gap:var(--space-8);color:var(--mute);font-size:13px}
.split-segment.unaccounted .tile{opacity:.35;pointer-events:none}
.notif{background:#1A1C20;border-radius:22px;padding:var(--space-16);margin-top:var(--space-24)}.grid.three{grid-template-columns:repeat(3,1fr)}
.mono-num{font-variant-numeric:tabular-nums}
/* Never wraps, even if the row runs out of room: real phone-width testing
   found seven pills overflowing the viewport, "Log out" wrapping its own
   text onto two lines, and (align-items' own default of stretch) every
   other pill stretching to match that one's now-taller box with its text
   stuck at the top -- the exact same mechanism the desktop nav-width bug
   documented in CLAUDE.md already fixed once, just never hit on mobile
   before now. flex-shrink:0 keeps each pill at its own natural size
   (never squeezed, which is what let text wrap in the first place);
   overflow-x:auto on the row itself means any remaining overflow scrolls
   sideways *inside* nav.tabs's own box, instead of inflating the whole
   page's scrollWidth the way an unconstrained overflow does. */
/* Padding/gap/font-size here are deliberately smaller than this file's own
   8px spacing scale and body font-size -- tuned by measuring the five
   items' real natural content width against real iPhone viewport widths
   (checked at 320/375/390/393/430px) after a real-device report that the
   overflow-x:auto fallback below was still needed even at 100% text zoom
   (a new user's default, no zoom adjustment) on a 393px phone: the
   previous padding/gap/font-size needed 499px for five items against
   as little as 343px of real available width, so the Settings pill was
   partially cut off rather than cleanly scrollable. These values fit all
   five comfortably (with real margin, not flush against the edge) from
   375px up; overflow-x:auto stays as the fallback for anything narrower
   (e.g. a 320px first-generation SE) or any future sixth item, matching
   .filterrow a's own existing 13px precedent rather than inventing a new
   font-size for pills. */
nav.tabs{display:flex;gap:4px;margin-top:var(--space-8);flex-wrap:nowrap;overflow-x:auto}
nav.tabs a{color:var(--mute);text-decoration:none;padding:7px 8px;font-size:13px;border-radius:var(--radius-pill);border:1px solid var(--ink);white-space:nowrap;flex:0 0 auto}
nav.tabs a.on{color:var(--bg);background:var(--ink);font-weight:700}
.filterrow{display:flex;gap:var(--space-8);flex-wrap:wrap;margin:var(--space-16) 0}
.filterrow a{padding:var(--space-8) var(--space-16);border-radius:var(--radius-pill);background:var(--card);color:var(--mute);border:1px solid var(--line);text-decoration:none;font-size:13px}
.filterrow a.on{background:var(--ink);color:var(--bg);font-weight:700;border-color:var(--ink)}
.journal-day{display:flex;align-items:center;gap:var(--space-12);margin:var(--space-24) 0 var(--space-12)}
.journal-ring{flex:none;width:42px;height:42px}.journal-ring svg{width:42px;height:42px}
.journal-thread-note{margin-top:var(--space-12);padding-top:var(--space-12);border-top:1px solid var(--line)}
.history-cards{display:grid;gap:var(--space-16);margin-top:var(--space-20);justify-items:center}
.history-card{display:flex;flex-direction:column;align-items:center;gap:var(--space-8);text-decoration:none;color:var(--ink);position:relative}
.history-ring{display:block;position:relative}.history-ring svg{width:100%;height:100%}
.history-date{font-size:12px;color:var(--mute)}
.history-notedot{display:flex;align-items:center;gap:4px;font-size:11px;color:var(--mute);position:absolute;top:-2px;right:-2px}
.history-notedot .dot{width:6px;height:6px;margin-inline-end:0;background:var(--green)}
.history-chipgrid{display:grid;grid-auto-flow:column;grid-template-rows:repeat(7,1fr);gap:3px;overflow-x:auto;padding:var(--space-16) 0;margin-top:var(--space-8)}
.history-chip{width:12px;height:12px;border-radius:3px;display:block;position:relative}
.history-chip.empty{background:transparent}
.chip-dot{position:absolute;top:-2px;right:-2px;width:5px;height:5px;border-radius:50%;background:var(--ink)}
.day-strip{display:flex;gap:var(--space-8);flex-wrap:wrap;margin:var(--space-16) 0}
.day-chip{position:relative;padding:var(--space-8) var(--space-16);border-radius:var(--radius-pill);background:var(--card);color:var(--mute);border:1px solid var(--line);font:inherit;cursor:pointer}
.day-chip.on{background:var(--ink);color:var(--bg);font-weight:700;border-color:var(--ink)}
.day-chip.dirty::after{content:'';position:absolute;top:-2px;right:-2px;width:6px;height:6px;border-radius:50%;background:var(--amber)}
.day-editor{margin-bottom:var(--space-16)}
.copy-target.copied{background:#0F1A14;color:var(--green);border-color:var(--green)}

.period-row{display:flex;flex-direction:column;gap:var(--space-8)}
.period-row .actions{margin-top:0}
.period-row .row1-badge{display:none}
/* Row 1's resolver defines where the whole day starts (day_boundaries()
   treats parts[0] specially) -- this already caused one real, shipped bug
   (4 Part Day's original ordering), so whichever row is physically first
   gets a persistent, hard-to-miss badge and border, kept in sync by JS on
   every add/remove/reorder, not a one-time instructions paragraph. */
.period-row.is-first{border-color:var(--amber);border-width:2px}
.period-row.is-first .row1-badge{display:inline-block;width:max-content}
.pill.row1-badge{background:#2A1F0A;color:var(--amber);border-color:#4D3B14;font-size:12px;padding:var(--space-8) var(--space-12)}
.field-clock,.field-solar,.field-prayer{display:flex;gap:var(--space-8)}
/* An open block past a flat 3-hour threshold (see today() /
   LONG_OPEN_THRESHOLD_MIN) -- amber like row1-badge above (attention, not
   the red/green chosen-vs-drift intent colors, since this doesn't know or
   claim which one applies), but block-level and wider since this needs to
   be genuinely hard to miss, not a small inline tag. */
.pill.long-open{display:block;width:100%;text-align:center;box-sizing:border-box;background:#2A1F0A;color:var(--amber);border-color:#4D3B14;font-size:13px;margin-top:var(--space-12)}

@media (min-width:768px){
  main,.top{max-width:680px}
  body{padding-inline:var(--space-24)}
  .login{max-width:400px;margin-inline:auto}
}
@media (min-width:1200px){
  body{padding:var(--space-32)}
  .top{max-width:1200px}
  body.dashboard main{max-width:1200px;display:grid;grid-template-columns:430px 1fr;gap:var(--space-20);align-items:start}
}
