/* Baby Brain — app styles */
/* Warm paper, dark green, a serif for headings: the same palette and type as
   the marketing site, converted from its hexes so the two genuinely match
   rather than merely resembling each other. */

:root {
  color-scheme: light dark;

  /* Event colours: one lightness, one chroma, eight evenly spaced hues.

     Holding BOTH constant is the whole point. The previous set fixed lightness
     at 0.80 and then took whatever chroma sRGB allowed per hue, which handed
     yellow 0.169 and blue 0.100 — so nappy shouted while feed whispered, and
     the set never settled. Chroma varying by 70% across a categorical palette
     is what "unharmonious" looks like.

     0.73 / 0.138 is the computed optimum: the highest chroma that every one of
     these hues can actually reach, so nothing is louder than its neighbours and
     nothing is washed out. Every swatch clears 6.4:1 against --colour-on-event.

     If you add a ninth event type, give it a hue and keep this L and C —
     except in the one direction where uniform L is actively wrong. See nappy. */
  --colour-temperature: oklch(0.73 0.138 35);
  /* Nappy is the deliberate exception, and yellow is why.

     Yellow's natural lightness is high, so pinning it to the shared 0.73 does
     not produce a darker yellow, it produces ochre — which for this particular
     label read as exactly the thing nobody wants to be reminded of. Moving the
     hue instead does not help: 90 and 100 go olive, and 55 and 60 land on top
     of temperature at 35. Lightness is the only axis that fixes it.

     So 0.85, high enough for yellow to read as yellow. 10.4:1 against
     --colour-on-event, and the chroma is nudged with it because more is
     reachable up here. Uniform chroma was the thing that actually fixed the
     old palette — one hue sitting lighter is not what "unharmonious" was. */
  --colour-nappy: oklch(0.85 0.145 88);
  --colour-milestone: oklch(0.73 0.138 120);
  --colour-weight: oklch(0.73 0.138 175);
  --colour-feed: oklch(0.73 0.138 230);
  /* Bottle and breast are a pair: adjacent hues, same weight, so they read as
     two kinds of one thing rather than two unrelated buttons. */
  --colour-feed-breast: oklch(0.73 0.138 265);
  --colour-sleep: oklch(0.73 0.138 300);
  --colour-medicine: oklch(0.73 0.138 350);

  /* Wake replaces Sleep in the grid rather than sitting beside it, so lifting
     the lightness marks the state change without breaking the row's uniformity
     — the two are never on screen together. */
  --colour-wake: oklch(0.83 0.138 300);

  --colour-on-event: oklch(0.243 0.012 67);

  /* Light ink for the dark pill inside a timer banner. Fixed rather than
     --colour-bg, which flips: the pill is --colour-on-event, also fixed and also
     dark, so pairing it with anything theme-dependent is what put that label at
     1.09:1 and invisible in dark mode. Both ends of this pair stay put. */
  --colour-banner-text: oklch(0.96 0.008 75);

  /* Scrim behind a modal. Fixed, not derived from --colour-text: a scrim's job
     is to dim, and built from the text colour it inverted with the theme and
     *lightened* the backdrop in dark mode. */
  --colour-scrim: oklch(0.15 0.008 67 / 0.62);

  /* Sync is mid-flight. Amber because it means "wait", not "nappy".
     Darker here than in the dark block: a mid amber is only 2.86:1 against the
     near-white header, and this is a 10px dot, so it needs the 3:1 a graphical
     object has to clear. It flips like the other semantic colours. */
  --colour-pending: oklch(0.62 0.13 75);

  /* Neutrals, converted from the marketing site's hexes. */
  --colour-bg: oklch(0.98 0.006 75);          /* #fbf8f4 */
  --colour-surface: oklch(0.995 0.005 78);    /* #fffdfa */
  --colour-text: oklch(0.243 0.012 67);       /* #241f1a */
  --colour-muted: oklch(0.502 0.018 67);      /* #6b6259 */
  --colour-danger: oklch(0.503 0.12 46);      /* #9a4a1f */
  --colour-success: oklch(0.454 0.062 160);
  --colour-border: oklch(0.904 0.018 78);     /* #e6ded2 */
  --colour-primary: oklch(0.454 0.062 160);   /* #35614a */
  /* For anything drawn on an inverted surface. The toast paints itself with
     --colour-text on --colour-bg, so the page's own accent lands dark-on-dark
     there; this is the same green picked for the opposite background. */
  --colour-primary-inverse: oklch(0.773 0.074 159);
  /* Text on a --colour-primary fill. It has to flip with the theme, because
     the primary does: white reads at 7.09:1 on the light theme's dark green
     and 1.99:1 on the dark theme's light green, which is where four components
     had been failing. */
  --colour-on-primary: oklch(1 0 0);

  /* Spacing scale — varied rhythm */
  --space-xs: 4px;
  --space-sm: 8px;
  --space-md: 16px;
  --space-lg: 24px;
  --space-xl: 40px;
  --space-2xl: 64px;

  /* Type scale — fluid sizing */
  --text-xs: clamp(0.7rem, 0.65rem + 0.25vw, 0.8rem);
  --text-sm: clamp(0.8rem, 0.75rem + 0.25vw, 0.9rem);
  --text-base: clamp(1rem, 0.95rem + 0.25vw, 1.05rem);
  --text-lg: clamp(1rem, 0.9rem + 0.5vw, 1.2rem);
  --text-xl: clamp(1.25rem, 1.1rem + 0.75vw, 1.6rem);
  --text-2xl: clamp(1.6rem, 1.3rem + 1.5vw, 2.2rem);

  /* Radii. Tighter than before, to match the site — the old 14/20 read as a
     kids' app, which is a different thing from an app about a kid. */
  --radius-sm: 4px;
  --radius-md: 8px;
  --radius-lg: 12px;
  --radius-full: 999px;

  /* Shadows — soft, warm-tinted */
  --shadow-sm: 0 1px 2px oklch(0.3 0.02 55 / 0.06);
  --shadow-md: 0 2px 8px oklch(0.3 0.02 55 / 0.08), 0 1px 2px oklch(0.3 0.02 55 / 0.04);
  --shadow-lg: 0 4px 16px oklch(0.3 0.02 55 / 0.1), 0 2px 4px oklch(0.3 0.02 55 / 0.06);

  /* Type. SF Pro Rounded for the interface, New York for the few headings —
     the same pairing as the site, so the two still match.

     `ui-rounded` and `ui-serif` are the generic families that get these on
     Apple platforms without naming a file, so this still loads no webfont and
     makes no third-party request. They are WebKit-only, though: on Android
     `ui-rounded` is not recognised, 'SF Pro Rounded' is not installed, and
     -apple-system means nothing, so it lands on the platform sans — Roboto.
     That is the intended fallback rather than an oversight. The rounded look
     is an iOS nicety; the layout must not depend on it. */
  --font-sans: ui-rounded, "SF Pro Rounded", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --font-display: ui-serif, "New York", "Iowan Old Style", "Palatino Linotype", Palatino, Georgia, serif;

  /* Easing — exponential (not generic ease-in-out) */
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  --ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
}

/* Dark mode — tinted, not pure gray */
@media (prefers-color-scheme: dark) {
  :root {
    /* Warm near-black, from the marketing site. The old dark mode was tinted
       blue-violet (hue 270) against a warm light mode, so the two themes
       looked like different products. */
    --colour-bg: oklch(0.207 0.008 67);       /* #1a1714 */
    --colour-surface: oklch(0.238 0.011 73);  /* #221e19 */
    --colour-text: oklch(0.946 0.013 75);     /* #f2ece4 */
    --colour-muted: oklch(0.707 0.02 75);     /* #a89f93 */
    --colour-border: oklch(0.301 0.015 71);   /* #332d26 */
    --colour-primary: oklch(0.773 0.074 159); /* #8cc4a4 */
    --colour-primary-inverse: oklch(0.454 0.062 160);
    --colour-on-primary: oklch(0.243 0.012 67);
    /* These are semantic colours on a surface that flips, so they have to flip
       too — the same fault --colour-on-primary had. Left on their light-mode
       values they were dark ink on a dark ground: "Discard feed" sat at 2.66:1
       and the synced dot was a dark green circle on near-black. --colour-pending
       flips too — it was left alone here on the strength of a number measured
       only against this surface, and failed at 2.86:1 on the light one. */
    --colour-danger: oklch(0.70 0.13 46);     /* 5.93:1 on --colour-surface */
    --colour-success: oklch(0.773 0.074 159); /* 8.33:1, and matches the primary */
    --colour-pending: oklch(0.68 0.11 75);    /* 5.70:1 — the light value is too dark here */
    --shadow-sm: 0 1px 2px oklch(0.05 0.008 67 / 0.3);
    --shadow-md: 0 2px 8px oklch(0.05 0.008 67 / 0.35);
    --shadow-lg: 0 4px 16px oklch(0.05 0.008 67 / 0.4);
  }

  .auth-form input {
    background: var(--colour-surface);
    color: var(--colour-text);
  }

  .modal { background: var(--colour-surface); }
  .modal-btn { background: var(--colour-surface); color: var(--colour-text); }
  .modal-btn:active, .modal-btn.active { background: var(--colour-border); border-color: var(--colour-feed); }

  .amount-input-group button { background: var(--colour-surface); color: var(--colour-text); }
  .amount-input-group button:active { background: var(--colour-border); }
  .amount-input-group input { background: var(--colour-surface); color: var(--colour-text); }


  /* Dark mode timeline icon backgrounds — higher opacity for visibility */
  .timeline-item.type-nappy .icon { background: color-mix(in oklab, var(--colour-nappy) 18%, transparent); }
  .timeline-item.type-feed .icon { background: color-mix(in oklab, var(--colour-feed) 18%, transparent); }
  .timeline-item.type-sleep .icon { background: color-mix(in oklab, var(--colour-sleep) 18%, transparent); }
  .timeline-item.type-medicine .icon { background: color-mix(in oklab, var(--colour-medicine) 18%, transparent); }
  .timeline-item.type-weight .icon { background: color-mix(in oklab, var(--colour-weight) 18%, transparent); }
  .timeline-item.type-temperature .icon { background: color-mix(in oklab, var(--colour-temperature) 18%, transparent); }
  .timeline-item.type-milestone .icon { background: color-mix(in oklab, var(--colour-milestone) 18%, transparent); }

  .timeline-day-count { background: var(--colour-border); }

  .timeline-item.active-event {
    background: color-mix(in oklch, var(--colour-primary) 8%, var(--colour-surface));
  }
}

* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

html {
  font-size: 16px;
  -webkit-text-size-adjust: 100%;
  touch-action: manipulation;
  overscroll-behavior-y: contain;
  overflow-x: hidden;
}

/* Prevent iOS zoom on focus — inputs must be >= 16px */
input, select, textarea { font-size: 16px; }

body {
  font-family: var(--font-sans);
  background: var(--colour-bg);
  color: var(--colour-text);
  min-height: 100vh;
  min-height: 100dvh;
  overflow-x: hidden;
  -webkit-font-smoothing: antialiased;
  line-height: 1.5;
}

/* Header — clean, confident, left-aligned title */
.header {
  background: var(--colour-surface);
  padding: 14px var(--space-md);
  display: flex;
  align-items: center;
  justify-content: space-between;
  border-bottom: 1px solid var(--colour-border);
  position: sticky;
  top: var(--banner-offset, 0px);
  z-index: 100;
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  background: color-mix(in oklch, var(--colour-surface) 85%, transparent);
}

.header h1 {
  font-family: var(--font-display);
  font-size: var(--text-lg);
  font-weight: 600;
  letter-spacing: -0.01em;
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.header-actions {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  /* Don't shrink: the title is flex:1, and word labels are wider than the
     emoji they replaced, so without this a long title could squeeze the nav. */
  flex: none;
}

/* Words rather than emoji. An emoji renders differently on every platform and
   means whatever the reader guesses; "Trends" and "Settings" don't. Sized for a
   44px tap target, since text is shorter than an emoji glyph was. */
.header-btn {
  background: none;
  border: none;
  cursor: pointer;
  font-family: inherit;
  font-size: var(--text-sm);
  font-weight: 600;
  padding: var(--space-sm) var(--space-sm);
  min-height: 44px;
  border-radius: var(--radius-sm);
  opacity: 0.6;
  transition: all 0.2s var(--ease-out);
  color: var(--colour-text);
}

.header-btn:active { opacity: 1; background: var(--colour-bg); }
.header-btn.active { opacity: 1; color: var(--colour-primary); }

/* The title, when it doubles as the way back to the dashboard. Inherits the
   h1's type so it reads as the heading it is, not as a control bolted on. */
.brand-btn {
  font: inherit;
  color: inherit;
  letter-spacing: inherit;
  background: none;
  border: none;
  padding: 0;
  margin: 0;
  cursor: pointer;
}

.brand-btn:active { opacity: 0.6; }

/* The sync dot: a 10px circle with a 44px tap target around it.
   
   It used to do that with width:10px, padding:18px and
   background-clip:content-box — but everything here is border-box, so 36px of
   padding swallowed the 10px width, leaving a content box 0px wide and a dot
   that painted nothing at all. It had been invisible for as long as the rule
   existed. Drawing the circle on a pseudo-element keeps the tap target and the
   visual independent, so neither can eat the other. The negative margin pulls
   the layout box back to 10px so the button doesn't reserve 44px in the nav. */
.sync-indicator {
  width: 44px;
  height: 44px;
  margin: -17px;
  padding: 0;
  display: grid;
  place-items: center;
  cursor: pointer;
  flex-shrink: 0;
  border: none;
  background: none;
  color: var(--colour-muted);
  -webkit-appearance: none;
  appearance: none;
}

.sync-indicator::before {
  content: '';
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background-color: currentColor;
  transition: background-color 0.3s var(--ease-out);
}

.sync-indicator.synced { color: var(--colour-success); }
.sync-indicator.pending { color: var(--colour-pending); animation: pulse 2s var(--ease-in-out) infinite; }
.sync-indicator.offline { color: var(--colour-danger); }
/* Not pulsing, unlike .pending. The pulse says "working on it", and the whole
   point of this state is that nothing is happening until someone acts. */
.sync-indicator.blocked { color: var(--colour-danger); }

@keyframes pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.3; }
}

/* Auth screen — welcoming, centered, warm */
.auth-screen {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  min-height: 100vh;
  min-height: 100dvh;
  padding: var(--space-lg);
}

.auth-screen h1 {
  font-family: var(--font-display);
  font-size: var(--text-2xl);
  font-weight: 600;
  letter-spacing: -0.01em;
  margin-bottom: var(--space-sm);
}

.auth-screen p {
  color: var(--colour-muted);
  margin-bottom: var(--space-xl);
  text-align: center;
  font-size: var(--text-base);
}

.auth-form {
  width: 100%;
  max-width: 320px;
}

.auth-form input {
  width: 100%;
  padding: 14px var(--space-md);
  border: 2px solid var(--colour-border);
  border-radius: var(--radius-md);
  font-size: var(--text-base);
  font-family: inherit;
  margin-bottom: var(--space-sm);
  outline: none;
  transition: border-color 0.2s var(--ease-out), box-shadow 0.2s var(--ease-out);
}

.auth-form input:focus {
  border-color: var(--colour-primary);
  box-shadow: 0 0 0 3px color-mix(in oklab, var(--colour-primary) 14%, transparent);
}

.auth-form .error {
  color: var(--colour-danger);
  font-size: var(--text-sm);
  margin-bottom: var(--space-sm);
  min-height: 1.25rem;
  font-weight: 500;
}

.btn-primary {
  width: 100%;
  padding: 14px;
  background: var(--colour-primary);
  color: var(--colour-on-primary);
  border: none;
  border-radius: var(--radius-md);
  font-size: var(--text-base);
  font-weight: 700;
  font-family: inherit;
  cursor: pointer;
  transition: transform 0.15s var(--ease-out), box-shadow 0.15s var(--ease-out);
}

.btn-primary:active { transform: scale(0.97); }
.btn-primary:disabled { opacity: 0.5; cursor: not-allowed; transform: none; }

/* Summary strip — flat, informational, clearly not interactive */
.summary {
  padding: var(--space-md) var(--space-md) var(--space-md);
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-sm);
  border-bottom: 1px solid var(--colour-border);
}

.summary-card {
  background: transparent;
  border-radius: 0;
  padding: var(--space-sm) var(--space-xs);
  border: none;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  text-align: center;
}

.summary-card .icon { font-size: 1.1rem; opacity: 0.7; }
.summary-card .label { color: var(--colour-muted); font-size: var(--text-xs); font-weight: 600; text-transform: uppercase; letter-spacing: 0.04em; }
.summary-card .value { font-weight: 600; font-size: var(--text-sm); color: var(--colour-muted); }

/* Quick-log buttons — the hero section, bold and tactile */
.quick-log {
  padding: var(--space-sm) var(--space-md) var(--space-md);
  margin-top: var(--space-sm);
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 10px;
}

.log-btn {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
  padding: var(--space-md) var(--space-sm);
  border: none;
  border-radius: var(--radius-md);
  font-size: var(--text-sm);
  font-weight: 700;
  font-family: inherit;
  cursor: pointer;
  transition: transform 0.12s var(--ease-out), box-shadow 0.12s var(--ease-out);
  min-height: 68px;
  color: var(--colour-on-event);
  box-shadow: var(--shadow-sm);
  position: relative;
  overflow: hidden;
}

.log-btn::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(to bottom, transparent, oklch(0 0 0 / 0.07));
  pointer-events: none;
}

.log-btn:active { transform: scale(0.94); box-shadow: none; }
.log-btn .emoji { font-size: 1.4rem; }

.log-btn.nappy { background: var(--colour-nappy); }
.log-btn.feed { background: var(--colour-feed); }
.log-btn.feed-breast { background: var(--colour-feed-breast); }
.log-btn.sleep { background: var(--colour-sleep); }
.log-btn.wake { background: var(--colour-wake); }
.log-btn.medicine { background: var(--colour-medicine); }
.log-btn.weight { background: var(--colour-weight); }
.log-btn.temperature { background: var(--colour-temperature); }
.log-btn.milestone { background: var(--colour-milestone); }

/* More events toggle */
.more-toggle {
  text-align: center;
  padding: 0 var(--space-md) var(--space-md);
}

.more-toggle button {
  background: none;
  border: none;
  color: var(--colour-primary);
  font-size: var(--text-sm);
  font-weight: 700;
  font-family: inherit;
  cursor: pointer;
  padding: var(--space-sm) var(--space-md);
  border-radius: var(--radius-full);
  transition: background 0.15s var(--ease-out);
}

.more-toggle button:active { background: color-mix(in oklab, var(--colour-primary) 10%, transparent); }

/* Filter bar — horizontal scroll, pill-shaped */
.filter-bar {
  padding: var(--space-md) var(--space-md) var(--space-md);
  display: flex;
  gap: var(--space-sm);
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}

.filter-bar::-webkit-scrollbar { display: none; }

.filter-chip {
  flex-shrink: 0;
  padding: 6px 14px;
  border: 1.5px solid var(--colour-border);
  border-radius: var(--radius-full);
  background: var(--colour-surface);
  font-size: var(--text-xs);
  font-weight: 700;
  font-family: inherit;
  cursor: pointer;
  color: var(--colour-muted);
  transition: all 0.15s var(--ease-out);
  text-transform: uppercase;
  letter-spacing: 0.03em;
}

.filter-chip.active {
  background: var(--colour-primary);
  border-color: var(--colour-primary);
  color: var(--colour-on-primary);
}

/* Modal — bottom sheet, refined */
.modal-overlay {
  position: fixed;
  inset: 0;
  background: var(--colour-scrim);
  z-index: 260;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
  animation: fadeIn 0.15s var(--ease-out);
}

@keyframes fadeIn {
  from { opacity: 0; }
  to { opacity: 1; }
}

.modal {
  background: var(--colour-surface);
  width: 100%;
  min-width: 0;
  max-width: 480px;
  border-radius: var(--radius-lg) var(--radius-lg) 0 0;
  padding: var(--space-lg) var(--space-md) var(--space-xl);
  animation: slideUp 0.25s var(--ease-out);
  max-height: 90vh;
  max-height: 90dvh;
  overflow-x: hidden;
  overflow-y: auto;
  will-change: transform;
}

@keyframes slideUp {
  from { transform: translateY(100%); }
  to { transform: translateY(0); }
}

.modal h2 {
  font-family: var(--font-display);
  font-size: var(--text-lg);
  font-weight: 600;
  letter-spacing: -0.01em;
  margin-bottom: var(--space-md);
  text-align: center;
}

.modal-options {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
  margin-bottom: var(--space-md);
}

.modal-btn {
  padding: var(--space-md);
  border: 2px solid var(--colour-border);
  border-radius: var(--radius-md);
  background: var(--colour-surface);
  font-size: var(--text-base);
  font-weight: 700;
  font-family: inherit;
  cursor: pointer;
  text-align: center;
  transition: border-color 0.15s var(--ease-out), background 0.15s var(--ease-out), transform 0.1s var(--ease-out);
  color: var(--colour-text);
}

.modal-btn:active { transform: scale(0.96); }

.modal-btn:active, .modal-btn.active {
  border-color: var(--colour-feed);
  background: color-mix(in oklab, var(--colour-feed) 8%, transparent);
}

.modal-btn .emoji { display: block; font-size: 1.5rem; margin-bottom: var(--space-xs); }

/* The "do nothing" escape from a modal, so it is deliberately the quietest
   thing on it. It used to be filled with --colour-bg while the modal is
   --colour-surface — 1.04:1 apart in light and 1.08:1 in dark, an edge nobody
   could see in either theme, so it read as floating text rather than a button.
   No fill at all is honest about being the tertiary action; :active still gives
   it a press. */
.modal-close {
  width: 100%;
  padding: 14px;
  border: none;
  border-radius: var(--radius-md);
  background: none;
  font-size: var(--text-base);
  font-weight: 700;
  font-family: inherit;
  color: var(--colour-muted);
  cursor: pointer;
  transition: background 0.15s var(--ease-out);
}

.modal-close:active { background: var(--colour-border); }

/* A row of modal buttons, side by side and equal width.
   .btn-primary and .modal-close are both width:100% on their own, so a pair of
   them needs a flex parent or they stack — and an unstyled .btn-secondary falls
   back to browser chrome, which is how the Copy button ended up a grey pill
   floating above a full-width Done. */
.modal-actions {
  display: flex;
  gap: var(--space-sm);
  margin-top: var(--space-md);
}

.modal-actions > * {
  flex: 1 1 0;
  min-width: 0;
}

/* The quieter half of that pair. Same metrics as .btn-primary so the two line
   up exactly; muted fill so the primary still reads as the default action. */
.btn-secondary {
  width: 100%;
  padding: 14px;
  border: none;
  border-radius: var(--radius-md);
  background: var(--colour-bg);
  color: var(--colour-muted);
  font-size: var(--text-base);
  font-weight: 700;
  font-family: inherit;
  cursor: pointer;
  transition: background 0.15s var(--ease-out), transform 0.15s var(--ease-out);
}

.btn-secondary:active {
  background: var(--colour-border);
  transform: scale(0.97);
}

/* Form fields in modals */
.modal-field {
  margin-bottom: var(--space-md);
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.modal-field label {
  display: block;
  font-size: var(--text-xs);
  font-weight: 700;
  color: var(--colour-muted);
  margin-bottom: 6px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.modal-field input,
.modal-field textarea,
.modal-field select {
  box-sizing: border-box;
  width: 100%;
  max-width: 100%;
  min-width: 0;
  flex: 1;
  padding: 12px;
  border: 2px solid var(--colour-border);
  border-radius: var(--radius-md);
  font-size: var(--text-base);
  font-family: inherit;
  outline: none;
  background: var(--colour-surface);
  color: var(--colour-text);
  transition: border-color 0.2s var(--ease-out), box-shadow 0.2s var(--ease-out);
}

.modal-field input:focus,
.modal-field textarea:focus,
.modal-field select:focus {
  border-color: var(--colour-primary);
  box-shadow: 0 0 0 3px color-mix(in oklab, var(--colour-primary) 14%, transparent);
}

.modal-field textarea {
  resize: vertical;
  min-height: 60px;
}

.datetime-row {
  display: flex;
  gap: var(--space-sm);
}

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

/* Bottle amount input */
.amount-input-group {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  margin-bottom: var(--space-md);
  justify-content: center;
}

.amount-input-group button {
  width: 48px;
  height: 48px;
  border: 2px solid var(--colour-border);
  border-radius: 50%;
  background: var(--colour-surface);
  font-size: 1.5rem;
  font-family: inherit;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--colour-text);
  transition: transform 0.1s var(--ease-out), background 0.15s var(--ease-out);
}

.amount-input-group button:active { transform: scale(0.92); background: var(--colour-bg); }

.amount-input-group input {
  width: 100px;
  text-align: center;
  font-size: 1.5rem;
  font-weight: 800;
  font-family: inherit;
  border: 2px solid var(--colour-border);
  border-radius: var(--radius-md);
  padding: var(--space-sm);
  outline: none;
  background: var(--colour-surface);
  color: var(--colour-text);
  font-variant-numeric: tabular-nums;
  -moz-appearance: textfield;
  appearance: textfield;
}

.amount-input-group input::-webkit-outer-spin-button,
.amount-input-group input::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}

.amount-input-group input:focus { border-color: var(--colour-feed); box-shadow: 0 0 0 3px color-mix(in oklab, var(--colour-feed) 12%, transparent); }

.amount-label {
  text-align: center;
  color: var(--colour-muted);
  font-size: var(--text-sm);
  font-weight: 600;
  margin-bottom: var(--space-md);
}

/* Action buttons */
.btn-save {
  width: 100%;
  padding: var(--space-md);
  background: var(--colour-feed);
  color: var(--colour-on-event);
  border: none;
  border-radius: var(--radius-md);
  font-size: var(--text-base);
  font-weight: 700;
  font-family: inherit;
  cursor: pointer;
  margin-top: var(--space-sm);
  transition: transform 0.12s var(--ease-out);
}

.btn-save:active { transform: scale(0.97); }

.btn-danger {
  width: 100%;
  padding: 14px;
  background: none;
  color: var(--colour-danger);
  border: 2px solid var(--colour-danger);
  border-radius: var(--radius-md);
  font-size: var(--text-base);
  font-weight: 700;
  font-family: inherit;
  cursor: pointer;
  margin-top: var(--space-sm);
  transition: all 0.15s var(--ease-out);
}

.btn-danger:active { background: var(--colour-danger); color: white; transform: scale(0.97); }

/* Timeline — rich, scannable, color-coded activity list */
.timeline { padding: 0 var(--space-md) var(--space-lg); margin-top: var(--space-sm); }

.timeline-day { margin-bottom: var(--space-lg); }

.timeline-day-header {
  font-size: var(--text-xs);
  font-weight: 800;
  color: var(--colour-muted);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  margin-bottom: var(--space-sm);
  padding-bottom: var(--space-xs);
  display: flex;
  align-items: center;
  gap: var(--space-sm);
}

.timeline-day-header::after {
  content: '';
  flex: 1;
  height: 1px;
  background: var(--colour-border);
}

.timeline-day-count {
  font-size: 0.65rem;
  font-weight: 800;
  background: var(--colour-border);
  color: var(--colour-muted);
  padding: 1px 7px;
  border-radius: var(--radius-full);
  letter-spacing: 0;
  flex-shrink: 0;
}

.timeline-item {
  background: var(--colour-surface);
  border-radius: var(--radius-md);
  padding: 10px 14px 10px 12px;
  margin-bottom: 6px;
  border: 1px solid var(--colour-border);
  display: flex;
  align-items: center;
  gap: 12px;
  cursor: pointer;
  transition: transform 0.1s var(--ease-out), box-shadow 0.1s var(--ease-out);
  contain: layout style;
  overflow: hidden;
  position: relative;
}

.timeline-item:active { transform: scale(0.98); box-shadow: var(--shadow-md); }

/* Color accent bar on left edge — absolute positioned to fill full height */
.timeline-item-accent {
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 4px;
  border-radius: 0 2px 2px 0;
}

.timeline-item.type-nappy .timeline-item-accent { background: var(--colour-nappy); }
.timeline-item.type-feed .timeline-item-accent { background: var(--colour-feed); }
.timeline-item.type-sleep .timeline-item-accent { background: var(--colour-sleep); }
.timeline-item.type-medicine .timeline-item-accent { background: var(--colour-medicine); }
.timeline-item.type-weight .timeline-item-accent { background: var(--colour-weight); }
.timeline-item.type-temperature .timeline-item-accent { background: var(--colour-temperature); }
.timeline-item.type-milestone .timeline-item-accent { background: var(--colour-milestone); }

/* Icon circle with tinted background */
.timeline-item .icon {
  font-size: 1.2rem;
  width: 40px;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  border-radius: var(--radius-sm);
  line-height: 1;
}

/* Dual emoji icon (wet & dirty) — shrink to fit side by side */
.timeline-item .icon.icon-dual {
  font-size: 0.85rem;
  gap: 1px;
  letter-spacing: -1px;
}

.timeline-item.type-nappy .icon { background: color-mix(in oklab, var(--colour-nappy) 12%, transparent); }
.timeline-item.type-feed .icon { background: color-mix(in oklab, var(--colour-feed) 12%, transparent); }
.timeline-item.type-sleep .icon { background: color-mix(in oklab, var(--colour-sleep) 12%, transparent); }
.timeline-item.type-medicine .icon { background: color-mix(in oklab, var(--colour-medicine) 12%, transparent); }
.timeline-item.type-weight .icon { background: color-mix(in oklab, var(--colour-weight) 12%, transparent); }
.timeline-item.type-temperature .icon { background: color-mix(in oklab, var(--colour-temperature) 12%, transparent); }
.timeline-item.type-milestone .icon { background: color-mix(in oklab, var(--colour-milestone) 12%, transparent); }

.timeline-item .details { flex: 1; min-width: 0; }
.timeline-item .details .title { font-weight: 700; font-size: var(--text-sm); line-height: 1.3; }
.timeline-item .details .subtitle {
  color: var(--colour-muted);
  font-size: var(--text-xs);
  line-height: 1.3;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Note indicator — subtle dot next to subtitle */
.timeline-item .note-indicator {
  display: inline;
  color: var(--colour-muted);
  font-size: 0.65rem;
  opacity: 0.6;
  margin-left: 4px;
  vertical-align: middle;
}

/* Time + relative time column */
.timeline-item .time-col {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  flex-shrink: 0;
  gap: 1px;
}

.timeline-item .time {
  color: var(--colour-text);
  font-size: var(--text-sm);
  font-weight: 700;
  flex-shrink: 0;
  font-variant-numeric: tabular-nums;
}

.timeline-item .time-ago {
  color: var(--colour-muted);
  font-size: 0.65rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  opacity: 0.7;
}

/* Edit hint chevron */
.timeline-item .edit-chevron {
  color: var(--colour-muted);
  opacity: 0.3;
  font-size: 0.75rem;
  flex-shrink: 0;
  margin-left: 4px;
}

/* In-progress / active event styling */
.timeline-item.active-event {
  border-color: var(--colour-primary);
  border-width: 1.5px;
  background: color-mix(in oklch, var(--colour-primary) 4%, var(--colour-surface));
}

.timeline-item.active-event .details .subtitle {
  color: var(--colour-primary);
  font-weight: 600;
}

@keyframes active-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.4; }
}

.active-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--colour-primary);
  animation: active-pulse 2s var(--ease-in-out) infinite;
  flex-shrink: 0;
}

.timeline-empty {
  text-align: center;
  color: var(--colour-muted);
  padding: var(--space-2xl) 0;
  font-size: var(--text-base);
}

.timeline-capped {
  text-align: center;
  color: var(--colour-muted);
  padding: var(--space-md) 0;
  font-size: var(--text-xs);
  font-weight: 600;
}

/* Active timer banner — attention-grabbing, sticky at top */
/* A running timer wears the colour of the button that started it: a bottle feed
   is the Bottle button's cyan, a breastfeed the Breast button's indigo, a sleep
   the Sleep button's purple. Filled with that colour and lettered in the dark
   ink, exactly like the buttons — so the banner reads as the same object as the
   thing you tapped, rather than as a separate dark surface.

   Neither of those colours flips with the theme, and neither does the ink, so
   this pairing is theme-independent: 6.5:1 at worst in both. Do not swap the
   ink for --colour-bg, which does flip — that is the pairing that put the
   finish button at 1.09:1 in dark mode.

   --banner-accent is set per variant below so everything inside can reference
   one name instead of each rule knowing which timer it belongs to. */
.active-timer-banner {
  --banner-accent: var(--colour-sleep);
  background: var(--banner-accent);
  color: var(--colour-on-event);
  padding: 12px var(--space-md) 16px;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 4px;
  cursor: pointer;
  position: sticky;
  top: 0;
  z-index: 101;
  overflow: hidden;
}

.active-timer-banner + .active-timer-banner {
  top: var(--banner-height, 96px);
}

.active-timer-banner::before {
  content: '';
  position: absolute;
  inset: 0;
  background: repeating-linear-gradient(
    -45deg,
    transparent,
    transparent 8px,
    oklch(0 0 0 / 0.05) 8px,
    oklch(0 0 0 / 0.05) 16px
  );
  pointer-events: none;
}

@keyframes banner-spinner {
  to { transform: rotate(360deg); }
}

/* Matched to .log-btn.feed and .log-btn.feed-breast. A breastfeed used to
   banner in the Bottle button's cyan, because both subtypes shared one class. */
.active-timer-banner.feed-bottle {
  --banner-accent: var(--colour-feed);
}

.active-timer-banner.feed-breast {
  --banner-accent: var(--colour-feed-breast);
}

.banner-spin {
  width: 18px;
  height: 18px;
  border: 2.5px solid oklch(0 0 0 / 0.25);
  border-top-color: var(--colour-on-event);
  border-radius: 50%;
  animation: banner-spinner 0.8s linear infinite;
  flex-shrink: 0;
}

.banner-clock {
  font-size: 18px;
  flex-shrink: 0;
  line-height: 1;
}
.active-timer-banner .timer-info {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-sm);
  font-weight: 600;
  font-size: var(--text-sm);
  position: relative;
  z-index: 1;
}
.active-timer-banner .timer-value {
  display: block;
  width: 100%;
  text-align: center;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  font-size: clamp(3.5rem, 22vw, 8rem);
  line-height: 1;
  letter-spacing: -0.04em;
  position: relative;
  z-index: 1;
}

.active-timer-banner .banner-finish-btn,
.active-timer-banner .banner-hint {
  display: none;
}

/* Expanded full-screen variant — modal-style */
.active-timer-banner.expanded {
  position: fixed;
  inset: 0;
  top: 0;
  z-index: 250;
  padding: var(--space-lg) var(--space-md);
  justify-content: center;
  align-items: center;
  gap: var(--space-md);
  animation: slideUp 0.25s var(--ease-out);
}

.active-timer-banner.expanded .timer-info {
  font-size: var(--text-base);
}

.active-timer-banner.expanded .timer-value {
  font-size: clamp(5rem, 35vw, 14rem);
}

.active-timer-banner.expanded .banner-finish-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-xs);
  width: min(80%, 360px);
  padding: 18px var(--space-md);
  margin-top: var(--space-md);
  border: none;
  border-radius: 999px;
  /* Dark pill on the coloured ground. --colour-banner-text, NOT --colour-bg:
     both --colour-bg and --colour-on-event are dark in dark mode, which is the
     pairing that had this label at 1.09:1 and invisible. Neither of these two
     flips, so it is 15:1 in both themes. */
  background: var(--colour-on-event);
  color: var(--colour-banner-text);
  font-size: var(--text-lg);
  font-weight: 700;
  font-family: inherit;
  cursor: pointer;
  position: relative;
  z-index: 2;
  box-shadow: 0 6px 24px oklch(0 0 0 / 0.18);
  transition: transform 0.1s var(--ease-out);
}

.active-timer-banner.expanded .banner-finish-btn:active {
  transform: scale(0.97);
}

.active-timer-banner.expanded .banner-hint {
  display: block;
  position: absolute;
  bottom: calc(var(--space-lg) + env(safe-area-inset-bottom, 0px));
  left: 0;
  right: 0;
  text-align: center;
  font-size: var(--text-xs);
  font-weight: 600;
  color: color-mix(in oklab, var(--colour-on-event) 70%, transparent);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  z-index: 1;
}

body.banner-expanded { overflow: hidden; }

/* Confetti — DOM-based burst */
.confetti-layer {
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 300;
  overflow: hidden;
}

.confetti-piece {
  position: absolute;
  left: 50%;
  top: 60%;
  width: 10px;
  height: 14px;
  border-radius: 2px;
  transform: translate(-50%, -50%);
  animation: confetti-burst 1.4s var(--ease-out) forwards;
  animation-delay: var(--delay, 0ms);
  opacity: 0;
}

@keyframes confetti-burst {
  0% {
    transform: translate(-50%, -50%) rotate(0);
    opacity: 1;
  }
  100% {
    transform: translate(calc(-50% + var(--dx)), calc(-50% + var(--dy))) rotate(var(--rot));
    opacity: 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  .confetti-layer { display: none; }
}

/* Stats screen */
.stats-view { padding: var(--space-md); }
.stats-view h2 { font-size: var(--text-lg); font-weight: 800; letter-spacing: -0.02em; margin-bottom: var(--space-md); }

.stats-period-toggle {
  display: flex;
  gap: 0;
  margin-bottom: var(--space-lg);
  border-radius: var(--radius-md);
  overflow: hidden;
  border: 2px solid var(--colour-border);
  background: var(--colour-surface);
}

.stats-period-toggle button {
  flex: 1;
  padding: 10px;
  border: none;
  background: transparent;
  font-size: var(--text-sm);
  font-weight: 700;
  font-family: inherit;
  cursor: pointer;
  color: var(--colour-muted);
  transition: all 0.2s var(--ease-out);
}

.stats-period-toggle button.active {
  background: var(--colour-primary);
  color: var(--colour-on-primary);
}

.stat-card {
  background: var(--colour-surface);
  border-radius: var(--radius-md);
  padding: var(--space-md);
  margin-bottom: var(--space-sm);
  border: 1px solid var(--colour-border);
}

.stat-card h3 {
  font-size: var(--text-xs);
  font-weight: 800;
  color: var(--colour-muted);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  margin-bottom: var(--space-sm);
}

.stat-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 5px 0;
}

.stat-row .stat-label { font-size: var(--text-sm); color: var(--colour-muted); }
.stat-row .stat-value { font-size: var(--text-base); font-weight: 800; font-variant-numeric: tabular-nums; }
.stat-row .stat-value.big { font-size: var(--text-xl); }

.stat-day-row {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  padding: 6px 0;
  border-bottom: 1px solid var(--colour-border);
}

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

.stat-day-label {
  width: 60px;
  font-size: var(--text-xs);
  font-weight: 700;
  color: var(--colour-muted);
  flex-shrink: 0;
}

.stat-bar {
  flex: 1;
  height: 18px;
  background: var(--colour-bg);
  border-radius: var(--radius-full);
  overflow: hidden;
}

.stat-bar-fill {
  height: 100%;
  border-radius: var(--radius-full);
  transition: width 0.4s var(--ease-out);
}

.stat-bar-fill.nappy { background: var(--colour-nappy); }
.stat-bar-fill.feed { background: var(--colour-feed); }
.stat-bar-fill.sleep { background: var(--colour-sleep); }

.stat-bar-value {
  font-size: var(--text-xs);
  font-weight: 700;
  width: 36px;
  text-align: right;
  flex-shrink: 0;
  font-variant-numeric: tabular-nums;
}

/* Trends grid — day x hour chart for event timing */
.trends-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 10px;
  margin-bottom: var(--space-sm);
}
.trends-legend-item {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: var(--text-xs);
  color: var(--colour-muted);
  font-weight: 600;
}
.trend-swatch {
  display: inline-block;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  flex-shrink: 0;
}
.trend-swatch.trend-sleep,
.trend-swatch.trend-feed { border-radius: 2px; }

.trends-grid {
  --trends-label-h: 30px;
  --trends-body-h: 360px;
  display: flex;
  gap: 4px;
}

.trends-hours {
  position: relative;
  width: 28px;
  flex-shrink: 0;
  margin-top: var(--trends-label-h);
  height: var(--trends-body-h);
}
.trends-hour-mark {
  position: absolute;
  left: 0;
  right: 0;
  transform: translateY(-50%);
  font-size: 10px;
  color: var(--colour-muted);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  text-align: right;
  padding-right: 4px;
}

.trends-days {
  flex: 1;
  display: grid;
  gap: 2px;
}

.trends-day {
  display: flex;
  flex-direction: column;
  min-width: 0;
}
.trends-day-label {
  height: var(--trends-label-h);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  font-size: 10px;
  font-weight: 700;
  color: var(--colour-muted);
  line-height: 1.1;
  gap: 1px;
}
.trends-day-label .trends-day-name { text-transform: uppercase; letter-spacing: 0.04em; }
.trends-day-label .trends-day-num { font-size: 11px; color: var(--colour-text); }
.trends-day.today .trends-day-label .trends-day-num {
  color: var(--colour-on-primary);
  background: var(--colour-primary);
  border-radius: var(--radius-full);
  min-width: 18px;
  height: 18px;
  line-height: 18px;
  padding: 0 4px;
  text-align: center;
}

.trends-day-body {
  position: relative;
  height: var(--trends-body-h);
  background: var(--colour-bg);
  border-radius: var(--radius-sm);
  background-image: linear-gradient(to bottom, var(--colour-border) 1px, transparent 1px);
  background-size: 100% 12.5%;
  background-repeat: repeat;
  overflow: hidden;
}

.trend-dot {
  position: absolute;
  left: 50%;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  transform: translate(-50%, -50%);
  box-shadow: 0 0 0 1px var(--colour-surface);
}

.trend-block {
  position: absolute;
  left: 10%;
  right: 10%;
  border-radius: 3px;
  min-height: 2px;
  opacity: 0.75;
}

.trend-nappy, .trend-swatch.trend-nappy { background: var(--colour-nappy); }
.trend-feed, .trend-swatch.trend-feed { background: var(--colour-feed); }
.trend-sleep, .trend-swatch.trend-sleep { background: var(--colour-sleep); }
.trend-medicine, .trend-swatch.trend-medicine { background: var(--colour-medicine); }
.trend-weight, .trend-swatch.trend-weight { background: var(--colour-weight); }
.trend-temperature, .trend-swatch.trend-temperature { background: var(--colour-temperature); }
.trend-milestone, .trend-swatch.trend-milestone { background: var(--colour-milestone); }

/* Settings screen */
.settings-view { padding: var(--space-md); }
.settings-view h2 { font-size: var(--text-lg); font-weight: 800; letter-spacing: -0.02em; margin-bottom: var(--space-md); }

.settings-group {
  background: var(--colour-surface);
  border-radius: var(--radius-md);
  border: 1px solid var(--colour-border);
  overflow: hidden;
  margin-bottom: var(--space-md);
}

.settings-item {
  padding: 14px var(--space-md);
  display: flex;
  align-items: center;
  justify-content: space-between;
  border-bottom: 1px solid var(--colour-border);
  cursor: pointer;
  transition: background 0.15s var(--ease-out);
}

.settings-item:last-child { border-bottom: none; }
.settings-item:active { background: var(--colour-bg); }
.settings-item .settings-label { font-size: var(--text-sm); font-weight: 600; }
.settings-item .settings-arrow { color: var(--colour-muted); font-size: var(--text-sm); }
.settings-item.danger .settings-label { color: var(--colour-danger); }

.settings-version {
  text-align: center;
  color: var(--colour-muted);
  font-size: var(--text-xs);
  padding: var(--space-md) 0;
}

/* Loading */
.loading {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 100vh;
  min-height: 100dvh;
  color: var(--colour-muted);
  font-size: var(--text-lg);
  font-weight: 600;
}

/* Toast — refined pill */
.toast {
  position: fixed;
  bottom: var(--space-lg);
  left: 50%;
  transform: translateX(-50%);
  background: var(--colour-text);
  color: var(--colour-bg);
  padding: 12px var(--space-lg);
  border-radius: var(--radius-full);
  font-size: var(--text-sm);
  font-weight: 600;
  z-index: 300;
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  box-shadow: var(--shadow-lg);
  animation: fadeInUp 0.25s var(--ease-out);
}

.toast button {
  background: none;
  border: none;
  color: var(--colour-primary-inverse);
  font-weight: 700;
  font-size: var(--text-sm);
  font-family: inherit;
  cursor: pointer;
  padding: 0;
}

@keyframes fadeInUp {
  from { opacity: 0; transform: translateX(-50%) translateY(12px); }
  to { opacity: 1; transform: translateX(-50%) translateY(0); }
}

.hidden { display: none !important; }

/* Safe area for notched phones */
@supports (padding-top: env(safe-area-inset-top)) {
  .header { padding-top: calc(14px + env(safe-area-inset-top)); }
  body { padding-bottom: env(safe-area-inset-bottom); }
}

/* Pull-to-refresh */
.pull-to-refresh {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  display: flex;
  justify-content: center;
  align-items: center;
  height: 0;
  overflow: hidden;
  z-index: 1000;
  pointer-events: none;
  transition: none;
}

.pull-to-refresh.releasing {
  transition: height 0.3s var(--ease-out), opacity 0.3s var(--ease-out);
}

.pull-to-refresh-spinner {
  width: 28px;
  height: 28px;
  border: 3px solid var(--colour-border);
  border-top-color: var(--colour-feed);
  border-radius: 50%;
  transition: transform 0.15s ease;
}

.pull-to-refresh.refreshing .pull-to-refresh-spinner {
  animation: ptr-spin 0.8s linear infinite;
}

.pull-to-refresh.threshold .pull-to-refresh-spinner {
  border-top-color: var(--colour-success);
}

@keyframes ptr-spin {
  to { transform: rotate(360deg); }
}

/* Pairing ------------------------------------------------------------- */

/* The two doors on the welcome screen. Full width and stacked, because on a
   phone a side-by-side pair either wraps or shrinks below a comfortable tap. */
.auth-choice {
  width: 100%;
  max-width: 320px;
  display: flex;
  flex-direction: column;
  gap: var(--space-sm, 8px);
}

/* .btn-secondary is filled with --colour-bg, which is fine on a modal's
   --colour-surface but invisible against the auth screen's own background. Give
   it an outline here. The border is on both buttons so they stay the same
   height — .btn-primary's is transparent purely to match the box. */
.auth-choice .btn-primary,
.auth-choice .btn-secondary {
  border: 2px solid transparent;
  padding: 12px;
}

.auth-choice .btn-secondary {
  border-color: var(--colour-border);
  color: var(--colour-text);
}

/* Sits directly under the code field, so it reads as a second way to act on
   the code rather than a form control of its own. */
.code-actions {
  margin: 0 0 var(--space-sm, 8px);
  text-align: right;
  font-size: 0.9rem;
}

.pair-hint {
  margin-top: var(--space-lg, 1.5rem);
  max-width: 22rem;
  font-size: 0.9rem;
  line-height: 1.5;
  opacity: 0.75;
  text-align: center;
}

/* The code input, with a Show/Hide toggle sitting inside it. type="password"
   is what makes iCloud Keychain and Google Password Manager offer to save the
   code — the only recovery path that exists, since we never hold it — and the
   toggle is what stops that being hostile for a code you need to read out. */
.code-field {
  position: relative;
  display: flex;
  align-items: center;
}

.code-field input {
  flex: 1;
  /* Monospace and smaller than the surrounding text: 29 characters in a
     proportional 16px font overflow the field, so the code you were just told
     to save is cut off mid-group. Monospace also makes 0/O and 1/I legible,
     which matters for something people transcribe. */
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.85rem;
  /* Only as much room as the Show/Hide toggle actually takes. */
  padding-right: 3.25rem;
}

.code-toggle {
  position: absolute;
  right: var(--space-md, 16px);
  font-size: 0.9rem;
}

.code-saved {
  margin: var(--space-md, 16px) 0;
  padding: var(--space-md, 16px);
  border: 1px solid var(--colour-border);
  border-radius: 8px;
  background: var(--colour-surface);
  text-align: left;
}

.code-saved label {
  display: flex;
  gap: var(--space-sm, 8px);
  align-items: flex-start;
  font-size: 0.95rem;
  cursor: pointer;
}

/* `.auth-form input` styles the code field — 14px padding, a 2px border and a
   bottom margin — and a checkbox is an input too, so it inherited all of that
   and grew the box by ~50px of dead space. Reset it rather than fight it. */
.code-saved input[type='checkbox'] {
  width: 20px;
  height: 20px;
  padding: 0;
  border: none;
  margin: 2px 0 0;
  flex-shrink: 0;
}

/* Scoped through .code-saved so it outranks `.auth-screen p`, which sets a
   40px bottom margin and centres its text. Without the extra class this note
   inherits both — a centred paragraph with a block of dead space under it. */
.code-saved .code-saved-note {
  margin: var(--space-sm, 8px) 0 0;
  font-size: 0.85rem;
  line-height: 1.45;
  text-align: left;
  color: var(--colour-muted);
}

/* The code shown back to you. Monospace and spaced out because it gets read
   aloud across a room and typed into another device. */
.sync-code {
  margin: var(--space-md, 16px) 0;
  padding: var(--space-md, 16px);
  border-radius: 8px;
  background: var(--colour-bg);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  /* Sized so all 29 characters fit one line on a 390px phone. Bigger looked
     better until a real code wrapped and split a group across two lines. */
  font-size: 1rem;
  letter-spacing: 0.02em;
  text-align: center;
  /* Never break-all: that splits a group mid-way ("4E1R-Z / 9ZF"), which is
     exactly what someone reading this aloud will get wrong. Browsers break
     after a hyphen by default, so a narrower screen wraps between groups. */
  overflow-wrap: break-word;
  user-select: all;
}

/* Demo bar. Bottom, not top: the top belongs to the timer banners, and two
   sticky bars competing is how you get neither of them read. */
.demo-bar {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 300;
  display: flex;
  align-items: center;
  justify-content: space-between;
  /* Wraps to two lines rather than crushing the label, which on a 320px phone
     is the difference between "none of this is real" and "none of this is re…". */
  flex-wrap: wrap;
  gap: var(--space-md);
  padding: 10px var(--space-md);
  padding-bottom: calc(10px + env(safe-area-inset-bottom, 0px));
  background: var(--colour-text);
  color: var(--colour-bg);
  font-size: var(--text-sm);
  font-weight: 600;
}

.demo-bar-actions {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  margin-left: auto;
}

/* Matches the Leave button's height so the two read as one row of controls,
   and so the link is a real tap target rather than 19px of text. */
.demo-bar a {
  display: inline-flex;
  align-items: center;
  min-height: 36px;
  color: inherit;
  text-decoration: underline;
  text-underline-offset: 2px;
  white-space: nowrap;
}

/* Leave is the way out, not the offer — so it reads as a link alongside the
   filled Set it up rather than competing with it for the same attention. */
.demo-bar .demo-bar-quiet {
  background: none;
  color: inherit;
  text-decoration: underline;
  text-underline-offset: 2px;
  padding: 6px 0;
  font-weight: 600;
}

.demo-bar button {
  flex-shrink: 0;
  min-height: 36px;
  padding: 6px var(--space-md);
  border: none;
  border-radius: var(--radius-full);
  background: var(--colour-bg);
  color: var(--colour-text);
  font: inherit;
  font-weight: 700;
  cursor: pointer;
}

/* Keep the bar off the last timeline entry and the toast. The height is
   measured and written to --demo-bar-h by renderDemoBar, because the bar wraps
   to two lines on a narrow phone and a hardcoded spacer was only ever right for
   one of the two. The fallback matches the single-line case. */
body.has-demo-bar main,
body.has-demo-bar #app-root {
  padding-bottom: calc(var(--demo-bar-h, 56px) + var(--space-md));
}

body.has-demo-bar .toast {
  bottom: calc(var(--demo-bar-h, 56px) + var(--space-md));
}

/* Modals sit at flex-end of a fixed overlay, so they land under the demo bar
   rather than above it — which cut the bottom button ("Keep timer running") in
   half. The spacer that keeps the timeline clear does not help here, because
   the overlay is fixed and not inside #app-root. */
body.has-demo-bar .modal-overlay {
  padding-bottom: var(--demo-bar-h, 56px);
}

/* A button that reads as a link, for an action offered mid-sentence. Inherits
   the surrounding font so it sits in the paragraph instead of interrupting it,
   but keeps a real <button> underneath so it is reachable by keyboard. */
.link-button {
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  color: var(--colour-primary);
  text-decoration: underline;
  cursor: pointer;
}

.link-button:hover,
.link-button:focus-visible {
  text-decoration-thickness: 2px;
}

/* The six digits the user compares against the other phone. Big and spaced,
   because misreading them is the one way this design fails. */
.fingerprint {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 2.75rem;
  font-weight: 700;
  letter-spacing: 0.35em;
  text-indent: 0.35em;
  text-align: center;
  margin: 1.25rem 0;
  color: var(--colour-text);
}

.modal-note {
  font-size: 0.9rem;
  line-height: 1.5;
  opacity: 0.8;
  margin-bottom: 1rem;
}
