:root {
  color-scheme: light dark;
  --bg: #f5f7f7;
  --surface: #ffffff;
  --ink: #10201f;
  --muted: #5b6b6a;
  --line: #dde5e4;
  --deep: #0f3b3a;      /* deep teal - the one bold note */
  --deep-ink: #ffffff;
  --amber: #b5720b;     /* meds accent, used sparingly later */
  --focus: #0f3b3a;
  --radius: 14px;
  box-sizing: border-box;
  padding-top: env(safe-area-inset-top, 0px);
  padding-bottom: env(safe-area-inset-bottom, 0px);
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #0c1413;
    --surface: #121d1c;
    --ink: #e7efee;
    --muted: #9fb0ae;
    --line: #223231;
    --deep: #57b8b3;
    --deep-ink: #06110f;
    --focus: #57b8b3;
  }
}

*, *::before, *::after { box-sizing: inherit; }

html, body { height: 100%; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font: 400 17px/1.55 ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
  -webkit-font-smoothing: antialiased;
}

/* A `display:` rule on an element beats the [hidden] attribute's display:none
   from the browser's default stylesheet. #gate sets display:grid, so hiding it
   did nothing and the construction splash stayed on screen on top of the app.
   Make the attribute win everywhere. */
[hidden] { display: none !important; }

/* ---- construction gate ---- */
#gate {
  min-height: 100%;
  display: grid;
  place-items: center;
  padding: 24px;
}

.gate-card {
  width: 100%;
  max-width: 30rem;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 32px;
  text-align: left;
}

.gate-card h1 {
  font-size: clamp(1.6rem, 5vw, 2.1rem);
  line-height: 1.1;
  margin: 14px 0 8px;
  letter-spacing: -0.01em;
}

.lede { font-size: 1.05rem; margin: 0 0 14px; max-width: 34ch; }
.muted { color: var(--muted); }
.small { font-size: 0.86rem; }

.badge {
  display: inline-block;
  font: 600 0.72rem/1 ui-monospace, SFMono-Regular, Menlo, monospace;
  color: var(--deep-ink);
  background: var(--deep);
  border-radius: 999px;
  padding: 5px 9px;
  vertical-align: middle;
}

hr { border: none; border-top: 1px solid var(--line); margin: 22px 0 16px; }

.tester label { display: block; margin-bottom: 4px; color: var(--muted); }

input[type="email"] {
  width: 100%;
  padding: 11px 12px;
  font-size: 1rem;
  color: var(--ink);
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: 10px;
  margin-bottom: 10px;
}

button {
  font: inherit;
  cursor: pointer;
}

.tester button, .app-bar + .app-body button {
  width: 100%;
  padding: 11px 14px;
  color: var(--deep-ink);
  background: var(--deep);
  border: 1px solid var(--deep);
  border-radius: 10px;
  font-weight: 600;
}

.linkish {
  background: none;
  border: none;
  color: var(--deep);
  padding: 10px 0 0;
  text-decoration: underline;
  text-underline-offset: 3px;
}

:focus-visible {
  outline: 3px solid var(--focus);
  outline-offset: 2px;
  border-radius: 6px;
}

/* ---- app shell ---- */
.app-bar {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 14px 18px;
  border-bottom: 1px solid var(--line);
  background: var(--surface);
}
.app-bar strong { font-size: 1.05rem; }
.app-bar .linkish { padding: 0; }
.app-body { padding: 22px 18px; max-width: 40rem; }

@media (prefers-reduced-motion: reduce) {
  * { animation: none !important; transition: none !important; }
}

/* ===== timeline screen (v0.2.0) ===================================== */

#app { display: flex; flex-direction: column; height: 100%; }

.patient { flex: 1; min-width: 0; }
.patient-btn {
  background: none; border: 0; padding: 0; text-align: left; cursor: pointer;
  display: flex; flex-direction: column; gap: 2px; color: inherit; font: inherit;
}
.patient-btn:hover strong { text-decoration: underline; }

.day-nav {
  display: flex; align-items: center; justify-content: center; gap: 14px;
  padding: 10px 14px 6px; font-weight: 600;
}
.day-nav button {
  background: none; border: 0; cursor: pointer; font-size: 18px;
  color: var(--muted); padding: 4px 10px; border-radius: 8px;
}
.day-nav button:hover { background: rgba(0, 0, 0, 0.06); color: inherit; }

/* The scrolling viewport. */
.timeline {
  position: relative; flex: 1; overflow-y: auto; overscroll-behavior: contain;
  border-top: 1px solid var(--line); border-bottom: 1px solid var(--line);
  background: var(--card, #fff);
}
.timeline-inner { position: relative; }

/* The spine: the centre line the sketch is built around. */
.spine {
  position: absolute; top: 0; bottom: 0; left: 50%; width: 2px;
  margin-left: -1px; background: var(--line);
}

.hours { position: absolute; inset: 0; }
/* z-index 3 so the HOUR LABEL INSIDE IT stays readable at every zoom.
   The label carries `z-index: 4` of its own, which at rest lifts it clear of
   the spine and the minute marks - both of which live OUTSIDE #ribbon and paint
   later. Zooming counter-scales this rule (see the end of this file), and a
   transform makes an element a STACKING CONTEXT, which traps the label's 4
   inside it: the spine then drew straight through "3 AM" at every zoom level
   but 1:1. A z-index here is what the label's own escape needs, so the pair
   travels as one.
   3, not 4: the day-break's black midnight bar is 4 and sits at the same y as
   the hour-0 rule, and a 1px grey line must not paint over it. Still under the
   chips (10) - if a dose lands on the hour, the dose is the thing you need. */
.hour {
  position: absolute; left: 0; right: 0; height: 0;
  border-top: 1px solid var(--line); z-index: 3;
}
/* Labels live in their own gutter at the far left. Putting them beside the
   spine meant a dose chip sat on top of the time it was due at. */
/* Steve on v0.2.0: "time numbers should be 2x bigger". These are read at a
   glance while holding a glass of water, not studied. */
.hour-label {
  position: absolute; left: 0; top: -13px; width: 88px; text-align: right;
  font-size: 22px; font-weight: 600; line-height: 1.1; white-space: nowrap;
  color: var(--muted);
}

/* The dashed "you are here" line. */
.now-line {
  position: absolute; left: 0; right: 0; height: 0;
  border-top: 2px dashed #c0392b; z-index: 500; pointer-events: none;
}
.now-line span {
  position: absolute; right: 6px; top: -9px; font-size: 11px; font-weight: 700;
  color: #c0392b; background: var(--card, #fff); padding: 0 4px; letter-spacing: 0.04em;
}

/* Lanes. Meds right of the spine, activities left - as drawn. */
.lane { position: absolute; top: 0; bottom: 0; }
.lane-right { left: 50%; right: 0; }
.lane-left { left: 96px; right: 50%; }

/* Showing one lane on its own gives it the whole width, so several doses due
   at the same time can chain across instead of scrolling sideways. The split
   only costs width when both lanes are actually on screen. */
.timeline-inner[data-lane='med'] .lane-right { left: 96px; }
.timeline-inner[data-lane='activity'] .lane-left { right: 0; }
.timeline-inner[data-lane='med'] .spine,
.timeline-inner[data-lane='activity'] .spine { opacity: 0.45; }

.slot-row {
  position: absolute; left: 0; right: 0; display: flex; gap: 6px;
  z-index: var(--row-z, 10);
  flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none;
  transform: translateY(-50%); padding: 0 8px;
}
.slot-row::-webkit-scrollbar { display: none; }
.lane-left .slot-row { justify-content: flex-end; }

/* A dose. Red outline until taken, matching the sketch. */
.chip {
  flex: 0 0 auto; display: flex; flex-direction: column; gap: 1px;
  align-items: flex-start; max-width: 118px; padding: 5px 8px;
  cursor: pointer; font: inherit; border: 2px solid #c0392b; border-radius: 10px;
  background: var(--card, #fff); color: inherit; text-align: left;
}
.chip-name {
  font-size: 12px; font-weight: 600; line-height: 1.2;
  max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.chip-time { font-size: 10px; color: var(--muted); }

.chip.is-taken {
  border-color: #1e7a52; background: #e9f3ee;
}
.chip.is-taken .chip-name { text-decoration: line-through; opacity: 0.75; }

/* Overdue and unticked: ask for attention without being unbearable. */
.chip.is-overdue { animation: chip-flash 1.4s ease-in-out infinite; }
@keyframes chip-flash {
  0%, 100% { background: var(--card, #fff); }
  50%      { background: rgba(192, 57, 43, 0.28); }
}
@media (prefers-reduced-motion: reduce) {
  .chip.is-overdue { animation: none; border-width: 3px; background: rgba(192, 57, 43, 0.18); }
}

.app-actions { display: flex; gap: 8px; padding: 10px 14px; }
.app-actions button { flex: 1; }
button.secondary { background: transparent; color: inherit; border: 1px solid var(--line); }

/* Dialogs. */
.dialog-backdrop {
  position: fixed; inset: 0; background: rgba(0, 0, 0, 0.45);
  display: flex; align-items: center; justify-content: center; padding: 20px; z-index: 1000;
}
.dialog {
  background: var(--card, #fff); border-radius: 14px; padding: 18px;
  width: min(420px, 100%); max-height: 88vh; overflow-y: auto;
}
.dialog h2 { margin: 0 0 12px; font-size: 18px; }
.dialog label { display: block; margin-top: 10px; margin-bottom: 4px; color: var(--muted); font-size: 14px; }
.dialog input, .dialog textarea { width: 100%; }
.times { border: 1px solid var(--line); border-radius: 10px; padding: 10px; margin-top: 12px; }
.time-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-top: 6px; }
.time-row input { width: auto; }
.dialog-actions { display: flex; gap: 8px; margin-top: 16px; }
.dialog-actions button { flex: 1; }

.lane-note { margin: 8px 14px 0; }
.app-actions button[disabled] { opacity: 0.5; cursor: not-allowed; }

/* ===== v0.4.0: one continuous ribbon of days ======================== */

.toolbar { position: relative; display: flex; padding: 10px 14px 0; }

.ribbon { position: absolute; inset: 0; }

/* A day that is not today sits on a grey wash, so drifting off today is
   obvious without reading the header. */
/* 3.5% black was technically present and practically invisible. A day that is
   not today has to READ as grey at a glance, which is the whole point. */
.day-band { position: absolute; left: 0; right: 0; background: #eceef0; }
.day-band.is-today { background: transparent; }

/* Midnight. A solid rule carrying the date that is starting - the boundary
   you scroll through rather than a wall you stop at. */
.day-break {
  position: absolute; left: 0; right: 0; height: 0;
  border-top: 2px solid #111; z-index: 4; /* under the chips, on purpose */
}
.day-break-label {
  position: absolute; left: 50%; transform: translateX(-50%); top: -11px;
  font-size: 12px; font-weight: 700; letter-spacing: 0.02em;
  background: #111; color: #fff; padding: 2px 10px; border-radius: 999px;
  white-space: nowrap;
}

/* Both lanes are always on screen now, so each keeps its half. */
.lane-right { left: 50%; right: 0; }
.lane-left { left: 96px; right: 50%; }

/* The detail panel behind a chip. */
.dialog.detail { max-width: 380px; }
.detail-strong { margin: 0 0 10px; font-size: 16px; font-weight: 600; }
.detail-rows { display: grid; grid-template-columns: auto 1fr; gap: 4px 14px; margin: 0 0 12px; }
.detail-rows dt { color: var(--muted); font-size: 14px; }
.detail-rows dd { margin: 0; font-size: 14px; }
.detail-notes {
  margin: 0 0 8px; padding: 10px; border-radius: 8px;
  background: rgba(0, 0, 0, 0.04); white-space: pre-wrap; font-size: 14px;
}
.dialog.detail .dialog-actions button { flex: 1 1 auto; }

/* Med | Activity switch, centred at the top of the add dialog. Swapping it
   relabels the form in place rather than throwing away what you typed. */
.kind-switch {
  display: flex; justify-content: center; gap: 0; margin: 0 auto 14px;
  border: 1px solid var(--line); border-radius: 999px; overflow: hidden;
  width: fit-content;
}
.kind-switch button {
  background: none; border: 0; color: var(--muted); font: inherit;
  padding: 8px 22px; cursor: pointer;
}
.kind-switch button[aria-pressed='true'] { background: #0f3b3a; color: #fff; }
.dialog h2 { text-align: center; }

/* ===== v0.5.0: a modern pass over windows and controls ============== */

:root {
  --radius: 14px;
  --focus: #2f6fed;
  --field: #f6f7f9;
}

/* Inputs. The browser defaults were doing the talking: thin 1px boxes with
   a system focus ring and a monospace time picker. */
.dialog input,
.dialog textarea {
  appearance: none; -webkit-appearance: none;
  font: inherit; font-size: 16px; /* 16px stops iOS zooming the page on focus */
  color: inherit; background: var(--field);
  border: 1.5px solid transparent; border-radius: 10px;
  padding: 11px 12px; transition: border-color .12s, background .12s;
}
.dialog input:hover, .dialog textarea:hover { background: #eef0f3; }
.dialog input:focus, .dialog textarea:focus {
  outline: none; background: var(--card, #fff);
  border-color: var(--focus); box-shadow: 0 0 0 3px rgba(47, 111, 237, 0.16);
}
.dialog input[type='time'], .dialog input[type='number'] { max-width: 100%; }
.dialog textarea { resize: vertical; min-height: 84px; }
.dialog label { font-weight: 500; color: #4a5560; margin-top: 14px; }

/* Windows. */
.dialog {
  border-radius: 20px; padding: 22px;
  box-shadow: 0 24px 60px rgba(0, 0, 0, 0.22), 0 2px 8px rgba(0, 0, 0, 0.1);
  border: 1px solid rgba(0, 0, 0, 0.06);
}
.dialog h2 { font-size: 20px; letter-spacing: -0.01em; margin-bottom: 4px; }
.dialog-backdrop { background: rgba(14, 20, 26, 0.5); backdrop-filter: blur(3px); }

/* Buttons. */
.dialog-actions { margin-top: 20px; gap: 10px; }
.dialog-actions button,
.app-actions button {
  border-radius: 12px; padding: 12px 16px; font-weight: 600;
  border: 1.5px solid transparent; cursor: pointer;
  transition: background .12s, border-color .12s, transform .06s;
}
.dialog-actions button:active, .app-actions button:active { transform: translateY(1px); }
button.secondary { background: var(--field); border-color: transparent; }
button.secondary:hover { background: #e7eaee; }

/* The two-state selectors read as one control with a moving highlight. */
.kind-switch {
  background: var(--field); border: 1.5px solid transparent;
  border-radius: 999px; padding: 3px; gap: 2px; margin-bottom: 16px;
}
.kind-switch button { border-radius: 999px; font-weight: 600; transition: background .12s, color .12s; }
.kind-switch.sched { width: 100%; }
.kind-switch.sched button { flex: 1; padding: 9px 10px; }

.times { border-radius: 12px; border-color: rgba(0, 0, 0, 0.08); }
.preview { margin: 10px 0 0; padding: 10px 12px; border-radius: 10px; background: var(--field); }

/* The Add button: a circle, as asked. */
.toolbar { align-items: center; gap: 10px; }
/* Grey circle with a dark +, per Steve on v0.5.0. */
.fab {
  width: 46px; height: 46px; min-width: 46px; padding: 0;
  border-radius: 50%; border: 1.5px solid rgba(0, 0, 0, 0.08); cursor: pointer;
  background: var(--field); color: #24303a;
  font-size: 26px; line-height: 1; font-weight: 400;
  display: flex; align-items: center; justify-content: center;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
  transition: transform .08s, box-shadow .12s, background .12s;
}
.fab:hover { background: #e7eaee; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.14); }
.fab:active { transform: scale(0.94); }
.fab[disabled] { opacity: 0.45; box-shadow: none; }

/* Chips get the same softening. */
.chip { border-radius: 12px; transition: transform .06s, box-shadow .12s; }
.chip:active { transform: scale(0.97); }

/* Detail panel rows. */
.detail-rows dt { font-weight: 500; }
.detail-notes { border-radius: 10px; background: var(--field); }

/* Dragging a chip. */
.chip.is-dragging {
  opacity: 0.35; pointer-events: none;
}
.drag-ghost {
  position: fixed; z-index: 900; pointer-events: none;
  border: 2px solid #c0392b; border-radius: 12px; background: var(--card, #fff);
  padding: 5px 8px; font-size: 12px; font-weight: 600;
  box-shadow: 0 10px 26px rgba(0, 0, 0, 0.26);
}
.drag-ghost .ghost-time { display: block; font-size: 11px; font-weight: 500; color: var(--muted); }
.drop-line {
  position: absolute; left: 0; right: 0; height: 0; z-index: 600;
  border-top: 2px dashed #2f6fed;
}
.drop-line span {
  position: absolute; left: 50%; transform: translateX(-50%); top: -11px;
  font-size: 11px; font-weight: 700; background: #2f6fed; color: #fff;
  padding: 2px 8px; border-radius: 999px; white-space: nowrap;
}

/* The move choices read as full-width statements, not a row of tight buttons:
   each one is a sentence the reader is agreeing to. */
.move-options { display: flex; flex-direction: column; gap: 8px; margin-top: 4px; }
.move-options button {
  text-align: left; padding: 13px 14px; border-radius: 12px;
  border: 1.5px solid var(--line); background: var(--card, #fff);
  color: inherit; font: inherit; cursor: pointer; transition: border-color .12s, background .12s;
}
.move-options button:hover { border-color: var(--focus); background: #f7f9ff; }
.move-options strong { display: block; font-size: 15px; margin-bottom: 2px; }
.move-options span { display: block; font-size: 13px; color: var(--muted); line-height: 1.35; }

/* --- v0.7.0 -------------------------------------------------------------- */

/* Three lines on a chip, in the order you read them: what it is, how much of
   it, and when. The name gets a line to itself because the name is what you
   scan a column of these for. */
.chip { gap: 2px; max-width: 132px; }
.chip-name { font-size: 13px; font-weight: 700; }
.chip-detail {
  font-size: 11px; color: var(--muted); line-height: 1.2;
  max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.chip-time { font-size: 11px; font-weight: 600; letter-spacing: 0.01em; }

/* v0.33.0: the description line. Same shape as `.chip-detail` - one line,
   ellipsised - because a chip that grows to fit its text would change the
   collision maths per chip, and `indentColumns` works on one height for the
   whole lane. Italic so the two grey lines are told apart at a glance: the
   detail is a DOSE and this is what the thing is for. */
.chip-desc {
  font-size: 11px; font-style: italic; color: var(--muted); line-height: 1.2;
  max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* A sideways drag arranges a stack rather than changing its time, so the drop
   line stops claiming a new time and says what it is actually about to do. */
.drop-line.is-arrange { border-top-color: #7a5af8; }
.drop-line.is-arrange span { background: #7a5af8; }

/* The one irreversible choice looks like one. */
.move-options button.danger { border-color: rgba(192, 57, 43, 0.45); }
.move-options button.danger:hover { border-color: #c0392b; background: rgba(192, 57, 43, 0.06); }
.move-options button.danger strong { color: #a5281a; }
.danger-text { color: #a5281a; }

/* A failed load has to look like one. An empty timeline reads as "nothing to
   take", which is not a thing this app may ever say by accident. */
.load-alert {
  margin: 0 14px 6px; padding: 9px 11px; border-radius: 10px;
  color: #a5281a; font-weight: 600; background: rgba(192, 57, 43, 0.09);
  border: 1.5px solid rgba(192, 57, 43, 0.35);
}

/* --- v0.8.0 -------------------------------------------------------------- */

/* A touch starting on a chip belongs to the gesture layer, not to the
   browser's scroll. Without this the browser claims the pan, fires
   pointercancel, and the drag dies halfway - which is what made dragging feel
   unreliable on a phone. Chips are small and the ribbon around them is mostly
   empty, so scrolling is unaffected in practice.

   The callout and selection suppression are for the same gesture: a long press
   on a touch screen otherwise raises a text-selection bubble on top of it. */
.chip {
  touch-action: none;
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  user-select: none;
}

/* Held long enough to arrange. It has to be unmistakable that the gesture
   took, because the whole point is that what happens next is different. */
.chip.is-arranging {
  opacity: 1;
  border-color: #7a5af8;
  box-shadow: 0 0 0 4px rgba(122, 90, 248, 0.22), 0 8px 20px rgba(0, 0, 0, 0.18);
  transform: scale(1.06);
}

/* --- v0.8.1 -------------------------------------------------------------- */

/* Version, Sign out, and who you are, stacked in the top right. The email sits
   under Sign out because that is the corner you look at when you want to know
   which account you are in. */
/* Superseded by .account in v0.13.0 - the markup these styled is gone. Kept
   only so an older cached page does not lose its layout mid-upgrade. */
.app-bar-right {
  margin-left: auto; display: flex; flex-direction: column;
  align-items: flex-end; gap: 2px; min-width: 0;
}
.app-bar-row { display: flex; align-items: center; gap: 10px; }
#welcome {
  max-width: 46vw; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* "go to now" as a link above the date, replacing the full-width button that
   used to sit under the ribbon. It is one of the most-used controls, and it
   was as far from the date it acts on as the layout allowed. */
.now-link { display: flex; justify-content: center; padding: 8px 14px 0; }
.now-link button { font-size: 14px; }
.now-link button[disabled] { opacity: 0.45; cursor: default; text-decoration: none; }

/* GO TO NOW IS HIDDEN WHILE NOW IS CENTRED (v0.36.0) - Steve: "hide the go to
   now", and bring it back once somebody scrolls. There is nowhere to go.

   VISIBILITY, NOT `hidden` OR `display: none`. Those take the button's box out
   of the band, and the band would change height every time it came and went -
   so the whole ribbon would jump each time somebody scrolled past now. v0.23.0
   took the bell out of the flow for exactly that. `visibility` keeps the slot
   and removes the button from the tab order and the accessibility tree, which
   is what hidden should mean for a control with nothing to do.

   The flag is on `#app` because it is the nearest element holding both this
   button and the ribbon it describes - see `syncFollow`. */
#app[data-following] #now-reset { visibility: hidden; }

/* The action row now holds only the install button, and only sometimes. Empty,
   it should take no room at all. */
.app-actions:empty, .app-actions:has(> :only-child[hidden]) { display: none; }

/* --- v0.8.2: the layer scale ---------------------------------------------

   Rows ten minutes apart overlap - that is eighteen pixels for a chip three
   times that tall - so chips are now explicitly ordered rather than stacking
   in whatever order the rows came back in. timeline.js writes each chip a
   z-index; everything drawn OVER the ribbon has to clear the whole band.

        4   the midnight rule       (deliberately under the chips)
    10-320   chips at rest          (stackLevels() in timeline.js)
       400   the chip under the pointer
       500   the now line
       600   the drop line while dragging
       900   the drag ghost
      1000   dialogs

   Change one of these and check the rest: before this release the dialog
   backdrop sat at 10, which a raised chip would now have covered. */

.chip { position: relative; z-index: var(--chip-z, 10); }

/* Hover is a PC affordance. Scoped to devices that actually have a pointer,
   because on a touch screen :hover sticks after a tap and the chip would stay
   raised until you touched something else. */
@media (hover: hover) {
  /* The row has to come up with the chip. `.slot-row` is a stacking context
     (it is transformed), so raising the chip alone lifts it only against its
     own row-mates - which never overlap it in the first place. */
  .slot-row:has(.chip:hover) { z-index: 400; }
  .chip:hover {
    z-index: 400;
    /* OPAQUE. The point of raising it is to read it whole, which a tint over
       whatever it happens to be covering does not achieve. */
    background: #fdf1ef;
    box-shadow: 0 6px 18px rgba(0, 0, 0, 0.16);
  }
  .chip.is-taken:hover { background: #e2efe8; }
}

/* --- v0.9.0 -------------------------------------------------------------- */

/* One strip instead of three stacked rows. Add is taken out of the flow and
   pinned left so the date stays centred on the spine rather than being pushed
   off by whatever sits beside it. */
/* 80px, which is 20% more than the 66.69px the first attempt produced: Steve
   on that version - "the last reduction was too much". Set as a min-height
   rather than padding so the band is a deliberate size rather than whatever
   its contents happen to add up to. Still well under the ~115px of the three
   stacked rows it replaced. */
.top-bar {
  position: relative; display: flex; align-items: center;
  padding: 6px 14px; min-height: 80px;
}
.top-bar .toolbar { position: absolute; left: 14px; padding: 0; }
.top-centre {
  margin: 0 auto; display: flex; flex-direction: column; align-items: center; gap: 0;
}
.top-centre .now-link { padding: 0; line-height: 1.2; }
.top-centre .day-nav { padding: 0; }
.top-centre .day-nav button { padding: 4px 10px; }

/* Minute marks between the hour rules, against the spine. A ruler, not
   content: short and quiet, but they have to be legible or they are just
   noise. The hour rules stay the full width and keep the numbers. */
.tick {
  position: absolute; left: 50%; width: 13px; height: 0;
  border-top: 1.5px solid #b4bec4; pointer-events: none;
}
/* The half hour crosses the spine and runs longer, so the eye has something to
   count from without reading a number. */
.tick.is-half {
  width: 30px; margin-left: -15px; border-top-color: #8b979e;
}

/* --- v0.10.0 ------------------------------------------------------------- */

/* Pinned right in the top band, opposite Add. A bare icon - no frame, no fill,
   no label - because it is a toggle sitting beside the date, not an action
   competing with Add for attention. ALWAYS PRESENT as of v0.34.0 - it used to
   appear only while something was overdue, which meant the only moment you
   could silence the chime was while it was already ringing.

   It still gets a 44px hit area: an icon that small is easy to draw and hard
   to tap, and the thing you are reaching for is a noise you want stopped. */
.mute-chime {
  position: absolute; right: 10px; top: 50%; transform: translateY(-50%);
  border: 0; background: none; padding: 0; cursor: pointer;
  width: 44px; height: 44px; font-size: 22px; line-height: 1;
  display: flex; align-items: center; justify-content: center;
  border-radius: 50%; transition: background .12s, transform .08s;
}
.mute-chime:hover { background: rgba(0, 0, 0, 0.06); }
.mute-chime:active { transform: translateY(-50%) scale(0.92); }
/* Silenced: stated by the icon, and dimmed so it reads as off at a glance. */
.mute-chime[aria-pressed='true'] { opacity: 0.5; }

/* Overdue: BLINK, not a gentle pulse. Steve asked for red and it has to read
   as red at arm's length, so the border and the fill both go. */
@keyframes chip-flash {
  0%, 100% { background: var(--card, #fff); border-color: #c0392b; }
  50%      { background: #f3b4ac; border-color: #8d2318; }
}
/* THE TEMPO IS DECLARED ONCE. The bell blinks with the chips as of v0.25.0,
   and two durations that have to agree are one too many - the same reasoning
   that collapsed v0.23.0's two chip gaps into one number. Change it here and
   both change together. */
:root { --overdue-blink: 1.2s; }
.chip.is-overdue { animation: chip-flash var(--overdue-blink) steps(1, end) infinite; }

/* --- v0.12.1 ------------------------------------------------------------- */

/* Rows closer together in time than a chip is tall overlap - a chip is three
   lines, about 58px, and the ribbon runs at 110px to the hour, so anything
   inside about half an hour collides. Ordering decided which one won; the
   loser was still buried under it.

   Each row in a colliding cluster now steps sideways, AWAY from the spine, so
   both stay readable and both stay reachable. Nothing moves vertically: a chip
   still sits exactly at its own minute, because that position is the time and
   the app does not get to fudge it.

   The step scales with the viewport for a plain reason. On a phone showing both
   lanes, a lane is about half of 390px and a chip is up to 132px, so there are
   only a few dozen pixels to spend; on a desktop there is room to be generous.
   clamp() spends what is actually there. */
/* v0.19.0: the offset is MEASURED in `spreadRows`, not a fixed step. A step of
   `column x 35px` moved a chip by a quarter of its own width, so colliding
   doses overlapped less and still overlapped. `--row-offset` is however many
   pixels it actually takes to clear the column to the left. */
.lane-right .slot-row {
  padding-left: calc(var(--lane-gutter, 8px) + var(--row-offset, 0px));
}
.lane-left .slot-row {
  padding-right: calc(var(--lane-gutter, 8px) + var(--row-offset, 0px));
}

/* A row spans the WHOLE lane, not just its chips - it has to, because it is
   what positions them. Once rows step sideways, one row's empty padding can
   come to rest directly over a neighbouring row's chip, and because the nearer
   row sits higher in the layer scale, that padding would swallow the tap. You
   would see a chip and be unable to press it.
   
   So a row takes no pointer events and a chip takes its own. Hit-testing falls
   through the empty space to whatever is actually drawn there. Nothing is lost:
   every gesture listener is bound to the LANE and delegates through
   `.closest('.chip')`, so a press on row padding was already a no-op.
   
   The one thing this does give up is dragging a row's padding to scroll a long
   stack sideways - already close to unreachable, since the scrollbar is hidden
   and the chips themselves take the drag. */
.slot-row { pointer-events: none; }
.slot-row > .chip { pointer-events: auto; }

/* --- v0.13.0 ------------------------------------------------------------- */

/* THE HOUR SITS ON THE SPINE. It used to live in a 96px gutter down the left
   edge at 22px, which is a lot of screen spent on something you glance at, and
   it put the time as far as it could get from the chips it labels. On the spine
   it reads as one column: hour, dose, hour, dose.

   Smaller, lighter, and lifted off the rule by its own background, so the hour
   line does not strike through the text. It stays UNDER the chips in the layer
   scale - if a dose lands on the hour, the dose is the thing you need to see. */
.hour-label {
  left: 50%; right: auto; width: auto; transform: translateX(-50%);
  top: -9px; text-align: center; z-index: 4;
  font-size: 11px; font-weight: 600; letter-spacing: 0.06em;
  text-transform: uppercase; color: var(--muted);
  background: var(--card, #fff); padding: 0 7px; border-radius: 999px;
}

/* The gutter the hour used to occupy is now the lanes'. That is 96px of extra
   width shared between them, which is also 96px more room for v0.12.1's
   sideways step before chips start colliding again. */
.lane-left { left: 0; }
.timeline-inner[data-lane='med'] .lane-right { left: 0; }

/* A channel down the spine for the hour to live in. Without it a chip starting
   8px from the spine lands straight on top of the label - a screenshot showed
   "10 P" and "11 P" sliced off by chips, which is the busiest hour losing the
   one thing that says which hour it is.
   
   32px each side, MEASURED: the widest label is "10 pm" at 55.9px, so its half
   width is 28px, and 26px left it clipped by two pixels - close enough to look
   deliberate and still wrong. It costs 64px of the 96px just reclaimed from the
   old gutter, so the lanes are still 32px wider than they were AND the hour is
   readable. A test hit-tests every on-screen label against the chips, so if a
   future change to the label's size eats that margin, it fails rather than
   shipping two clipped characters. */
.slot-row { --lane-gutter: 32px; }

/* The wall clock beside the date. Same weight as the date, dimmer, so the pair
   reads as one line without the time shouting over the day. */
.day-clock {
  font-variant-numeric: tabular-nums;
  color: var(--muted); font-weight: 600; letter-spacing: 0.01em;
}

/* A chip may not be wider than the room left for it. Its max-width was a flat
   132px, which fit when a row started 8px from the spine. It no longer does:
   the spine channel (32px) plus an indent step (up to 9vw) plus 132px plus the
   row's own right padding overruns a half-width lane on a phone, and the row
   scrolls it out of sight - a screenshot showed "Pain Med" sliced by the screen
   edge.
   
   min() caps it at whatever the row can actually give. The chip's three lines
   already ellipsize, so a narrow chip loses the tail of a long name rather than
   disappearing off the edge - and the name's START, the part you scan for, is
   what survives. */
.chip { max-width: min(132px, 100%); }

/* The date and the clock each stay on one line. Adding the clock pushed the
   pair past a 360px screen, and the date wrapped to "Today · Mon" / "9/21" -
   two lines of header for something Steve had already asked to keep compact.
   The tighter gap and the arrows' padding are what actually buy the room - a
   test proves it by turning this block off and watching the header grow to two
   lines. The nowrap is belt and braces for a longer date string later. */
.day-nav { gap: 8px; }
#day-label, .day-clock { white-space: nowrap; }
/* Phone widths, which is every phone worth supporting and not just the small
   ones - 412px is a current Pixel. The first cut of this stopped at 380px and
   a screenshot at 412 showed the date ellipsised to "Today...", because the
   centre was giving up characters while the arrows kept their generous padding
   and the clock its full size.
   
   The DATE is the thing being read. So the chrome yields first: tighter gaps,
   narrower arrow hit-padding, a smaller clock - the now-line carries the time
   as well, so the header clock is the second copy - and a smaller bell. That
   buys enough for "Today - Mon 9/21" to stay whole at 412 even with the bell
   on screen. */
@media (max-width: 430px) {
  .day-nav { gap: 4px; font-size: 15px; }
  .top-centre .day-nav button { padding: 4px 4px; }
  .day-clock { font-size: 0.88em; }
  .top-bar { gap: 2px; padding: 6px 8px; }
  .mute-chime { width: 30px; height: 30px; font-size: 18px; }
  .top-right { gap: 2px; }
}

/* THE HEADER IS THREE COLUMNS NOW, not a centred block with things floated
   over it. "now" moves out of the centre and into the top right corner - it is
   a way BACK to the present, not part of reading the date, and stacking it
   above the date made the header two lines tall for a one-word link.
   
   That was one absolute overlay too many. The + was absolute on the left and
   the bell absolute on the right, which worked while the centre was narrow;
   adding the clock and then "now" put four things in the same space and a
   screenshot showed the clock, the bell, the next-day arrow and the link all
   written on top of each other.
   
   So: add on the left, date in the middle taking the slack, bell and "now" on
   the right, and nothing overlapping anything. The bell sits inside the link
   because it comes and goes with whether something is overdue, and a control
   that appears should not shift the one that is always there. */
.top-bar { gap: 6px; }
/* Equal side columns, so the date lands on the spine rather than wherever the
   right-hand controls happen to leave it. The first attempt let the sides size
   themselves and pushed the date 12px off centre - which a v0.9.0 test caught,
   because "the date stays centred on the spine" is a property Steve asked for
   and not an accident of the markup. */
.top-bar .toolbar { position: static; flex: 1 1 0; }
/* ...but never below their own content. `flex: 1 1 0` alone let the right-hand
   column shrink to ZERO while the centre kept its full content width, so the
   bell and "now" had no room of their own and spilled backwards across the
   date. min-content is the floor: the sides stay equal while there is space to
   share - which is what centres the date - and when there is not, the centre
   gives up characters instead of the corner giving up its controls. */
.top-bar .toolbar, .top-right { min-width: min-content; }
/* Allowed to SHRINK, not just to sit. With `0 0 auto` the centre refused to
   give any ground, so on a 360px screen with the bell showing it simply ran
   off the right-hand edge: the clock was cut mid-"PM" and the next-day arrow
   and "now" were pushed off the screen entirely. Centring is the nicety;
   staying on the screen is not. */
.top-centre { flex: 0 1 auto; margin: 0; min-width: 0; }
/* `min-width: 0` alone was not enough. `.top-centre` is a flex COLUMN, so the
   date row's width comes from its content and it happily grew wider than its
   own parent - 344px of day-nav inside a 326px column - and slid out under the
   bell. max-width pins it to the column; the label gives up characters first,
   because "Today · Mon 9/21" losing its tail still tells you the day. */
.day-nav { min-width: 0; max-width: 100%; }
#day-label { overflow: hidden; text-overflow: ellipsis; min-width: 0; }
.top-right {
  flex: 1 1 0; display: flex; align-items: center; justify-content: flex-end;
  gap: 4px;
}
.mute-chime {
  position: static; transform: none; flex: 0 0 auto;
  width: 36px; height: 36px; font-size: 20px;
}
.mute-chime:active { transform: scale(0.92); }
.top-right .now-link { padding: 0; display: flex; flex: 0 0 auto; }
.top-right .now-link button { font-size: 14px; padding: 0; }

/* A second, tighter tier. 390px is a current iPhone width and 412 a current
   Pixel; the 430px tier buys enough for the Pixel but not the iPhone, so the
   date was still being clipped there. The date holds its size longer than
   anything around it because it is what the band exists to say. */
@media (max-width: 400px) {
  .day-nav { font-size: 14px; gap: 3px; }
  .day-clock { font-size: 0.85em; }
  .top-centre .day-nav button { padding: 4px 2px; font-size: 16px; }
}

/* --- v0.13.0: the account menu -------------------------------------------- */

/* The version badge, the email and Sign out were permanent furniture for
   things you look at rarely. One avatar replaces all three, which is the shape
   every other app uses for this, and gives the patient's name the whole bar. */
.account { position: relative; flex: 0 0 auto; }
.avatar {
  width: 40px; height: 40px; padding: 0; border: 0; border-radius: 50%;
  background: none; cursor: pointer; display: block; line-height: 0;
}
.avatar svg { width: 40px; height: 40px; display: block; border-radius: 50%; }
.avatar:hover svg { filter: brightness(0.96); }
.avatar:focus-visible { outline: 2px solid var(--accent, #0f3b3a); outline-offset: 2px; }
.avatar[aria-expanded='true'] svg { filter: brightness(0.92); }

.account-menu {
  position: absolute; right: 0; top: calc(100% + 8px); z-index: 1100;
  min-width: 232px; max-width: min(88vw, 320px);
  background: var(--card, #fff); border: 1px solid var(--line);
  border-radius: 12px; padding: 8px;
  box-shadow: 0 12px 32px rgba(0, 0, 0, 0.16);
  display: flex; flex-direction: column; gap: 2px;
}
.account-menu[hidden] { display: none; }
.account-menu hr { border: 0; border-top: 1px solid var(--line); margin: 6px 2px; }

/* Every row is the same shape, so the eye does not have to re-learn the menu
   halfway down it. */
.account-menu button {
  width: 100%; text-align: left; background: none; border: 0; cursor: pointer;
  font: inherit; color: inherit; padding: 9px 10px; border-radius: 8px;
  text-decoration: none;
}
.account-menu button:hover:not([disabled]) { background: rgba(0, 0, 0, 0.06); }
.account-menu button:focus-visible { outline: 2px solid var(--accent, #0f3b3a); outline-offset: -2px; }
.account-menu button[disabled] { opacity: 0.45; cursor: default; }

.account-who {
  margin: 0; padding: 6px 10px 8px; word-break: break-word; line-height: 1.3;
}
.account-version { margin: 0; padding: 2px 10px 4px; }

/* --- v0.14.0: the front door ---------------------------------------------- */

/* The gate card grew from a paragraph into a form, so it needs room and a
   left-aligned reading line - a centred column of labelled inputs is hard to
   scan. The heading stays centred; the form does not. */
.gate-card { text-align: left; max-width: 26rem; }
.gate-card h1 { text-align: center; margin-bottom: 4px; }
.gate-card .lede { text-align: center; }

.gate-points {
  margin: 14px 0 20px; padding-left: 20px; color: var(--muted);
  font-size: 0.95rem; line-height: 1.5;
}
.gate-points li { margin: 4px 0; }

.auth-panel { display: flex; flex-direction: column; gap: 4px; }
.auth-panel[hidden] { display: none; }
.auth-panel h2 { margin: 6px 0 10px; font-size: 1.15rem; }
.auth-panel label { font-size: 0.85rem; font-weight: 600; margin-top: 10px; }
.auth-panel > button:not(.linkish) { margin-top: 16px; }
.auth-panel p.small { margin: 4px 0 0; }

/* The password field and its eye share one box, so the eye reads as part of
   the field rather than as a control that happens to sit beside it. */
.pw-field { position: relative; display: flex; }
.pw-field input { flex: 1; padding-right: 46px; }
.pw-eye {
  position: absolute; right: 4px; top: 50%; transform: translateY(-50%);
  width: 38px; height: 38px; padding: 0; border: 0; border-radius: 8px;
  background: none; cursor: pointer; font-size: 17px; line-height: 1;
  opacity: 0.55;
}
.pw-eye:hover { background: rgba(0, 0, 0, 0.06); opacity: 0.8; }
.pw-eye[aria-pressed='true'] { opacity: 1; }

/* The ways out of a panel, on one line, quieter than the thing you came to do. */
.auth-alt {
  display: flex; flex-wrap: wrap; gap: 14px; justify-content: space-between;
  margin-top: 16px; padding-top: 12px; border-top: 1px solid var(--line);
}

.gate-foot { text-align: center; margin: 18px 0 0; }

/* Every field on the door looks the same. The base rule only ever styled
   `input[type="email"]`, so the password boxes fell back to the browser's thin
   default and sat beside a filled, rounded email box looking like a different
   app. The primary button had the same problem from the other direction: its
   look was scoped to `.tester`, which the front door replaced. */
.gate-card input {
  width: 100%;
  appearance: none; -webkit-appearance: none;
  font: inherit; font-size: 16px;   /* 16px stops iOS zooming the page on focus */
  color: var(--ink); background: var(--field, #f2f4f7);
  border: 1.5px solid transparent; border-radius: 10px;
  padding: 11px 12px; margin-bottom: 0;
  transition: border-color .12s, background .12s;
}
.gate-card input:hover { background: #eef0f3; }
.gate-card input:focus {
  outline: none; background: var(--card, #fff);
  border-color: var(--focus); box-shadow: 0 0 0 3px rgba(47, 111, 237, 0.16);
}

.auth-panel > button:not(.linkish) {
  width: 100%; padding: 12px 14px;
  color: var(--deep-ink); background: var(--deep);
  border: 1px solid var(--deep); border-radius: 10px;
  font-weight: 600;
}
.auth-panel > button:not(.linkish):hover { filter: brightness(1.08); }
.auth-panel > button:not(.linkish):active { transform: translateY(1px); }

/* --- v0.16.0: a single occurrence ----------------------------------------- */

/* Radios, not pills. The pills above choose a KIND of schedule; this chooses
   whether there is a schedule at all, which is a different question and gets a
   different control. Side by side while they fit, stacked when they do not. */
.radio-row {
  display: flex; flex-wrap: wrap; gap: 8px 18px;
  margin: 10px 0 4px;
}
.radio-row label {
  display: flex; align-items: center; gap: 7px;
  font-size: 0.95rem; font-weight: 500; cursor: pointer;
  margin: 0; color: inherit;
}
/* `appearance: auto` is load-bearing, not tidying. `.dialog input` sets
   `appearance: none` - right for the text and time fields it was written for,
   and fatal here: a radio with no appearance is a rounded grey box with no dot,
   so NOTHING looks selected. A screenshot caught it; the tests were green
   because `:checked` was true the whole time. */
.radio-row input[type='radio'] {
  appearance: auto; -webkit-appearance: radio;
  width: 18px; height: 18px; margin: 0; padding: 0;
  background: none; border: 0; border-radius: 50%;
  accent-color: var(--deep); cursor: pointer; flex: 0 0 auto;
}
#per-day-row[hidden] { display: none; }

/* --- v0.37.0: which day a single occurrence was -------------------------- */

/* The day choices stack rather than wrap. "Yesterday · Sat 9/26" is long
   enough that two to a line at 360px would split a label from its date, and a
   list of four is read top to bottom. */
.radio-stack { flex-direction: column; gap: 10px; }

/* EXPLICIT, because `hidden` alone loses to any `display:` rule on the same
   element - the v0.1.7 trap, which kept a sign-in panel on screen that was
   marked hidden. `.dialog input` sets one. */
#once-date[hidden] { display: none; }

/* The chosen day and time, shown in the "Time taken?" group in place of a bare
   time field, with a way back into the popup beside it. */
.once-when {
  display: flex; align-items: baseline; flex-wrap: wrap; gap: 4px 12px;
  margin: 6px 0 2px;
}
.once-summary { font-weight: 600; }

/* --- v0.17.0: the halo says what just changed ----------------------------- */

/* Steve, v0.16.1: "when a new med or activity is entered onto the grid, have a
   purple halo around the entry"; then v0.17.0: "reduce the purple glow to 5
   seconds and also show purple glow on any edit/save state toggle (ignore
   close with nothing done and delete)."
   
   The halo lives on a PSEUDO-ELEMENT rather than on the chip, because the chip
   may already be running the overdue blink - and two `animation` declarations
   on one element do not both win, the later one simply replaces the earlier.
   A new dose that is already overdue needs to blink AND be findable. */
.chip.is-halo::after {
  content: ''; position: absolute; inset: -3px;
  border-radius: 13px; pointer-events: none;
  box-shadow: 0 0 0 3px rgba(122, 90, 248, 0.55),
              0 0 18px 5px rgba(122, 90, 248, 0.42);
  animation: chip-halo 5s ease-out forwards;
}

/* Held, then faded - rather than fading from the first instant. A glow that
   starts dying immediately is one you can miss by looking away for a moment,
   which defeats the purpose. */
@keyframes chip-halo {
  0%, 55% { opacity: 1; }
  100%    { opacity: 0; }
}

/* No animation: the halo simply stays until the script clears it at five
   seconds, which is the same promise kept a plainer way. */
@media (prefers-reduced-motion: reduce) {
  .chip.is-halo::after { animation: none; opacity: 1; }
}

/* --- v0.18.0: the buttons, and sliding the lanes sideways ----------------- */

/* Steve, on a phone: "I'd like a cleaner look/style to the buttons ... looks
   clunky and cheap." And underneath the look there was a real fault - the
   detail panel's four buttons overflowed the dialog and "Mark taken" was
   CLIPPED at the right edge on his screen.
   
   Both come from the same cause. Four equal grey slabs in one row is what you
   get when every action is styled as though it carries the same weight: it
   reads as a wall, and it does not fit. Ranking them fixes the look and the
   overflow at once. */

/* A row that CANNOT overflow, whatever goes in it. Flex items refuse to shrink
   below their own text by default, which is exactly how "Mark taken" ended up
   off screen; `min-width: 0` lets them, and wrapping catches the case where
   even that is not enough. */
.dialog-actions { flex-wrap: wrap; }
.dialog-actions button { min-width: 0; }

/* The stacked shape: the answer on its own full-width row, everything else on
   the row below. */
.dialog-actions.stacked { display: block; }
.dialog-actions.stacked .wide { display: block; width: 100%; }
.dialog-actions.stacked .action-row {
  display: flex; gap: 8px; margin-top: 8px;
}
.dialog-actions.stacked .action-row button { flex: 1 1 0; min-width: 0; }

/* THE PRIMARY ACTION IS THE ONE THE PANEL IS OPEN FOR. It was the same grey as
   Close, so nothing in the row said which button answered the question. Deep
   teal is the app's one bold note and this is what it is for. */
.dialog-actions button:not(.secondary),
.app-actions button:not(.secondary) {
  background: var(--deep); color: var(--deep-ink);
  border-color: transparent; letter-spacing: 0.005em;
}
.dialog-actions button:not(.secondary):hover,
.app-actions button:not(.secondary):hover { filter: brightness(1.12); }

/* The rest step back: lighter weight, a hairline instead of a fill, and a
   smaller type size. Three quiet options under one clear one reads as a
   choice; four loud ones read as clutter. */
.dialog-actions .action-row button.secondary {
  background: transparent; border: 1px solid var(--line);
  font-weight: 500; font-size: 15px; padding: 10px 8px; color: var(--muted);
}
.dialog-actions .action-row button.secondary:hover {
  background: var(--field); border-color: var(--line); color: var(--ink);
}
.dialog-actions .action-row button.danger-text { color: #a5281a; }
.dialog-actions .action-row button.danger-text:hover {
  color: #a5281a; border-color: rgba(165, 40, 26, 0.45);
  background: rgba(165, 40, 26, 0.05);
}

/* A visible focus ring. The old buttons had none that carried over the new
   backgrounds, and a dialog you arrive at by keyboard needs one. */
.dialog-actions button:focus-visible,
.app-actions button:focus-visible {
  outline: 2px solid var(--focus); outline-offset: 2px;
}

/* Steve: "allow user to touch and hold empty area on the screen and slide it to
   the left or right in order to expose meds or activities that currently go off
   screen to the far right or far left because of horizontal stacking."
   
   v0.12.1 steps colliding doses sideways so none is buried. Four or five at the
   same time on a phone and the outermost runs off the edge - readable only if
   the ribbon can be slid.
   
   ONLY THE LANES MOVE. The spine, the hour rules, the hour labels and the now
   line are the reference grid: a chip's position IS its time, and sliding the
   grid with the chips would say nothing about where anything is. The content
   slides over a grid that stays put.
   
   The transform is applied ONLY when panned. `translateX(0)` still creates a
   stacking context, which would change how chips in the two lanes layer against
   each other even at rest - so at rest there is no transform at all and the
   layering is exactly what it was. */
.timeline-inner[data-panned] .lane { transform: translateX(var(--pan-x, 0px)); }

/* v0.19.0 REVERSES v0.18.0 on this, at Steve's request. v0.18.0 held the spine
   and the hour labels still and slid only the chips, reasoning that a chip's
   position IS its time so the grid must not move with it. Sliding a ribbon and
   watching its centre line stay behind reads as the chips coming loose - so the
   spine and the hour labels travel with them and the whole thing pans as one
   canvas.
   
   What still does NOT move: the horizontal hour rules and the now line. They
   span the full width, so sliding them sideways would only open a gap at one
   edge, and the height they sit at is the part that carries meaning. */
.timeline-inner[data-panned] .spine { transform: translateX(var(--pan-x, 0px)); }
.timeline-inner[data-panned] .hour-label {
  transform: translateX(calc(-50% + var(--pan-x, 0px)));
}

/* THE MINUTE MARKS ARE ANCHORED TO THE SPINE TOO (`left: 50%`), and the first
   version of this release moved the spine and the labels and left them behind -
   a ruler stranded half a screen from the line it measures. Steve circled them.
   
   Everything hung off the spine travels with the spine. The only things that
   stay are the ones that span the FULL WIDTH: the hour rules and the now line,
   where sliding sideways would just open a gap at one edge. */
.timeline-inner[data-panned] .tick { transform: translateX(var(--pan-x, 0px)); }

/* AND SO DOES THE MIDNIGHT PILL. It is anchored at `left: 50%` exactly as the
   hour labels and the minute marks are, and it was the one thing hung off the
   spine that stayed behind - v0.19.0's defect, in the one element v0.19.0 did
   not go back and check. Steve reported the pill twice in one release, which is
   what moved this from "recorded" to "fixed". */
.timeline-inner[data-panned] .day-break-label {
  transform: translateX(calc(-50% + var(--pan-x, 0px)));
}

/* Set once the spine has carried the labels far enough that one would be cut
   by an edge. A sliced label is worse than no label - v0.13.0 settled that when
   a chip rendered "10 PM" as "10 P"; panning renders "3 AM" as "M", which is
   the same defect arriving by a different road. The hour RULES stay put, so the
   grid is still legible while panned, just unlabelled. */
.timeline-inner .hour-label { transition: opacity .12s ease-out; }
.timeline-inner[data-labels-off] .hour-label { opacity: 0; }

/* THE PILL NEEDS ITS OWN FLAG, not the hour labels'. The note on
   `markLabelsClipped` says "every label shares the spine's x, so measuring one
   settles all of them" - true of the hour labels, which are all about the width
   of "10 pm", and NOT true of a date pill, which is wider and therefore runs
   into an edge first. Sharing one flag would either slice the pill or blank the
   hours early; two flags, two measurements. */
.timeline-inner .day-break-label { transition: opacity .12s ease-out; }
.timeline-inner[data-day-labels-off] .day-break-label { opacity: 0; }

/* MORE IS OUT THERE. Chips clear each other completely as of v0.19.0, so a
   crowded hour runs off the edge by design - and something has to say so, or a
   dose that exists is simply invisible with nothing to suggest otherwise.
   A bar one chip tall, in the same purple as Reset to center. */
.timeline-frame { position: relative; flex: 1; min-height: 0; display: flex; }
.timeline-frame > .timeline { flex: 1; min-width: 0; }
/* `top` and `height` are set from script: the bar spans the cut-off cluster
   from its topmost edge to its bottommost, so it is level with the doses it is
   about rather than being a fixed stub at the middle of the screen. */
.offscreen-mark {
  position: absolute; width: 4px; height: 64px; border-radius: 2px;
  background: #7a5af8; pointer-events: none; z-index: 800;
  box-shadow: 0 0 8px rgba(122, 90, 248, 0.55);
}
.offscreen-mark.is-left { left: 2px; }
.offscreen-mark.is-right { right: 2px; }

/* Dragging across the ribbon SELECTED IT - the hour labels came up highlighted
   in blue behind the chips, which a screenshot showed and no assertion would
   have. A drag is a gesture here, never a text selection; chips have said so
   since v0.12.1 and the ground they sit on had not. */
.timeline { user-select: none; -webkit-user-select: none; }

/* ZOOM. The ribbon SCALES rather than re-laying out, so a chip keeps its shape
   and its time and the whole picture just gets smaller.
   
   `transform-origin: 50% 0` is the spine: zooming holds the centre line still
   and grows or shrinks around it, which is what makes it feel like zooming a
   map rather than sliding one.
   
   The margin is not decoration. A transform scales what you SEE and not the
   layout height, so the scroller would let you run past the end when zoomed out
   and refuse to reach it when zoomed in. `--inner-h` is the ribbon's natural
   height, published by `renderRibbon`, and this corrects the scrollable area by
   exactly the difference.
   
   Applied ONLY when zoomed, like the pan: `scale(1)` still creates a stacking
   context and a containing block, and at rest nothing should change at all. */
.timeline-inner[data-zoomed] {
  transform: scale(var(--zoom, 1));
  transform-origin: 50% 0;
  margin-bottom: calc(var(--inner-h, 0px) * (var(--zoom, 1) - 1));
}

/* THE FULL-WIDTH FURNITURE IS COUNTER-SCALED BACK OUT.
   
   A uniform scale shrinks the hour rules, the day bands and the now line along
   with everything else, so zooming out left the grid floating in white gutters
   with a visible edge where the ribbon stopped - a screenshot showed it at once.
   
   There is no information in how LONG a horizontal rule is; it spans the width
   because that is what a rule does. `scaleX(1 / zoom)` about the centre undoes
   exactly the horizontal part of the parent's scale, so these stay edge to edge
   at any zoom while the chips, the spine and the ruler shrink as they should.
   
   The now line's time label rides along and stays legible, which is the point
   of a label. */
.timeline-inner[data-zoomed] .hour,
.timeline-inner[data-zoomed] .day-band,
.timeline-inner[data-zoomed] .day-break,
.timeline-inner[data-zoomed] .now-line {
  transform: scaleX(calc(1 / var(--zoom, 1)));
  transform-origin: 50% 50%;
}

/* `pan-y` hands vertical dragging to the browser's own scroller and leaves
   horizontal gestures to the script. Without it the scroller and the pan fight
   over the same finger; with it neither has to guess. Chips already set
   `touch-action: none`, so their own gestures are untouched. */
.timeline { touch-action: pan-y; }

/* Zero height, so it overlays the top of the ribbon rather than pushing it
   down. */
.pan-reset-wrap {
  position: relative; height: 0; z-index: 900;
  display: flex; justify-content: center; align-items: flex-start; gap: 8px;
}
/* NOT absolute. Both pills can be showing at once - zoomed and slid - and two
   absolutely positioned things in one spot is the mistake v0.13.0 spent four
   attempts unpicking in the header. A flex row centres one or two of them
   without either knowing the other exists. */
.pan-reset {
  position: relative; top: 8px;
  border: 0; border-radius: 999px; padding: 7px 16px;
  font: inherit; font-size: 14px; font-weight: 600;
  background: #7a5af8; color: #fff; cursor: pointer; white-space: nowrap;
  box-shadow: 0 2px 10px rgba(122, 90, 248, 0.38);
}
.pan-reset:hover { background: #6a48f0; }
.pan-reset:active { transform: translateY(1px); }
.pan-reset:focus-visible { outline: 2px solid #7a5af8; outline-offset: 3px; }

/* A ROW USED TO CLIP ITS OWN CHIPS, which is why sliding the lane revealed
   nothing: the chip and the box cutting it off moved together.
   
   `.slot-row` carried `overflow-x: auto` from v0.8.x - a per-row sideways
   scroll that v0.13.0 already recorded as "close to unreachable, since the
   scrollbar is hidden and the chips themselves take the drag". It is replaced
   here by a pan that moves every row at once, which is what you actually want:
   a cluster shifts as a cluster, not a row at a time.
   
   Nothing new lands on the spine. A row's indent grows its padding on the
   INWARD side, so its chips overflow OUTWARD - right lane to the right, left
   lane to the left - away from the spine and the hour labels, and into the
   viewport edge where `#timeline` clips them. */
.slot-row { overflow: visible; }
.timeline { overflow-x: hidden; }

/* AND THE CHIPS WERE NEVER RUNNING OFF THE EDGE - they were being SQUEEZED,
   which is why sliding them revealed nothing at first. `max-width: min(132px,
   100%)` clamps a chip to what is left of its row after the indent, so the
   third dose in a cluster came out 75px wide on a 360px phone. It was all on
   screen. It was just too narrow to read: "Hydrochlorothiazide" in 75px is not
   a medicine, it is a hint.
   
   That silently undoes the thing v0.12.1 and v0.13.0 were for. Stepping a chip
   sideways so its NAME is not buried, and then narrowing it until the name does
   not fit, buries the name a different way.
   
   So a chip keeps a readable width and overflows the lane instead. Overflow is
   recoverable - slide the lanes and it is there; a chip narrowed to a sliver is
   not recoverable by any gesture at all. */
.slot-row .chip { max-width: 132px; }

/* THE RIBBON SCALES; ITS LABELS DO NOT.
   
   Measured at the 0.4 floor: an hour label renders at 4.8px. v0.13.0 and again
   v0.19.0 settled that a label which cannot be read whole should not be drawn -
   and 4.8px cannot be read at all, so leaving it would ship the same defect a
   third way.
   
   Counter-scaling beats hiding here. The whole point of zooming out is to see
   where the day's clusters are, and that is only useful if you can tell WHEN.
   At the 0.4 floor the hour rules are still 44px apart, so a 12px label has
   room. The now line's time label already works this way, by being a child of a
   counter-scaled line.
   
   These sit at the END of the stylesheet on purpose: the panned rule above also
   writes `transform`, and v0.13.0 lost an afternoon to a floor that a LATER
   rule in this same file quietly overrode. Later wins, so later is where the
   combined transforms have to live. */
/* scaleY ONLY. The labels are CHILDREN of the full-width elements above, so
   those elements' `scaleX(1 / zoom)` has already cancelled the ribbon's scale
   horizontally - the glyphs are at their natural width and squashed only
   vertically. Counter-scaling both axes here stretched the letters wide enough
   to read "11 PM" as "1 1  P M", which a screenshot showed and the height
   measurement alone did not: the height came back correct while the width was
   two and a half times over.
   
   So: the full-width furniture is counter-scaled ACROSS, its labels are
   counter-scaled DOWN, and between them the text lands at natural size. */
.timeline-inner[data-zoomed] .hour-label {
  transform: translateX(-50%) scaleY(calc(1 / var(--zoom, 1)));
}
/* THE PAN IS MULTIPLIED BY THE ZOOM, and only here. Steve: "the time offsets
   from the center grid line when you drag the screen to see offscreen items ...
   It happens when you zoom then drag."

   The label lives INSIDE `.hour`, which carries `scaleX(1 / zoom)` - so a
   translation written here is divided by the zoom on its way to the screen,
   while the spine's identical `translateX(var(--pan-x))` is multiplied by it.
   Measured at zoom 1.74 with a pan of -80px: the spine moved 139 screen pixels
   and the label moved 80, leaving the time 59px off the line it labels.

   Multiplying by `--zoom` puts the translation into the parent's units, so both
   land on the same pixel. At zoom 1 the two are the same number, which is why
   this was invisible until a zoom and a drag happened together. */
.timeline-inner[data-zoomed][data-panned] .hour-label {
  transform:
    translateX(calc(-50% + var(--pan-x, 0px) * var(--zoom, 1)))
    scaleY(calc(1 / var(--zoom, 1)));
}
.timeline-inner[data-zoomed] .now-line span {
  transform: scaleY(calc(1 / var(--zoom, 1)));
}
/* THE MIDNIGHT PILL KEEPS ITS OWN CENTRING. It is `left: 50%` plus
   `translateX(-50%)`, and a rule that writes `transform` REPLACES that - so
   zooming dropped the centring and the date sat half its own width to the right
   of the spine. Measured at 1.74x: 39px off, at rest, with no drag involved.
   Third time a new rule has quietly overridden an old one in this stylesheet
   (v0.13.0's min-width floor, v0.23.0's `top`), and the same answer each time:
   read the computed value, and restate what the base rule was doing.
   `.now-line span` is not affected - it is pinned to the RIGHT edge and has no
   translate to lose. */
.timeline-inner[data-zoomed] .day-break-label {
  transform: translateX(-50%) scaleY(calc(1 / var(--zoom, 1)));
}
/* Zoomed AND panned: `* var(--zoom)` for the same reason as the hour label -
   `.day-break` carries `scaleX(1 / zoom)`, so a translation written here is
   divided by the zoom before it reaches the screen. */
.timeline-inner[data-zoomed][data-panned] .day-break-label {
  transform:
    translateX(calc(-50% + var(--pan-x, 0px) * var(--zoom, 1)))
    scaleY(calc(1 / var(--zoom, 1)));
}

/* --- v0.21.0: rewind and redo -------------------------------------------- */

/* BOTTOM LEFT, floating, and not in the header band. The band was measured
   before this was designed rather than after it failed: at 360px it runs
   80 + 173 + 80, and two arrows in the left column drive the right column to
   its `min-content` floor of 37px, which slides the date 42px off the spine.
   That is the property v0.13.0 spent four attempts nailing down and it is not
   being spent on a control that has somewhere else to live.

   Fixed rather than absolute: the ribbon scrolls, and an arrow that scrolls
   away is an arrow you have to go and find. `env(safe-area-inset-*)` because
   the installed PWA runs under `viewport-fit=cover`, so the home indicator
   sits exactly where these otherwise would. */
.undo-bar {
  position: fixed; z-index: 900;
  left: calc(10px + env(safe-area-inset-left, 0px));
  bottom: calc(10px + env(safe-area-inset-bottom, 0px));
  display: flex; gap: 8px;
}
/* `.ask-arrow` rides these rules rather than wearing the `.undo-arrow` class
   (v0.30.0): the two look alike on purpose, but "the arrows" has to keep
   meaning the two that change the record. A test asserts there are exactly two
   of those, and a third wearing their class would have quietly joined them. */
.undo-arrow, .ask-arrow {
  /* 25% smaller than the 44px they shipped at, per Steve. 33px is still a
     comfortable target and they sit over the ribbon, so every pixel they give
     back is a pixel of timeline. */
  width: 33px; height: 33px; border: 0; border-radius: 999px;
  font: inherit; font-size: 17px; line-height: 1;
  background: #7a5af8; color: #fff; cursor: pointer;
  box-shadow: 0 2px 10px rgba(122, 90, 248, 0.38);
  display: flex; align-items: center; justify-content: center;
}
.undo-arrow:hover:not(:disabled), .ask-arrow:hover { background: #6a48f0; }
.undo-arrow:active:not(:disabled), .ask-arrow:active { transform: translateY(1px); }
.undo-arrow:focus-visible, .ask-arrow:focus-visible { outline: 2px solid #7a5af8; outline-offset: 3px; }
/* Dimmed, not hidden. A control that comes and goes teaches you to hunt for
   it; one that is visibly unavailable tells you there is nothing to undo,
   which is the answer to the question you were about to ask. */
.undo-arrow:disabled {
  background: #d8d5e6; color: #8d8aa0; cursor: default;
  box-shadow: none;
}

/* The sentence the confirm dialog leads with. It is the whole content of the
   dialog, so it gets room to be a sentence rather than a label. */
#undo-text { margin: 0 0 6px; }

/* --- v0.22.0: one band, not two ------------------------------------------- */

/* THE APP BAR IS GONE. It held the patient's name and the avatar and nothing
   else, and both of those now live in the date band - so the ribbon gets a
   whole row of height back, which is the only thing up here anybody is short
   of. `.app-bar` and `.patient` rules above are dead markup now; they stay
   only because the gate screen still uses `.linkish` and `.small`.

   Both side columns are now TWO ROWS: `+` on the left, date over "go to now"
   in the middle, avatar over the name on the right. The columns are still
   `flex: 1 1 0` with a `min-content` floor, so the date still lands on the
   spine - that property is asserted and has been since v0.9.0. */
.top-centre { gap: 2px; }
.top-centre .now-link { padding: 0; }
.top-centre .now-link button { font-size: 13px; padding: 0; }

/* The right column stacks, and the bell sits beside the avatar rather than
   above it - it comes and goes, and a control that appears may not shift the
   one that is always there (v0.13.0). */
.top-right { align-items: flex-start; }
.top-right .account {
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  min-width: 0; max-width: 100%;
}

/* The name wears exactly what "now" used to wear, per Steve, and it is still
   the button that opens the patient dialog - so the name you are reading is
   the one you press to change it. */
.top-right .patient-btn {
  display: block; text-align: center; max-width: 100%;
  padding: 0; font-size: 13px; line-height: 1.2;
}
/* A LONG NAME MAY NOT WIDEN THE COLUMN. The side columns are `flex: 1 1 0`
   with a `min-content` floor, which keeps them equal - and equal is what puts
   the date on the spine. A name that sized its own column raises that floor
   past the fair share, the right column wins, and the date slides by half the
   difference. Measured at 13ch: the date came out 28px off the spine at 360px,
   against a test that allows 6.

   So the cap is a MEASURED number, not a guess. At 360px the bar has 340px of
   content, the date needs 173, and the two sides share the remaining 167 -
   83px each. 76px leaves room for the gap and holds at 390 and 412 as well.
   Names give up characters; the date does not. */
#patient-name {
  display: block; max-width: 76px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Wider screens have the room, so the name takes it. 520px is where the fair
   share passes 165px. */
@media (min-width: 520px) {
  #patient-name { max-width: 160px; }
}
.top-right .patient-btn[disabled] { opacity: 0.55; cursor: default; }
.top-right .patient-btn:hover:not([disabled]) #patient-name { text-decoration: underline; }

/* The bell is the only thing left that comes and goes up here, so it keeps its
   own fixed slot beside the avatar whether or not it is showing. */
.top-right .mute-chime { align-self: center; }

/* The phone moved into the menu, under the email. */
.account-who #patient-phone { display: block; }

/* --- v0.24.0: Add back where it was, the bell beside it ------------------ */

/* The column holds two things at two heights, so it is a positioning context
   that stretches to the band rather than a stack. Add sits where it always
   sat - vertically centred at the left - and the bell is placed beside it. */
.top-bar .toolbar {
  position: relative; align-self: stretch;
  flex-direction: row; align-items: center; justify-content: flex-start;
  gap: 0;
}

/* ADD IS CENTRED AGAIN, at 80% of the size it was before v0.23.0. Steve: "The
   plus should be like it was prior, only maybe 20% smaller than its original
   size." v0.23.0 shrank it to 36px AND moved it to the top of the band to make
   room for a bell stacked underneath; the size was about right and the
   position was the complaint. 46 x 0.8 = 37. */
.top-bar .toolbar .fab {
  width: 37px; height: 37px; min-width: 37px; font-size: 22px;
}

/* BESIDE Add, not under it, and on "go to now"'s line. Steve: "the bell should
   be slightly offset to the right horizontally from that plus sign and
   centered on the center of Go to Now text."

   Out of the flow, for the reason v0.23.0 found and which has not changed: the
   bell comes and goes with whether anything is overdue, and in the flow its
   appearing grew the band and jumped the whole ribbon. It costs no height.

   `top: auto` explicitly - the base `.mute-chime` rule sets `top: 50%`, and a
   box with both `top` and `bottom` resolves by TOP. v0.23.0 lost an attempt to
   exactly this and wrote it down; it would have lost another one here.

   `left` and `bottom` are MEASURED: 40px clears Add's 37px with 3px to spare,
   so the two never share a hit box even though they overlap vertically, and
   2px puts the bell's centre on "go to now"'s. A test asserts both. */
.top-bar .toolbar .mute-chime {
  position: absolute; top: auto; bottom: 2px; left: 40px;
  transform: none;
  width: 30px; height: 30px; padding: 0; font-size: 20px;
  display: flex; align-items: center; justify-content: center;
}
.top-bar .toolbar .mute-chime:active { transform: scale(0.92); }

/* The name under the avatar, one point down, per Steve. The 76px cap that
   keeps it from pushing the date off the spine is unchanged and still
   measured against the date's width, not the name's - a smaller font just
   means more of the name fits inside it. */
.top-right .patient-btn { font-size: 12px; }

/* --- v0.25.0: the bell blinks with the chips ------------------------------ */

/* Steve: "when the bell is ringing also blink in unison with the flashing chips
   tempo."

   IN UNISON means two things, and CSS can only give one of them. Same tempo is
   `--overdue-blink` above, shared. Same PHASE is not something CSS can express:
   two animations started at different moments run at the same rate forever out
   of step, and the bell is a permanent element while the chips are rebuilt on
   every paint. `paintChime` copies a blinking chip's `currentTime` onto the
   bell's, which is what actually puts them on the same beat - and is a thing a
   test can read back.

   `steps(1, end)` and not a fade, because that is what the chips do: a blink,
   hard on and hard off. A pulse next to a blink reads as two different alarms.

   ONLY WHILE IT IS ACTUALLY RINGING. Muting stops the sound, and now stops the
   bell blinking too - the chips keep going, because what is overdue has not
   changed. That gives mute a visible effect on the control you pressed, and the
   bell stays put and dimmed so there is still a way back to sound. */
@keyframes bell-flash {
  0%, 100% { background: transparent; }
  50%      { background: #f3b4ac; }
}
.mute-chime.is-ringing {
  animation: bell-flash var(--overdue-blink) steps(1, end) infinite;
}

/* The name under the avatar, five pixels down, per Steve. Its own margin rather
   than the column's gap: the gap also sets the space under the AVATAR, and only
   the name was asked to move. */
.top-right .patient-btn { margin-top: 5px; }

/* --- v0.27.0: reading a label with the camera -----------------------------
   The panel sits ABOVE the form rather than beside it, because at 360px there
   is no beside. The effect Steve asked for is the same: what the AI read stays
   on screen, unedited, while the form fills - so there is always something to
   check the fields against. */
.label-camera { margin: 4px 0 12px; }
.label-camera #label-status:empty { display: none; }
.label-camera #label-status { margin: 8px 0 0; }

.label-panel {
  border: 1.5px solid var(--line); border-radius: 12px;
  background: var(--field, #f2f4f6);
  padding: 12px; margin: 0 0 16px;
}
.label-panel-head { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
.label-panel h3 { margin: 0; font-size: 15px; }

/* The caution Steve asked for, stated before the readings rather than under
   them: it is the frame you read them in, not a footnote to them. */
.label-verify {
  margin: 8px 0 12px; font-size: 13.5px; line-height: 1.45;
  color: #8a2d1d;
}
@media (prefers-color-scheme: dark) { .label-verify { color: #ffb4a2; } }

.label-rows { margin: 0; display: grid; gap: 10px; }
.label-row { display: grid; gap: 4px; }
.label-row-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.label-row dt { font-size: 12px; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); }
/* READ-ONLY, and it has to LOOK read-only, or the panel reads as a second form
   and the question becomes which of the two gets saved. No field chrome. */
.label-row dd { margin: 0; font-weight: 600; overflow-wrap: anywhere; }
.label-row .label-use {
  flex: 0 0 auto; padding: 4px 10px; font-size: 13px; border-radius: 8px;
  border: 1.5px solid var(--line); background: var(--surface); color: inherit;
  cursor: pointer;
}
.label-row .label-use:hover { border-color: var(--focus); }
.label-row .label-use[data-used="true"] { color: var(--muted); border-style: dashed; }

/* "have the confirm autofill information button flash red" - Steve.
   It flashes because it is the one control here that fills EVERY field at
   once, which is the moment a misreading gets furthest before anyone looks at
   it. It flashes on its own tempo, not `--overdue-blink`: that beat means a
   dose is late, and two unrelated things sharing a beat start to look related. */
:root { --verify-flash: 1.6s; }
.label-use-all {
  display: block; width: 100%; margin-top: 14px;
  padding: 11px 12px; border-radius: 10px; font: inherit; font-weight: 600;
  cursor: pointer;
  border: 2px solid #c0392b; background: var(--surface); color: #a5281a;
  animation: verify-flash var(--verify-flash) ease-in-out infinite;
}
@keyframes verify-flash {
  0%, 100% { border-color: #c0392b; background: var(--surface); }
  50% { border-color: #f0705f; background: rgba(192, 57, 43, .12); }
}
@media (prefers-color-scheme: dark) { .label-use-all { color: #ff8d78; } }
/* A flashing red control is exactly what somebody who turned motion off has
   turned it off to avoid. The red stays; only the movement goes. */
@media (prefers-reduced-motion: reduce) {
  .label-use-all { animation: none; background: rgba(192, 57, 43, .12); }
}

/* --- v0.30.0: ask about the record --------------------------------------- */

/* The microphone sits in the undo bar, after a WIDER gap than the 8px between
   the two arrows. Same size and shape - the row is one cluster under the thumb
   and an odd-sized third button would break the line - but a different job:
   undo and redo change the record, this one only reads it. The gap is what
   says so, and 16px is the smallest that reads as a separation rather than as
   a sloppy 8. Measured: the bar goes 74px -> 123px, right edge 133px of a
   360px screen, overlapping nothing at 360, 390 or 412. */
.ask-arrow { margin-left: 8px; font-size: 17px; }

/* Listening is a STATE THAT COSTS SOMETHING - a microphone is open - so it is
   never merely implied by a panel being up. It pulses, on its own timing
   rather than sharing the overdue blink, because the two mean nothing alike
   and v0.25.0 spent a release on the one case where a shared beat is right. */
.ask-arrow.is-listening { background: #c0392b; animation: ask-pulse 1.1s ease-in-out infinite; }
@keyframes ask-pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(192, 57, 43, 0.55); }
  50%      { box-shadow: 0 0 0 7px rgba(192, 57, 43, 0); }
}
@media (prefers-reduced-motion: reduce) { .ask-arrow.is-listening { animation: none; } }

/* Above the bar, not over it. Fixed for the same reason the arrows are: the
   ribbon scrolls and an answer that scrolls away is an answer you have to go
   and find. It is capped at 46vh so the timeline being asked about is still
   visible - that is the whole reason this is not a dialog. */
.ask-panel {
  position: fixed; z-index: 901;
  left: calc(10px + env(safe-area-inset-left, 0px));
  right: calc(10px + env(safe-area-inset-right, 0px));
  bottom: calc(53px + env(safe-area-inset-bottom, 0px));
  max-width: 460px;
  display: flex; flex-direction: column; gap: 8px;
  max-height: 46vh;
  padding: 12px;
  border: 1.5px solid var(--line); border-radius: var(--radius);
  background: var(--surface); color: var(--ink);
  box-shadow: 0 6px 28px rgba(0, 0, 0, 0.22);
}
.ask-panel[hidden] { display: none; }
.ask-head { display: flex; align-items: center; gap: 8px; }
.ask-head h2 { flex: 1 1 auto; min-width: 0; }
.ask-close[hidden] { display: none; }
.ask-head h2 { margin: 0; font-size: 15px; }
.ask-close {
  flex: 0 0 auto; width: 30px; height: 30px; border-radius: 999px;
  border: 1.5px solid var(--line); background: var(--surface); color: inherit;
  font: inherit; font-size: 18px; line-height: 1; cursor: pointer;
}
.ask-close:hover { border-color: var(--focus); }

.ask-log { overflow-y: auto; display: grid; gap: 8px; font-size: 14px; line-height: 1.45; }
.ask-turn { margin: 0; overflow-wrap: anywhere; }
/* The question is quoted back so an answer is never read without the thing it
   answers - speech recognition mishears, and "no, it heard me wrong" has to be
   possible to notice. */
.ask-turn.is-you { color: var(--muted); }
.ask-turn.is-you::before { content: "You: "; font-weight: 600; }
.ask-turn.is-reply { font-weight: 500; }

.ask-status:empty { display: none; }
.ask-status { margin: 0; }

.ask-form { display: flex; gap: 8px; align-items: center; }
.ask-mic {
  flex: 0 0 auto; width: 36px; height: 36px; border-radius: 999px; border: 0;
  background: #7a5af8; color: #fff; font: inherit; font-size: 16px; cursor: pointer;
}
.ask-mic.is-listening { background: #c0392b; }
.ask-mic[hidden] { display: none; }
.ask-text {
  flex: 1 1 auto; min-width: 0;
  padding: 8px 10px; font: inherit; font-size: 15px;
  border: 1.5px solid var(--line); border-radius: 10px;
  background: var(--surface); color: inherit;
}
.ask-text:focus-visible { outline: 2px solid var(--focus); outline-offset: 1px; }
.ask-send {
  flex: 0 0 auto; padding: 8px 14px; font: inherit; font-size: 15px; font-weight: 600;
  border: 0; border-radius: 10px; background: var(--deep); color: var(--deep-ink);
  cursor: pointer;
}
.ask-send:disabled { opacity: .55; cursor: default; }
.ask-foot { margin: 0; }

/* A label that is there for a screen reader and not for the eye. */
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
