/* Splash — soft pastel-yellow welcome screen shown once per app load, ahead
   of onboarding. Lives inside #app-shell (not fixed to the browser viewport)
   so on desktop, where #app-shell is a centered phone-sized frame, the
   splash appears as the first thing ON the phone, not as a backdrop behind
   it. Sits in the normal document flow (not JS-rendered) so it paints
   immediately, before any module script has even run. */
#splash {
  position: absolute; inset: 0; z-index: 9999;
  background: var(--splash-bg);
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 22px;
  /* Nudges the centered logo+tap-text group up a touch — dead-centering the
     flex group left the logo reading slightly low in the frame. */
  padding-bottom: 46px;
  transition: opacity .8s ease, visibility .8s ease;
  cursor: pointer;
}
#splash.splash--hidden { opacity: 0; visibility: hidden; pointer-events: none; }
#splash::before {
  content: ""; position: absolute; top: 50%; left: 50%; width: 280px; height: 280px; border-radius: 50%;
  transform: translate(-50%, -50%); background: radial-gradient(circle, rgba(var(--sky-blue-rgb),0.14), transparent 70%);
  pointer-events: none;
}
[data-theme="dark"] #splash::before { background: radial-gradient(circle, rgba(var(--sky-blue-rgb),0.1), transparent 70%); }
#splash__logo {
  width: 170px; max-width: 55%; opacity: 0; transform: scale(0.86); position: relative;
  animation: splashLogoIn 1.5s cubic-bezier(.2,.8,.2,1) forwards;
}
/* Logo art is brown ink drawn for a light background — invert to white in
   dark mode (same technique the onboarding cover already uses for its
   photo-background logo) so it doesn't disappear against the dark splash. */
[data-theme="dark"] #splash__logo { filter: brightness(0) invert(1); }
#splash__tap {
  font-family: var(--font-body); font-size: 0.8rem; font-weight: 600; letter-spacing: .02em; color: rgba(var(--ink-rgb),0.72);
  opacity: 0; animation: splashTapIn .9s ease 1.3s forwards; position: relative;
}
@keyframes splashLogoIn { 0% { opacity: 0; transform: scale(0.86); } 65% { opacity: 1; } 100% { opacity: 1; transform: scale(1); } }
@keyframes splashTapIn { from { opacity: 0; } to { opacity: 1; } }
@media (prefers-reduced-motion: reduce) {
  #splash__logo, #splash__tap { animation: none; opacity: 1; transform: none; }
}

/* App shell — centers a phone-sized viewport on desktop for demos, goes
   full-width on real mobile browsers. The page has ambient color washes
   (soft radial blooms of the accent tints, not a flat fill) instead of the
   original flat --cream — this is one of the biggest single legibility-of-
   change signals in this pass: the app no longer sits on a plain block of
   background color. */
#app-shell {
  max-width: 430px;
  margin: 0 auto;
  /* dvh, not vh (with a vh fallback for older engines) — same fix already
     applied to .onboarding-screen below, for the same reason: 100vh on
     mobile sizes against the LARGEST possible viewport (address bar
     hidden), not the one actually visible, so the shell (and everything
     flexed to fill it) could render taller than the real screen. Founder
     review (2026-09-25) flagged pages scrolling into a large stretch of
     empty space past the real content on multiple screens; an oversized
     shell computed off the wrong viewport height is the global cause. */
  min-height: 100vh;
  min-height: 100dvh;
  background:
    radial-gradient(ellipse 90% 40% at 15% 0%, rgba(var(--sky-blue-rgb),0.14), transparent 60%),
    radial-gradient(ellipse 80% 45% at 100% 18%, rgba(var(--blush-pink-rgb),0.13), transparent 55%),
    radial-gradient(ellipse 70% 40% at 0% 100%, rgba(var(--sage-green-rgb),0.08), transparent 55%),
    var(--cream);
  position: relative;
  box-shadow: var(--shadow-lg);
  display: flex;
  flex-direction: column;
}
@media (min-width: 431px) {
  body { background: var(--frame-bg); padding: 24px 0; }
  /* Was 860px — a real-phone height is correct on an actual device (dvh
     handles that), but as a flat DESKTOP PREVIEW minimum it force-stretched
     every short screen (a 7-row list, a simple settings page) to full phone
     height, showing a large blank stretch below the real content before the
     floating bottom nav. Lowered so short screens end closer to their
     actual content on the desktop demo; real mobile is unaffected, it was
     already sized off the true viewport via dvh above. */
  #app-shell { border-radius: 44px; overflow: hidden; min-height: 700px; border: 8px solid var(--frame-border); }
}

/* Small-phone tier (iPhone SE / older or budget Android, ~375px and under).
   #app-shell's own max-width already caps its rendered width to the real
   viewport on any phone-sized screen (no horizontal scroll, nothing
   broken) — but before this, every phone got IDENTICAL absolute padding
   and type sizing regardless of how much width it actually had, so the
   smallest real devices felt proportionally more cramped than larger ones
   with the exact same numbers. This tightens spacing/type specifically for
   that tier rather than leaving one untuned size for every phone. */
@media (max-width: 380px) {
  #screen { padding: 10px 14px 108px; }
  .vital-grid { gap: 8px; }
  .vital-tile { padding: 14px 15px; }
  .vital-tile__value { font-size: 1.8rem; }
  .card { padding: 17px; }
  .topbar h1 { font-size: var(--text-lg); }
  .wellness-card { padding: 13px 14px; gap: 12px; }
}

/* Large-phone tier (iPhone Pro Max / large Android, ~415-430px) — a touch
   more breathing room since there's real width to spend it on, rather than
   the same tight spacing tuned for a mid-size phone. */
@media (min-width: 415px) and (max-width: 430px) {
  #screen { padding: 14px 24px 120px; }
  .vital-grid { gap: 14px; }
}

#screen {
  flex: 1;
  overflow-y: auto;
  padding: 12px 20px 116px;
  -webkit-overflow-scrolling: touch;
}

/* Top bar — no more solid fill + hairline; it sits directly on the ambient
   shell background so the page reads as one continuous surface rather than
   "header bar stacked on a page." Title is bigger and carries more weight. */
.topbar {
  display: flex; align-items: center; justify-content: space-between;
  padding: 18px 20px 14px; position: sticky; top: 0; z-index: 5;
  background: var(--glass-surface);
  backdrop-filter: blur(var(--glass-blur)) saturate(1.4);
  -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(1.4);
  border-bottom: 1px solid var(--glass-border);
}
.topbar h1 { font-size: var(--text-xl); letter-spacing: -0.01em; }
.topbar .profile-switch {
  font-size: var(--text-sm); font-weight: 600; color: var(--brown); background: var(--white);
  border-radius: var(--radius-full); padding: 7px 14px 7px 7px; border: none; cursor: pointer;
  box-shadow: var(--shadow-sm); display: inline-flex; align-items: center; gap: 6px;
}
.topbar__icon-btn {
  position: relative; background: var(--white); border: none; color: var(--brown-mid); cursor: pointer;
  width: 36px; height: 36px; border-radius: 50%; display: inline-flex; align-items: center; justify-content: center;
  box-shadow: var(--shadow-sm);
}
.topbar__badge {
  position: absolute; top: -2px; right: -2px; background: var(--critical); color: var(--white);
  font-size: 0.7rem; font-weight: 700; border-radius: var(--radius-full); min-width: 15px; height: 15px;
  display: flex; align-items: center; justify-content: center; padding: 0 3px; border: 2px solid var(--cream);
}
.status-dot { width: 9px; height: 9px; border-radius: 50%; display: inline-block; flex-shrink: 0; }
.status-dot--good { background: var(--good); box-shadow: 0 0 0 3px rgba(var(--good-rgb),0.25); }
.status-dot--critical { background: var(--critical); box-shadow: 0 0 0 3px rgba(var(--critical-rgb),0.25); }

/* Bottom nav — floating rounded pill, detached from the screen edges
   (Oura/Instagram-style), not an edge-to-edge bar. Active tab gets a solid
   tinted pill behind it rather than just a color change, so the active
   state is legible at a glance, not just by hue. */
.bottom-nav {
  /* fixed (not absolute) so it stays pinned to the real, visible viewport
     on mobile no matter how tall a given screen's content is — with
     absolute, it was anchored to #app-shell's own (content-height-driven)
     bottom edge, so on any screen taller than one viewport it scrolled
     away with the page instead of staying put. Desktop preview overrides
     this back to absolute (see the min-width:431px block) since there
     #app-shell is a centered frame narrower than the browser window. */
  position: fixed; left: 14px; right: 14px; bottom: calc(14px + env(safe-area-inset-bottom));
  display: flex; gap: 2px; background: var(--glass-surface);
  backdrop-filter: blur(var(--glass-blur)) saturate(1.4);
  -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(1.4);
  border: 1px solid var(--glass-border);
  border-radius: var(--radius-full);
  padding: 7px; z-index: 10;
  box-shadow: 0 12px 32px rgba(var(--ink-rgb),0.18), 0 2px 6px rgba(var(--ink-rgb),0.08);
}
.bottom-nav__item {
  flex: 1; display: flex; flex-direction: column; align-items: center; gap: 3px;
  padding: 8px 2px 6px; border-radius: var(--radius-full); cursor: pointer; color: var(--text-muted);
  font-size: 0.7rem; font-weight: 600; background: none; border: none; font-family: var(--font-body);
  transition: color .15s, background .15s;
}
.bottom-nav__item.active { color: var(--brown); }
.bottom-nav__icon { display: flex; line-height: 1; position: relative; }
/* Lightweight nav-level notice for the You tab (not checked in yet) — a
   plain dot, the same restrained language as .status-dot, not a numbered
   badge; this is the only "notification" surface the app has left after
   the v2 alert system was retired. */
.bottom-nav__badge {
  position: absolute; top: -2px; right: -4px; width: 8px; height: 8px; border-radius: 50%;
  background: var(--blush-pink); border: 2px solid var(--surface);
}
/* Small active-tab dot instead of a filled tinted pill — reads clearly
   against the new frosted glass background without competing with it. */
.bottom-nav__item::after {
  content: ""; width: 4px; height: 4px; border-radius: 50%; background: transparent; transition: background .15s;
}
.bottom-nav__item.active::after { background: var(--sky-blue); }
@media (hover: hover) and (pointer: fine) {
  .bottom-nav__item:not(.active):hover { color: var(--brown); background: rgba(var(--ink-rgb),0.04); }
  .topbar__icon-btn:hover { box-shadow: var(--shadow-md); }
}
.bottom-nav__item:active { transform: scale(0.94); }
.topbar__icon-btn:active, .topbar .profile-switch:active { transform: scale(0.94); }
.topbar__icon-btn, .topbar .profile-switch { transition: transform .12s ease-out, box-shadow .15s ease-out; }

/* Check-in nudge (js/components/checkinNudge.js) — floats just above the
   bottom nav, same fixed/absolute responsive split as .bottom-nav itself so
   it stays inside the phone frame on the desktop preview. Slides/fades in
   rather than popping, and never dims the page behind it — it's a gentle
   surface, not a blocking dialog. */
.checkin-nudge {
  position: fixed; left: 14px; right: 14px; bottom: calc(92px + env(safe-area-inset-bottom));
  z-index: 9; display: block;
  background: var(--surface); border: 1px solid var(--glass-border); border-radius: var(--radius-lg);
  padding: 14px 16px 16px;
  box-shadow: 0 12px 32px rgba(var(--ink-rgb),0.18), 0 2px 6px rgba(var(--ink-rgb),0.08);
  opacity: 0; transform: translateY(14px);
  transition: opacity .35s ease-out, transform .35s cubic-bezier(.2,.8,.2,1);
}
.checkin-nudge--in { opacity: 1; transform: translateY(0); }
.checkin-nudge__icon {
  width: 30px; height: 30px; border-radius: 50%; flex-shrink: 0; margin-top: 1px;
  background: var(--blush-pink-tint); color: var(--blush-pink);
  display: flex; align-items: center; justify-content: center;
}
.checkin-nudge__body { flex: 1; min-width: 0; }
.checkin-nudge__eyebrow { display: flex; align-items: center; gap: 5px; font-size: 0.7rem; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; color: var(--blush-pink); padding-right: 28px; }
.checkin-nudge__text { font-family: var(--font-heading); font-size: 0.98rem; font-weight: 700; margin-top: 6px; line-height: 1.35; padding-right: 8px; }
.checkin-nudge__chips { margin-top: 12px; }
.checkin-nudge__cta {
  display: block; margin-top: 9px; background: none; border: none; padding: 0; cursor: pointer;
  font-family: var(--font-body); font-size: 0.8rem; font-weight: 700; color: var(--blush-pink);
}
.checkin-nudge__close {
  position: absolute; top: 6px; right: 8px; width: 26px; height: 26px; background: none; border: none; cursor: pointer;
  font-size: 1.2rem; line-height: 1; color: var(--text-muted); border-radius: 50%;
}
@media (hover: hover) and (pointer: fine) {
  .checkin-nudge__close:hover { background: rgba(var(--ink-rgb),0.06); }
}

/* Dashboard greeting header — avatar + "Hello {relation}!" replacing the
   old plain name row, battery/notifications/connected cluster on the right
   (megupdates.zip reference). Sits inside #screen (not the sticky app-wide
   .topbar above it, which keeps the logo/mute/profile-switch), so it scrolls
   with the page like the rest of the dashboard content. */
/* Dashboard header panel (founder review round 3, items 8-10). The empty
   sticky topbar that used to sit above this is gone on the dashboard, so
   the page opens straight onto the greeting. Hierarchy: parent greeting
   largest, then the baby, then age and a small Bluetooth/battery status,
   inside one soft, deliberate panel (not a stats card). The baby name
   doubles as the profile switcher. */
.dash-header {
  background: var(--surface); border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm); padding: 16px 18px 14px; margin: 4px 0 14px;
}
.dash-header__greeting { font-family: var(--font-heading); font-size: 1.6rem; font-weight: 700; color: var(--brown); line-height: 1.15; }
.dash-header__baby-row { margin-top: 8px; padding-top: 8px; border-top: 1px solid rgba(var(--ink-rgb),0.07); }
.dash-header__baby {
  display: inline-flex; align-items: center; gap: 4px; background: none; border: none; padding: 0; cursor: pointer;
  font-family: var(--font-heading); font-size: 1.1rem; font-weight: 700; color: var(--blush-pink); line-height: 1.2;
}
.dash-header__meta {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  font-size: 0.78rem; color: var(--text-muted); margin-top: 3px;
}
.dash-header__conn {
  display: inline-flex; align-items: center; gap: 5px; color: var(--sky-blue);
  font-family: var(--font-data); font-size: 0.7rem; font-weight: 600;
}
.dash-header__conn .status-dot { width: 6px; height: 6px; box-shadow: none; background: var(--sage-green); }
.dash-header__conn > span:last-child { color: var(--text-muted); }
.dash-header__conn .dash-header__battery--low { color: var(--warning); font-weight: 700; }
.dash-section-title { font-family: var(--font-heading); font-size: 1rem; font-weight: 700; color: var(--brown); margin: 18px 2px 10px; }
button.card.report-row { display: flex; align-items: center; gap: 12px; text-align: left; width: 100%; }
.report-row .icon-badge { width: 34px; height: 34px; flex-shrink: 0; }
.report-row__text { flex: 1; display: flex; flex-direction: column; min-width: 0; }
.report-row__title { font-weight: 700; font-size: 0.9rem; }
.report-row__sub { font-size: 0.74rem; color: var(--text-muted); margin-top: 2px; }
.report-row__chev { color: var(--text-muted); flex-shrink: 0; display: inline-flex; }
/* Health Report card reuses the old For You card's photo-led format. The
   photo box has a fixed aspect ratio so the image never shifts layout while
   it decodes. */
button.card.health-report-card { display: block; width: 100%; text-align: left; padding: 0; overflow: hidden; }
.health-report-card__photo { display: block; aspect-ratio: 5 / 2; overflow: hidden; background: var(--sky-blue-tint); }
.health-report-card__photo img { display: block; width: 100%; height: 100%; object-fit: cover; object-position: center 30%; }
.health-report-card__body { display: flex; flex-direction: column; padding: 14px 18px 16px; }
.health-report-card__headline { font-family: var(--font-heading); font-weight: 700; font-size: 1.02rem; margin-top: 6px; line-height: 1.3; }
.health-report-card__sub { display: flex; align-items: center; gap: 4px; font-size: 0.76rem; color: var(--text-muted); margin-top: 4px; }
.vital-tile .icon-badge { width: 34px; height: 34px; }

/* Buttons — primary = solid dusty blue, secondary = solid dusty rose,
   tertiary = outlined pill (matches the reference's "Get Started" /
   "Create Account" / "Continue with Apple" hierarchy). */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  font-family: var(--font-body); font-weight: 700; font-size: 0.94rem;
  padding: 14px 24px; border-radius: var(--radius-full); border: none; cursor: pointer;
  text-decoration: none; transition: transform .15s, box-shadow .15s;
}
.btn:active { transform: scale(0.97); }
/* Primary now uses --blush-pink (Serene Pastel Premium's "primary" token
   is the soft mauve-pink, not blue) — a real swap from the old palette
   where blue drove primary actions. --sky-blue moves to secondary. */
.btn--primary { background: linear-gradient(135deg, var(--blush-pink), #937B7E); color: var(--white); box-shadow: 0 6px 16px rgba(var(--blush-pink-rgb),0.35); }
.btn--secondary { background: linear-gradient(135deg, var(--sky-blue), #717E8A); color: var(--white); box-shadow: 0 6px 16px rgba(var(--sky-blue-rgb),0.32); }
.btn--tertiary { background: var(--white); color: var(--brown); border: none; box-shadow: var(--shadow-sm); }
.btn--outline { background: transparent; color: var(--brown); border: 1.5px solid var(--brown); }
/* Danger — soft coral tint with solid coral text (DESIGN.md's "Danger"
   button spec), not a loud filled gradient. Destructive confirmation actions
   (Forget Device, Reset Simulation) should never be the visually loudest
   button in the dialog — the safe/cancel option is, see .modal__actions
   below — this is deliberately quiet. */
.btn--danger { background: var(--critical-tint); color: var(--critical); box-shadow: none; }
.btn--full { width: 100%; }
.btn--sm { padding: 10px 18px; font-size: 0.82rem; }

/* Cards / tiles — bigger radius, more generous padding, a hairline border
   for definition (instead of leaning on shadow alone), no more uniform flat
   white block for every card type. A subtle inset top highlight (light
   catching an edge) adds tactile depth without going full glass/skeuomorphic
   — skipped in dark mode, where a light inset against a dark surface reads
   wrong rather than premium. */
.card {
  background: var(--surface); border-radius: var(--radius-lg); padding: 22px;
  border: 1px solid rgba(var(--ink-rgb),0.05);
  box-shadow: var(--shadow-sm), inset 0 1px 0 rgba(255,255,255,0.5);
  margin-bottom: 16px; transition: transform .12s ease-out, box-shadow .15s ease-out;
}
[data-theme="dark"] .card { box-shadow: var(--shadow-sm); }
/* Any card rendered as a real <button> (see card()'s onClick) gets press
   feedback and, on devices with a real mouse, a gentle hover lift — gated
   behind (hover: hover) so touch devices don't get a false "stuck hover". */
button.card:active, .profile-row-open:active { transform: scale(0.98); }
@media (hover: hover) and (pointer: fine) {
  button.card:hover, .profile-row-open:hover { box-shadow: var(--shadow-md); }
}

/* Icon badge — small circular tinted icon, the recurring "icon in a soft
   colored circle" pattern used across vital tiles, why-cards, and alerts. */
.icon-badge {
  display: inline-flex; align-items: center; justify-content: center; border-radius: 50%; flex-shrink: 0;
}
.icon-badge--blue { background: var(--sky-blue-tint); color: var(--sky-blue); }
.icon-badge--pink { background: var(--blush-pink-tint); color: var(--blush-pink); }
.icon-badge--green { background: var(--sage-green-tint); color: var(--sage-green); }
.icon-badge--warning { background: var(--warning-tint); color: var(--warning); }
.icon-badge--critical { background: var(--critical-tint); color: var(--critical); }
.icon-badge--square { border-radius: var(--radius-md); }

/* Avatar — colored circle with an initial, used wherever a baby doesn't have
   a real photo (dashboard header, baby profile rows). Color is picked from
   the same accent set as everything else, keyed off name so it's stable. */
.avatar {
  display: inline-flex; align-items: center; justify-content: center; border-radius: 50%;
  font-family: var(--font-heading); font-weight: 700; color: var(--white); flex-shrink: 0;
}

.vital-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-bottom: 18px; }
/* Vital tiles now carry a soft tinted surface matching their own accent
   color (not a uniform white block with a small colored badge as the only
   differentiator) — each tile reads as its own colored "zone" at a glance,
   Oura/Apple-Fitness-card style, and the numeral is the dominant element on
   the tile rather than competing with the icon badge above it. */
.vital-tile {
  background: var(--sky-blue-tint); border-radius: var(--radius-lg); padding: 18px 20px;
  cursor: pointer; position: relative; overflow: hidden; border: 1px solid rgba(var(--ink-rgb),0.04);
}
.vital-tile--pink { background: var(--blush-pink-tint); }
.vital-tile--green { background: var(--sage-green-tint); }
.vital-tile--warning { background: var(--warning-tint); }
/* Native <button>-as-tile reset — vital tiles are real buttons now (keyboard
   accessibility fix), styled identically to the old <div>. */
/* Flex column, top-aligned: a <button> centers its content vertically by
   default, so in a stretched grid row a one-line card ("Snug") drifted
   lower than a two-line one ("Within expected range"), misaligning the
   icon/header rows. The raw reading is pinned to the bottom edge. */
button.vital-tile {
  display: flex; flex-direction: column; justify-content: flex-start; align-items: stretch; width: 100%; text-align: left; font-family: var(--font-body); border: none; appearance: none;
  transition: transform .12s ease-out, box-shadow .15s ease-out;
}
button.vital-tile:active { transform: scale(0.97); }
@media (hover: hover) and (pointer: fine) {
  button.vital-tile:hover { box-shadow: var(--shadow-md); }
}
.vital-tile__top { display: flex; align-items: center; gap: 9px; margin-bottom: 10px; }
.vital-tile__label { font-size: 0.72rem; font-weight: 700; text-transform: uppercase; letter-spacing: .06em; color: var(--text-medium); }
.vital-tile__value { font-family: var(--font-heading); font-size: 2.1rem; font-weight: 700; letter-spacing: -0.02em; line-height: 1; font-variant-numeric: tabular-nums; text-align: right; }
.vital-tile__unit { font-size: 0.8rem; font-weight: 500; color: var(--text-light); margin-left: 2px; }
.vital-tile__sub { font-size: 0.74rem; color: var(--text-medium); margin-top: 6px; }
/* Interpretation-first vital reads (00_GOVERNANCE item 12): the plain-
   language read leads, the raw measurement stays but reads as secondary —
   tapping the tile still opens the full number/sparkline/history. */
.vital-tile__interpretation { font-family: var(--font-heading); font-size: 1.12rem; font-weight: 700; letter-spacing: -0.01em; line-height: 1.2; }
.vital-tile__raw { font-size: 0.74rem; color: var(--text-medium); margin-top: auto; padding-top: 4px; font-variant-numeric: tabular-nums; }
.vital-tile__top { flex-shrink: 0; }

/* Parent Wellness conversation log (js/screens/parentWellness.js) —
   deliberately NOT chat bubbles (founder review, 2026-09-24: this should
   never read as opening a chatbot). Every turn is a plain, left-aligned,
   labeled line — no left/right alternation, no per-speaker pill colors —
   so it reads as a guided reflection card the product walks you through,
   not a messaging thread. Flows naturally with the page (no inner scroll
   box), same as any other card content. Only the "tip" gets a soft
   highlight, matching the app's existing info-callout treatment. */
.checkin-log { display: flex; flex-direction: column; gap: 14px; margin: 10px 0 16px; }
.checkin-log__who { font-size: 0.7rem; font-weight: 700; text-transform: uppercase; letter-spacing: .07em; color: var(--text-muted); margin-bottom: 3px; }
.checkin-log__text { font-size: 0.88rem; line-height: 1.5; }
.checkin-log__tip {
  display: flex; gap: 8px; align-items: flex-start; font-size: 0.86rem; line-height: 1.5;
  background: var(--sage-green-tint); border-radius: var(--radius-md); padding: 10px 12px;
}
.checkin-log__tip svg { flex-shrink: 0; margin-top: 2px; color: var(--sage-green); }
.chip-row { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 4px; }
.chip {
  border: 1.5px solid rgba(var(--ink-rgb),0.14); background: var(--surface); border-radius: var(--radius-full);
  padding: 8px 14px; font-size: 0.82rem; font-weight: 600; font-family: var(--font-body); cursor: pointer;
  transition: transform .1s ease-out, border-color .15s ease-out, background .15s ease-out;
}
.chip:active { transform: scale(0.96); }
@media (hover: hover) and (pointer: fine) {
  .chip:hover { border-color: var(--sky-blue); background: var(--sky-blue-tint); }
}
.checkin-input-row { display: flex; gap: 8px; margin-top: 12px; }
.checkin-input-row input {
  flex: 1; min-width: 0; padding: 10px 14px; border-radius: var(--radius-full); border: 1.5px solid rgba(var(--ink-rgb),0.15);
  font-family: var(--font-body); font-size: 16px; background: var(--surface); color: inherit;
}
.checkin-send-btn {
  width: 40px; height: 40px; border-radius: 50%; border: none; flex-shrink: 0; cursor: pointer;
  background: var(--blush-pink); color: #fff; display: flex; align-items: center; justify-content: center;
  transition: transform .1s ease-out;
}
.checkin-send-btn:active { transform: scale(0.92); }
.wellness-support-link {
  display: block; width: 100%; text-align: center; background: none; border: none; cursor: pointer;
  font-family: var(--font-body); font-size: 0.76rem; font-weight: 600; color: var(--text-muted);
  margin-top: 14px; padding: 4px; text-decoration: underline; text-underline-offset: 2px;
}
.vital-tile--pink .icon-badge { background: rgba(255,255,255,0.6); color: var(--blush-pink); }
.vital-tile--blue .icon-badge { background: rgba(255,255,255,0.6); color: var(--sky-blue); }
.vital-tile--green .icon-badge { background: rgba(255,255,255,0.6); color: var(--sage-green); }
.vital-tile .icon-badge { background: rgba(255,255,255,0.6); }

.badge {
  display: inline-flex; align-items: center; gap: 5px; font-size: 0.7rem; font-weight: 700;
  padding: 4px 10px; border-radius: var(--radius-full); text-transform: uppercase; letter-spacing: .04em;
}
.badge--blue { background: rgba(var(--sky-blue-rgb),0.16); color: var(--badge-blue-ink); }
.badge--pink { background: rgba(var(--blush-pink-rgb),0.2); color: var(--badge-pink-ink); }
.badge--green { background: rgba(var(--sage-green-rgb),0.24); color: var(--badge-green-ink); }
.badge--critical { background: rgba(var(--critical-rgb),0.16); color: var(--badge-critical-ink); }
.badge--warning { background: rgba(var(--warning-rgb),0.2); color: var(--badge-warning-ink); }

.eyebrow { font-size: 0.7rem; font-weight: 700; letter-spacing: .1em; text-transform: uppercase; color: var(--brown-mid); display: inline-flex; align-items: center; gap: 6px; }

/* Alert banner */
.alert-banner {
  display: flex; gap: 12px; align-items: flex-start; padding: 14px 16px; border-radius: var(--radius-md);
  margin-bottom: 14px; animation: slideIn .3s ease-out;
}
.alert-banner--critical { background: rgba(var(--critical-rgb),0.1); border: 1.5px solid rgba(var(--critical-rgb),0.3); }
.alert-banner--standard { background: rgba(var(--warning-rgb),0.12); border: 1.5px solid rgba(var(--warning-rgb),0.3); }
.alert-banner--info { background: rgba(var(--sky-blue-rgb),0.12); border: 1.5px solid rgba(var(--sky-blue-rgb),0.3); }
.alert-banner__icon { display: flex; flex-shrink: 0; margin-top: 1px; }
.alert-banner--critical .alert-banner__icon { color: var(--critical); }
.alert-banner--standard .alert-banner__icon { color: var(--warning); }
.alert-banner--info .alert-banner__icon { color: var(--sky-blue); }
.alert-banner__title { font-weight: 700; font-size: 0.92rem; }
.alert-banner__why { font-size: 0.78rem; color: var(--text-medium); margin-top: 4px; line-height: 1.5; }
/* Native <button>-as-banner reset — clickable alert banners (data-nav) are
   real buttons now, so Tab/Enter/Space reach them like any other control. */
button.alert-banner { width: 100%; text-align: left; font-family: var(--font-body); appearance: none; transition: transform .12s ease-out; }
button.alert-banner:active { transform: scale(0.98); }

@keyframes slideIn { from { opacity: 0; transform: translateY(-6px); } to { opacity: 1; transform: translateY(0); } }
@keyframes pulse { 0%, 100% { transform: scale(1); opacity: 1; } 50% { transform: scale(1.15); opacity: 0.6; } }
@keyframes fadeIn { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: translateY(0); } }
.fade-in { animation: fadeIn .4s ease-out; }
/* Live data ticks (~1/sec) re-render a screen's content without navigating —
   don't replay the entrance animation every tick, that's what caused the blink. */
#screen.screen--live-update .fade-in { animation: none; }

/* Drill-in transition — spatial continuity for "going deeper" (profile
   detail, a settings sub-screen, a per-vital detail view) vs. `.fade-in`'s
   plain cross-fade for switching between top-level tabs. Meaningfully
   different motion for a meaningfully different navigational action, not
   the same fade reused everywhere. Same live-tick suppression rule as
   `.fade-in`, since vitalDetail is a LIVE_ROUTE. */
@keyframes slideInRight { from { opacity: 0; transform: translateX(20px); } to { opacity: 1; transform: translateX(0); } }
.slide-in-right { animation: slideInRight .32s cubic-bezier(.2,.8,.2,1); }
#screen.screen--live-update .slide-in-right { animation: none; }

/* Chart entrance — canvas charts (and the CSS-drawn donut) grow up from
   their own baseline on first paint, rather than simply popping into
   existence fully-formed. Purely visual (the canvas pixels are drawn
   instantly either way) but reads as the data "arriving," not just present. */
@keyframes chartGrow { from { opacity: 0; transform: scaleY(0.85); } to { opacity: 1; transform: scaleY(1); } }
canvas.sparkline, canvas.bar-chart, .donut-chart {
  animation: chartGrow .5s cubic-bezier(.2,.8,.2,1);
  transform-origin: bottom;
}
#screen.screen--live-update canvas.sparkline,
#screen.screen--live-update canvas.bar-chart,
#screen.screen--live-update .donut-chart { animation: none; }

/* Notification list stagger — each row appears in sequence (via the
   `--stagger-i` custom property each row sets inline) instead of the whole
   list just being present at once, so multiple new notifications read as
   arriving, not as a static block. */
@keyframes staggerIn { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } }
.stagger-in { animation: staggerIn .35s ease-out both; animation-delay: calc(var(--stagger-i, 0) * 45ms); }
/* Missing from here caused the Insights screen's cards to visibly "glitch"
   every ~2.8s live tick (Insights is a LIVE_ROUTE) — same root cause as the
   .fade-in/.slide-in-right rules above, just missed when .stagger-in was
   added later. */
#screen.screen--live-update .stagger-in { animation: none; }
@keyframes shimmer { 0% { background-position: -200px 0; } 100% { background-position: 200px 0; } }

/* Slow "breathing" animation — a calming, reassurance-coded motion (inhale/
   exhale timing, not a sharp pulse) used for the pairing search circle and
   any other "actively, gently monitoring" indicator. The core circle gently
   scales (inhale/exhale) while concentric rings continuously expand outward
   and fade, staggered so more than one ring is visible at a time. */
@keyframes breathe-core {
  0%, 100% { transform: scale(0.94); }
  50% { transform: scale(1.04); }
}
@keyframes breathe-ring {
  0% { transform: scale(0.72); opacity: 0.5; }
  70% { opacity: 0.12; }
  100% { transform: scale(1.7); opacity: 0; }
}

/* Skeleton loading (Linear/Notion-style shimmer placeholder) */
.skeleton {
  background: linear-gradient(90deg, rgba(var(--ink-rgb),0.08) 25%, rgba(var(--ink-rgb),0.15) 37%, rgba(var(--ink-rgb),0.08) 63%);
  background-size: 400px 100%; animation: shimmer 1.4s ease-in-out infinite; border-radius: var(--radius-sm);
}
.skeleton--value { height: 26px; width: 60%; margin-top: 6px; }
.skeleton--sub { height: 12px; width: 40%; margin-top: 8px; }

/* Alert/notification card — white surface + colored left border by severity,
   replacing the flat tinted-background banner. Shared by Alerts and
   Notifications so both read as one consistent "event card" language. */
.alert-card {
  display: block; background: var(--surface);
  border-radius: var(--radius-lg); padding: 16px 18px; margin-bottom: 14px;
  border: 1px solid rgba(var(--ink-rgb),0.05); border-left: 4px solid var(--text-muted);
  box-shadow: var(--shadow-sm);
}
/* Tappable header (icon + title + time) that navigates to the alert detail
   screen — kept separate from the description/disclosure below it so tapping
   the inline "Learn more" toggle doesn't also trigger navigation (a <details>
   can't nest inside a <button> anyway). */
.alert-card__taprow {
  display: flex; gap: 12px; align-items: flex-start; width: 100%;
  background: none; border: none; padding: 0; margin-bottom: 8px; cursor: pointer;
  text-align: left; font-family: var(--font-body); appearance: none;
}
.alert-card__desc { margin-left: 48px; }
.alert-card .disclosure { margin-left: 48px; }
.alert-card--critical { border-left-color: var(--critical); }
.alert-card--standard { border-left-color: var(--warning); }
.alert-card--info { border-left-color: var(--sky-blue); }
.alert-card--good { border-left-color: var(--sage-green); }
.alert-card--pink { border-left-color: var(--blush-pink); }
.alert-card__body { flex: 1; min-width: 0; }
.alert-card__top { display: flex; justify-content: space-between; align-items: flex-start; gap: 10px; }
.alert-card__eyebrow { font-size: 0.7rem; font-weight: 700; text-transform: uppercase; letter-spacing: .07em; color: var(--text-muted); }
.alert-card--critical .alert-card__eyebrow { color: var(--critical); }
.alert-card--standard .alert-card__eyebrow { color: var(--warning); }
.alert-card--info .alert-card__eyebrow { color: var(--brown-mid); }
.alert-card--good .alert-card__eyebrow { color: var(--sage-green); }
.alert-card--pink .alert-card__eyebrow { color: var(--blush-pink); }
.alert-card__title { font-family: var(--font-heading); font-weight: 700; font-size: 1rem; color: var(--brown); margin-top: 2px; line-height: 1.3; }
.alert-card__time { font-family: var(--font-data); font-size: 0.7rem; color: var(--text-muted); white-space: nowrap; flex-shrink: 0; margin-top: 3px; }
.alert-card__desc { font-size: 0.85rem; color: var(--text-medium); line-height: 1.55; margin-top: 6px; }

/* Filter pill row (Alerts screen: All Activity / Critical / Standard) —
   individually-sized pills, not an equal-width segmented control like .tabs. */
.filter-pills { display: flex; gap: 8px; margin-bottom: 18px; flex-wrap: wrap; }
.filter-pill {
  padding: 8px 16px; border-radius: var(--radius-full); font-size: 0.78rem; font-weight: 700;
  background: var(--white); color: var(--text-medium); border: 1px solid rgba(var(--ink-rgb),0.08);
  cursor: pointer; transition: background .15s ease-out, color .15s ease-out, transform .1s ease-out;
  font-family: var(--font-body);
}
.filter-pill.active { background: var(--sky-blue); color: var(--white); border-color: var(--sky-blue); }
.filter-pill:active { transform: scale(0.96); }

/* Insight card (compact — used nowhere currently but kept for reuse) */
.insight-card { display: flex; gap: 12px; padding: 14px 16px; }
.insight-card__icon { display: flex; flex-shrink: 0; }
.insight-card__title { font-weight: 700; font-size: 0.9rem; margin-bottom: 3px; }
.insight-card__why { font-size: 0.78rem; color: var(--text-medium); line-height: 1.5; }
.insight-card__confidence { font-size: 0.7rem; color: var(--text-muted); margin-top: 10px; padding-top:10px; border-top: 1px solid rgba(var(--ink-rgb),0.07); }

/* Insight card — full, 3-part WHOOP-style structure (11_ASSETS/UI_INSPIRATION.md) */
.insight-card--full { border-left: 4px solid var(--text-muted); }
.insight-card--full.insight-card--good { border-left-color: var(--good); }
.insight-card--full.insight-card--watch { border-left-color: var(--warning); }
.insight-card--full.insight-card--neutral { border-left-color: var(--sky-blue); }
.insight-card__section { margin-top: 10px; }
.insight-card__section-label { display: block; font-size: 0.7rem; font-weight: 700; text-transform: uppercase; letter-spacing: .05em; color: var(--text-muted); margin-bottom: 2px; }
.insight-card__section p { margin: 0; font-size: 0.83rem; color: var(--text-medium); line-height: 1.55; }
.insight-card__feedback {
  display: flex; align-items: center; gap: 8px; margin-top: 12px; padding-top: 12px;
  border-top: 1px solid rgba(var(--ink-rgb),0.07); font-size: 0.78rem; color: var(--text-medium);
}
.feedback-btn {
  padding: 5px 13px; border-radius: var(--radius-full); border: 1px solid rgba(var(--ink-rgb),0.12);
  background: var(--white); font-size: 0.74rem; font-weight: 600; color: var(--text-medium);
  cursor: pointer; font-family: var(--font-body); transition: transform .1s ease-out, background .15s, color .15s, border-color .15s;
}
.feedback-btn:active { transform: scale(0.95); }
.feedback-btn.selected { background: var(--sky-blue); color: var(--white); border-color: var(--sky-blue); }

/* Hero status card — used only for the pre-first-reading "Connecting…" state
   now; the Wellness Score card below replaced this as the live dashboard hero
   per founder direction 2026-07-07 (see 00_GOVERNANCE/RISK_REGISTER.md R-009). */
.hero-status {
  display: flex; align-items: center; gap: 16px; padding: 20px;
  background: linear-gradient(135deg, var(--sky-blue) 0%, var(--blush-pink) 100%);
  border-radius: var(--radius-lg); margin-bottom: 16px; box-shadow: var(--shadow-md);
}
.hero-status--alert { background: linear-gradient(135deg, var(--critical-strong), var(--critical)); }
.hero-status__icon { display: flex; color: var(--white); }
.hero-status__label { font-family: var(--font-heading); font-size: 1.3rem; font-weight: 700; color: var(--white); }
.hero-status__sub { font-size: 0.78rem; color: rgba(255,255,255,0.9); margin-top: 2px; }

/* Wellness Score — restructured entirely, not just resized. It used to be
   the one gradient-filled "hero" block on the dashboard (the thing the UX
   audit flagged as visually dominating everything else); now it's built
   from the same bordered-card + left-accent-stripe language as the other
   AI feature cards below it (Cry Intelligence, Digital Twin) so it reads as
   one card among equals, distinguished only by its accent color and ring —
   not by being the only colored block on the page. 00_GOVERNANCE/RISK_
   REGISTER.md R-009's regulatory-framing note is why this stays de-
   emphasized rather than restored to a hero treatment; the disclaimer line
   must stay visible wherever this card appears. */
/* Wellness hero. No number, no ring (founder review round 3: "do not put
   the sapling inside a circle"). The illustrated sapling stands on its own
   with a soft tinted glow behind it as the only state cue. */
.wellness-hero {
  display: flex; flex-direction: column; align-items: center; text-align: center;
  padding: 6px 0 4px; margin-bottom: 10px;
}
.wellness-hero__label { font-family: var(--font-heading); font-size: 1.08rem; font-weight: 700; color: var(--brown); max-width: 280px; line-height: 1.3; }
.sprout-hero { position: relative; display: flex; align-items: flex-end; justify-content: center; flex-shrink: 0; overflow: hidden; }
.sprout-hero .sprout { flex-shrink: 0; }
.sprout-hero::before {
  content: ""; position: absolute; left: 14%; right: 14%; bottom: 0; height: 70%;
  border-radius: 50%; background: radial-gradient(closest-side, var(--sprout-glow), transparent);
  transition: background .8s ease-out;
}
.sprout-hero .sprout { position: relative; }
.sprout { display: inline-flex; align-items: flex-end; justify-content: center; flex-shrink: 0; }
.sprout img { display: block; width: 100%; height: 100%; object-fit: contain; }
/* Gentle idle sway, pivoting from the base of the stem like something
   rooted, not the whole image spinning around its center. */
.sprout--sway img { transform-origin: 50% 100%; animation: sproutSway 4.5s ease-in-out infinite; }
@keyframes sproutSway {
  0%, 100% { transform: rotate(-2deg); }
  50% { transform: rotate(2deg); }
}
@media (prefers-reduced-motion: reduce) {
  .sprout--sway img { animation: none; }
}

/* Cry Intelligence™ card */
.cry-intelligence-card { border-left: 4px solid var(--blush-pink); }
.cry-alt-causes { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 10px; }

/* "For You" dashboard card — founder review (2026-09-25): the home screen
   needs a larger, more prominent entry point into the parent-wellness
   experience than the small Sleep/Health Report rows, with a real photo at
   the top rather than an icon badge, while staying warm and approachable
   rather than clinical. Text-align left throughout (the card is a button). */

/* Sleep timeline */
.sleep-timeline { display: flex; gap: 2px; height: 26px; border-radius: var(--radius-full); overflow: hidden; margin: 12px 0; }
.sleep-timeline__seg--deep { background: var(--blush-pink); }
.sleep-timeline__seg--light { background: var(--sky-blue); }
.sleep-timeline__seg--awake { background: var(--cream-dark); }
.sleep-legend { display: flex; gap: 16px; font-size: 0.75rem; color: var(--text-medium); flex-wrap: wrap; }
.sleep-legend__dot { width: 10px; height: 10px; border-radius: 2px; display: inline-block; margin-right: 5px; }

/* Night Wakings & Patterns — a real timeline of wake-event start times
   across the last 24h (trend.sleepStageRuns(), not fabricated), dots on a
   thin track rather than a full chart axis since these are sparse discrete
   events, not a continuous series. */
.wake-timeline { margin: 14px 0 4px; }
.wake-timeline__track { position: relative; height: 6px; background: rgba(var(--ink-rgb),0.08); border-radius: var(--radius-full); }
.wake-timeline__dot {
  position: absolute; top: 50%; width: 11px; height: 11px; margin-left: -5.5px; transform: translateY(-50%);
  background: var(--warning); border-radius: 50%; border: 2px solid var(--surface); box-shadow: var(--shadow-sm);
}
.wake-timeline__labels { display: flex; justify-content: space-between; font-size: 0.7rem; color: var(--text-muted); margin-top: 6px; }
.wake-event-list { margin-top: 10px; }
.wake-event-row { display: flex; justify-content: space-between; align-items: center; padding: 8px 0; border-bottom: 1px solid rgba(var(--ink-rgb),0.06); font-size: 0.82rem; }
.wake-event-row:last-child { border-bottom: none; }
.wake-event-row__time { font-weight: 600; color: var(--brown); }
.wake-event-row__duration { color: var(--text-muted); font-family: var(--font-data); font-size: 0.74rem; }

/* Sleep Consistency — one bar per real sleep stretch found in the last 24h
   (trend.sleepStageRuns(), grouped — see sleep.js's sleepStretches()). Bar
   height = duration (magnitude), the deep-sleep % is a direct text label
   rather than a second color channel, per the one-encoding-per-value rule —
   this is two different measures, not one, so it gets two separate marks. */
/* Sleep Consistency bars — founder review (2026-09-25): the bar-height
   percentages, deep-sleep percents, and start times used to live inside the
   SAME flex column as the fill bar, so the fill's `height:X%` was computed
   against a box whose height also had to fit two lines of text below it,
   visually jumbling the numbers into the bars. Bars and their labels are
   now two separate rows with matching flex proportions (same gap, same
   flex:1 per item), so each label sits directly under its own bar with no
   ambiguity about which number belongs to which stretch. */
.stretch-bars { display: flex; align-items: flex-end; gap: 10px; height: 64px; margin: 16px 0 8px; }
.stretch-bar { flex: 1; display: flex; align-items: flex-end; justify-content: center; height: 100%; min-width: 0; }
.stretch-bar__fill { width: 100%; max-width: 34px; border-radius: 6px 6px 3px 3px; background: var(--blush-pink); min-height: 4px; }
.stretch-bar-labels { display: flex; gap: 10px; }
.stretch-bar-label { flex: 1; text-align: center; min-width: 0; }
.stretch-bar-label__pct { font-size: 0.7rem; font-weight: 700; color: var(--brown); font-variant-numeric: tabular-nums; white-space: nowrap; }
.stretch-bar-label__time { font-size: 0.7rem; color: var(--text-muted); margin-top: 2px; white-space: nowrap; }

/* Donut / pie chart — CSS conic-gradient ring with a hollow center label,
   used for Sleep Stage composition and anywhere a proportion breakdown
   reads better as a ring than a bar. */
.donut-chart { border-radius: 50%; display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
.donut-chart__inner { border-radius: 50%; background: var(--white); display: flex; flex-direction: column; align-items: center; justify-content: center; }
.donut-chart__label { font-family: var(--font-heading); font-size: 1.15rem; font-weight: 700; color: var(--brown); line-height: 1; letter-spacing: -0.01em; font-variant-numeric: tabular-nums; }
.donut-chart__sub { font-size: 0.7rem; color: var(--text-muted); margin-top: 2px; text-align: center; }
.donut-legend { display: flex; flex-direction: column; gap: 6px; }
.donut-legend__item { display: flex; align-items: center; gap: 8px; font-size: 0.78rem; color: var(--text-medium); }
.donut-legend__dot { width: 10px; height: 10px; border-radius: 3px; flex-shrink: 0; }
.donut-legend__value { margin-left: auto; font-weight: 700; color: var(--brown); font-family: var(--font-heading); }

/* Bar chart canvas + stat-chip row (Reports "Sleep Duration" / "Report Summary") */
canvas.bar-chart { width: 100%; height: 130px; display: block; }
.stat-chip-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(78px, 1fr)); gap: 8px; margin-top: 10px; }
.stat-chip { background: var(--cream); border-radius: var(--radius-sm); padding: 10px 6px; text-align: center; }
.stat-chip__icon { margin: 0 auto 4px; }
.stat-chip__value { font-family: var(--font-heading); font-weight: 700; font-size: 1.05rem; color: var(--brown); letter-spacing: -0.01em; font-variant-numeric: tabular-nums; }
.stat-chip__label { font-size: 0.7rem; color: var(--text-muted); text-transform: uppercase; letter-spacing: .03em; margin-top: 1px; }

/* Report vital card (health_report_visual_summary): icon + eyebrow + big
   value inline, then a thin range-position bar and a one-line caption —
   denser and more informative than a plain label/value row, each card
   carries its own distinct visual instead of repeating the same layout. */
/* Full pastel-tinted card background per vital (not just an icon-badge
   accent) — matches the Serene Pastel Premium "Pediatric Summary" report
   screen, where each vital gets its own colored card rather than a
   uniform white one. Icon badge inside switches to a translucent white
   circle so it still reads clearly against the tint. */
.report-vital-card--pink { background: var(--blush-pink-tint); border-color: transparent; }
.report-vital-card--blue { background: var(--sky-blue-tint); border-color: transparent; }
.report-vital-card--green { background: var(--sage-green-tint); border-color: transparent; }
.report-vital-card--pink .icon-badge, .report-vital-card--blue .icon-badge, .report-vital-card--green .icon-badge { background: rgba(255,255,255,0.6); }
.report-vital-card__top { display: flex; align-items: center; gap: 12px; }
.report-vital-card__value { font-family: var(--font-heading); font-weight: 700; font-size: 1.5rem; color: var(--brown); letter-spacing: -0.01em; font-variant-numeric: tabular-nums; }
.report-vital-card__value span { font-size: 0.85rem; font-weight: 500; color: var(--text-light); margin-left: 2px; }
.range-bar { height: 6px; border-radius: var(--radius-full); background: rgba(var(--ink-rgb),0.08); margin-top: 12px; overflow: hidden; }
.range-bar__fill { height: 100%; border-radius: var(--radius-full); transition: width .5s ease-out; }
.report-vital-card__caption { font-size: 0.72rem; color: var(--text-muted); margin-top: 8px; }
.report-vital-card canvas.sparkline { height: 40px; margin-top: 10px; }
/* Illustrative heartbeat line (reports.js) — a continuously scrolling
   monitor-strip effect, not a bouncing icon (founder review, 2026-09-24:
   the icon badge next to this must stay perfectly still). The path draws
   the same beat shape twice back-to-back; the SVG's own viewBox clips it to
   a 64px window, and this animation scrolls that path left by exactly one
   beat-width on a seamless loop. */
.heartbeat-wave { flex-shrink: 0; }
.heartbeat-wave__track { animation: heartbeatScroll 2.6s linear infinite; }
@keyframes heartbeatScroll { from { transform: translateX(0); } to { transform: translateX(-64px); } }
/* Green-tinted reassurance callout — matches the Serene Pastel Premium
   profile-setup screen's "why we ask" info box. */
.info-callout {
  display: flex; align-items: flex-start; gap: 10px; background: var(--sage-green-tint); border-radius: var(--radius-lg);
  padding: 14px 16px; margin: 20px 0; font-size: 0.8rem; color: var(--badge-green-ink); line-height: 1.5;
}
.info-callout svg { flex-shrink: 0; margin-top: 2px; color: var(--sage-green); }

/* Pairing wizard — multi-step device setup (Owlet/Nanit-style) */
.pairing-wizard { text-align: center; padding-top: 24px; position: relative; }

/* Small celebratory flourish on the final "Paired!" step (pairing_connected)
   — pure decoration, plays once and fades, respects the app-wide
   prefers-reduced-motion rule in theme.css (no extra media query needed here). */
.confetti { position: absolute; width: 7px; height: 7px; border-radius: 2px; opacity: 0; pointer-events: none; animation: confettiPop .7s ease-out forwards; }
.confetti--1 { top: 36px; left: 10%; background: var(--blush-pink); animation-delay: .1s; }
.confetti--2 { top: 84px; right: 12%; background: var(--sky-blue); animation-delay: .25s; transform: rotate(20deg); }
.confetti--3 { top: 190px; left: 6%; background: var(--sage-green); animation-delay: .4s; border-radius: 50%; }
.confetti--4 { top: 250px; right: 9%; background: var(--blush-pink); animation-delay: .55s; border-radius: 50%; }
.confetti--5 { top: 54px; right: 28%; background: var(--sage-green); animation-delay: .7s; }
.confetti--6 { top: 270px; left: 24%; background: var(--sky-blue); animation-delay: .85s; border-radius: 50%; }
@keyframes confettiPop {
  0% { opacity: 0; transform: scale(.3) translateY(6px) rotate(0deg); }
  55% { opacity: 1; }
  100% { opacity: 0; transform: scale(1) translateY(-16px) rotate(55deg); }
}
.pairing-wizard__progress { height: 4px; background: rgba(var(--ink-rgb),0.1); border-radius: var(--radius-full); overflow: hidden; margin-bottom: 8px; }
.pairing-wizard__progress-bar { height: 100%; background: var(--sky-blue); transition: width .4s ease; }
.pairing-wizard__step-label { font-family: var(--font-data); font-size: 0.7rem; color: var(--text-muted); text-transform: uppercase; letter-spacing: .06em; font-weight: 700; margin-bottom: 8px; }

/* Breathing circle — calm, slow pulse with concentric rings expanding
   outward (not a sharp "loading" spinner feel), used for the pairing search
   state and anywhere the app reassures "actively monitoring, all is well."
   Structure: a solid core circle (its own gentle inhale/exhale scale) plus
   3 ring spans, staggered in time, each expanding and fading independently
   so multiple rings are visible at once, like a sonar ping. */
.breathing-circle {
  width: 140px; height: 140px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center; margin: 24px auto;
  position: relative;
}
.breathing-ring {
  position: absolute; inset: 0; border-radius: 50%; border: 2.5px solid var(--sky-blue);
  opacity: 0; pointer-events: none; animation: breathe-ring 3.6s ease-out infinite;
}
.breathing-ring--2 { border-color: var(--blush-pink); animation-delay: 1.2s; }
.breathing-ring--3 { border-color: var(--sky-blue); animation-delay: 2.4s; }
.breathing-circle__core {
  width: 100%; height: 100%; border-radius: 50%;
  /* Fixed literal neutral (Serene Pastel Premium's own "inverse-surface"
     token), not the theme-flipping --brown ink var — this badge should
     read as a deep neutral charcoal in both light and dark mode, not
     invert to near-white at night. */
  background: radial-gradient(circle, #303030, #4A4A4A);
  display: flex; align-items: center; justify-content: center;
  color: var(--white); position: relative; z-index: 1;
  animation: breathe-core 3.6s ease-in-out infinite;
}
.breathing-circle--still .breathing-ring { animation: none; opacity: 0; }
.breathing-circle--still .breathing-circle__core { animation: none; }
.breathing-circle--success .breathing-circle__core { background: var(--sage-green); }
.breathing-circle--success .breathing-ring { border-color: var(--sage-green); }
/* Pairing's device visual (js/screens/pairing.js) — shows the actual band
   product photo instead of an abstract dark disc + bare Bluetooth glyph,
   which founder review flagged as unclear ("I do NOT understand what the
   circular graphic is supposed to represent"). A small badge overlay signals
   the step (Bluetooth while scanning/connecting, a shield during
   permissions, a check once paired) without needing to change the core. */
.breathing-circle--device .breathing-circle__core {
  background: var(--surface); box-shadow: inset 0 0 0 1px rgba(var(--ink-rgb),0.06);
}
.breathing-circle__device-img { width: 64%; height: 64%; object-fit: contain; filter: drop-shadow(0 6px 14px rgba(var(--ink-rgb),0.16)); }
.pairing-visual__badge {
  position: absolute; bottom: 6px; right: 6px; width: 32px; height: 32px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center; color: #fff; border: 3px solid var(--cream); z-index: 2;
}
.pairing-visual__badge--blue { background: var(--sky-blue); }
.pairing-visual__badge--green { background: var(--sage-green); }

.pairing-wizard__title { font-family: var(--font-heading); font-weight: 700; font-size: 1.6rem; letter-spacing: -0.01em; margin: 0; }
/* Device-found section — deliberately NOT a white card anymore. Floats
   directly on the page/ambient background: no box, no border, no shadow,
   just the product shot and the wheel picker below it in open space. */
.device-found-card { display: flex; flex-direction: column; align-items: center; gap: 2px; margin: 0 0 20px; text-align: center; }

/* Soft radial glow behind the real product photo (Pairing "Found" step,
   Settings > Device) — a plain product shot on a flat card reads cheap;
   this gives it a bit of stage lighting instead. */
.product-shot {
  display: flex; align-items: center; justify-content: center; margin: 4px auto 12px; width: 168px; height: 168px;
  background: radial-gradient(circle at 50% 42%, rgba(var(--sky-blue-rgb),0.18), rgba(var(--blush-pink-rgb),0.08) 60%, transparent 75%);
  border-radius: 50%;
}

/* Swipeable band-color wheel (ui.js's bandWheelMarkup()/initBandWheel()) —
   horizontal scroll-snap strip, center item is the selection. No background
   of its own; padding-inline centers the first/last real items instead of
   leaving them stranded at the strip's hard edges. */
.band-wheel {
  /* position:relative makes this the offsetParent for its items — without
     it, item.offsetLeft resolves against a distant ancestor (whatever the
     nearest positioned element up the tree happens to be), and the
     scrollLeft-centering math in initBandWheel() silently computes against
     the wrong origin. width:100% matters just as much: its parent
     (.device-found-card) is align-items:center, which lets a flex child
     shrink-to-fit its own unwrapped content instead of filling the parent —
     without it this box silently sizes to all 5 items end-to-end (wider
     than the visible card), so clientWidth/2 in the centering math and the
     padding's own 50% basis (computed off the constrained parent, not this
     box) fall out of sync. */
  position: relative;
  width: 100%;
  display: flex; gap: 4px; overflow-x: auto;
  /* proximity, not mandatory — mandatory fights the JS-driven centering in
     initBandWheel() on a fast flick (both try to snap at once, which is
     what made the swipe feel jumpy); proximity lets JS own the final
     settle while still getting native snap assistance on a slow drag. */
  scroll-snap-type: x proximity;
  padding: 16px calc(50% - 44px) 8px; -webkit-overflow-scrolling: touch;
  scrollbar-width: none; -ms-overflow-style: none;
  /* A soft center "slot" so it's obvious there's a fixed selection point to
     swipe items into, not just a row of icons to guess at. */
  background-image: linear-gradient(90deg, transparent calc(50% - 52px), rgba(var(--ink-rgb),0.05) calc(50% - 52px), rgba(var(--ink-rgb),0.05) calc(50% + 52px), transparent calc(50% + 52px));
  background-position: 0 22px; background-repeat: no-repeat; background-size: 100% 76px;
  border-radius: var(--radius-lg);
}
.band-wheel::-webkit-scrollbar { display: none; }
.band-wheel__item {
  scroll-snap-align: center; flex-shrink: 0; width: 88px; display: flex; flex-direction: column;
  align-items: center; gap: 6px; opacity: 0.4; transform: scale(0.76); cursor: pointer;
  transition: opacity .2s ease-out, transform .2s ease-out;
}
.band-wheel__item.active { opacity: 1; transform: scale(1.15); }
.band-wheel__item img { width: 58px; height: 58px; object-fit: contain; filter: drop-shadow(0 6px 14px rgba(var(--ink-rgb),0.16)); }
.band-wheel__label { font-size: 0.7rem; font-weight: 600; color: var(--text-medium); white-space: nowrap; }
.band-wheel__item.active .band-wheel__label { color: var(--brown); font-weight: 700; }

/* Onboarding slide transition — its own animation, deliberately NOT `.fade-in`.
   `.fade-in` is coupled to the live-data-tick suppression rule above
   (`#screen.screen--live-update .fade-in { animation: none; }`); onboarding
   isn't a live-data route so that coupling wouldn't actually misfire here,
   but giving it a separately-named class keeps the two concerns cleanly
   separated rather than relying on that being true. Slides move in from the
   right, matching forward navigation through the carousel. */
@keyframes onboardingSlideIn { from { opacity: 0; transform: translateX(18px); } to { opacity: 1; transform: translateX(0); } }
.onboarding-slide-in { animation: onboardingSlideIn .35s ease-out; }

/* Onboarding — multi-slide carousel (Oura/WHOOP/Apple-HIG-style welcome flow).
   min-height was `calc(100% + 116px)`, a percentage of #screen's height —
   but #screen's height comes from flex:1 (implicit/stretched), not an
   explicit `height`, so percentage resolution against it isn't reliable:
   measured on an actual mobile viewport, the box rendered at ~544px tall
   inside an 844px viewport, leaving `justify-content: center` centering
   content within only the top 64% of the screen — reading as "stuck near
   the top," not centered. 100dvh (with a 100vh fallback for older engines)
   sizes directly off the real viewport instead of an ancestor chain. */
.onboarding-screen {
  display: flex; flex-direction: column; align-items: center; text-align: center; justify-content: center;
  min-height: 100vh; min-height: 100dvh;
  margin: -20px -18px -96px; padding: 40px 24px 32px;
}
/* Cover slide's photo, made genuinely edge-to-edge — founder review
   (2026-09-26): the margin-cancels-#screen's-padding trick above only
   approximately cancels it (the negative margins are fixed numbers,
   #screen's own padding changes per breakpoint), leaving a thin strip of
   the plain cream background visible around the photo on some screen
   widths, which read as "an obvious blank/negative space around the
   photo." `position: fixed; inset: 0` sizes straight off the real
   viewport instead, the same fix already applied to #splash and the
   modals/sheets, so the photo always reaches every edge with nothing
   showing around it. */
.onboarding-screen--photo {
  position: fixed; inset: 0; margin: 0; z-index: 20;
  background-image: linear-gradient(180deg, rgba(253,243,231,0.1) 0%, rgba(var(--ink-rgb),0.5) 74%, rgba(var(--ink-rgb),0.8) 100%), url('../assets/bunnybaby.jpeg');
  background-size: cover; background-position: center 28%; padding-bottom: 56px;
}
.onboarding-screen:not(.onboarding-screen--photo) { background: var(--cream); }
.onboarding-screen__logo { width: 200px; max-width: 70%; margin-bottom: 8px; filter: brightness(0) invert(1) drop-shadow(0 4px 14px rgba(0,0,0,0.35)); }
.onboarding-screen .eyebrow { color: var(--cream); background: rgba(255,255,255,0.18); padding: 4px 12px; border-radius: var(--radius-full); }
.onboarding-screen p.tagline { font-style: italic; color: var(--cream); margin: 0 0 18px; font-family: var(--font-heading); font-size: 1.05rem; }
.onboarding-screen p.desc { line-height: 1.6; margin: 0 0 28px; }
.onboarding-screen--photo p.desc { color: rgba(253,243,231,0.92); }
.onboarding-slide__icon {
  width: 92px; height: 92px; border-radius: 50%; display: flex; align-items: center; justify-content: center;
  margin: 0 auto 24px; background: linear-gradient(135deg, var(--sky-blue), var(--blush-pink));
  box-shadow: var(--shadow-md);
}
.onboarding-dots { display: flex; gap: 7px; justify-content: center; margin: 20px 0; }
.onboarding-dot { width: 7px; height: 7px; border-radius: 50%; background: rgba(var(--ink-rgb),0.2); cursor: pointer; transition: all .2s; }
.onboarding-screen--photo .onboarding-dot { background: rgba(255,255,255,0.4); }
.onboarding-dot.active { width: 22px; border-radius: var(--radius-full); background: var(--brown); }
.onboarding-screen--photo .onboarding-dot.active { background: var(--white); }
/* Cover-slide headline — still bolder than the later slides' body copy
   (serif vs. sans, heavier weight), but sized down from the original
   oversized 1.85rem treatment per explicit request. */
.onboarding-screen__headline {
  font-family: var(--font-heading); font-weight: 700; font-size: 1.35rem; line-height: 1.25;
  letter-spacing: -0.01em; margin: 0 0 20px; max-width: 300px;
}
.onboarding-screen--photo .onboarding-screen__headline { color: var(--white); text-shadow: 0 2px 12px rgba(0,0,0,0.25); }
.onboarding-fine-chip {
  display: inline-block; font-size: 0.7rem; padding: 8px 18px; border-radius: var(--radius-full);
  background: rgba(255,255,255,0.16); backdrop-filter: blur(6px);
}
.onboarding-actions { display: flex; justify-content: space-between; align-items: center; width: 100%; gap: 12px; }
.onboarding-actions .btn--primary { flex: 1; }
.onboarding-actions .btn--full-cta { flex: 1; }
/* Circular icon-only "Next" button on non-final slides, replacing the wide
   text pill — matches the export's arrow-in-a-circle affordance and reads
   lighter against the photo background than a solid blue bar. */
.onboarding-arrow-btn {
  width: 52px; height: 52px; border-radius: 50%; flex-shrink: 0; border: none; cursor: pointer;
  background: var(--sky-blue); color: var(--white); display: flex; align-items: center; justify-content: center;
  box-shadow: 0 6px 16px rgba(var(--sky-blue-rgb),0.35); transition: transform .15s;
}
.onboarding-arrow-btn:active { transform: scale(0.92); }
.onboarding-skip-btn {
  background: none; border: none; cursor: pointer; font-family: var(--font-body); font-weight: 600;
  font-size: 0.85rem; color: var(--brown-mid); padding: 10px 4px;
}
.onboarding-screen--photo .onboarding-skip-btn { color: rgba(255,255,255,0.85); }
.onboarding-screen--photo .btn--tertiary { background: rgba(255,255,255,0.15); color: var(--white); border-color: rgba(255,255,255,0.5); }
.onboarding-screen .fine-print { color: rgba(253,243,231,0.7) !important; }

/* Form */
.field { margin-bottom: 16px; text-align: left; }
.field label { display: block; font-size: 0.8rem; font-weight: 600; color: var(--text-medium); margin-bottom: 6px; }
/* Pill-shaped inputs (DESIGN.md "Input Fields": fully rounded, thin warm
   border, solid accent border + soft glow on focus) — previously --radius-md
   like a card, one of the more visible "still looks like the old app"
   tells since every Stitch form screen shows pill inputs. */
.field input, .field select, .field textarea {
  width: 100%; padding: 13px 20px; border-radius: var(--radius-full); border: 1.5px solid rgba(var(--ink-rgb),0.15);
  font-family: var(--font-body); font-size: 0.95rem; background: var(--white); color: var(--brown);
  transition: border-color .15s, box-shadow .15s;
}
/* textarea needs its own radius (a fully pill-rounded multi-line box looks
   broken) and isn't display:block by default — everything else inherits
   from the shared rule above, including dark-mode background/color, which
   plain <textarea> elements don't get automatically (no browser default
   respects theme vars). Found via dark-mode testing: both this file's
   Describe-It box and parentWellness.js's check-in note / journal entry
   textareas were rendering a hardcoded white box in dark mode before this. */
.field textarea { border-radius: var(--radius-md); display: block; }
.field textarea:focus { border-color: var(--sky-blue); box-shadow: 0 0 0 3px rgba(var(--sky-blue-rgb),0.18); }
.field select {
  appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%237d6857' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 18px center; background-size: 16px; padding-right: 42px;
}
.field input:focus, .field select:focus { border-color: var(--sky-blue); box-shadow: 0 0 0 3px rgba(var(--sky-blue-rgb),0.18); }
/* Error state — pairs with an inline message rendered right under the field,
   not just a red border with no explanation (Forms & Feedback best
   practice: error near the field, not only at the top of the form). */
.field--error input, .field--error select, .field--error textarea { border-color: var(--critical); }
.field--error input:focus, .field--error select:focus, .field--error textarea:focus { box-shadow: 0 0 0 3px rgba(var(--critical-rgb),0.16); }
.field__error { font-size: 0.76rem; color: var(--critical); margin-top: 6px; }

/* Tabs */
.tabs { display: flex; gap: 6px; margin-bottom: 16px; background: var(--cream-dark); border-radius: var(--radius-full); padding: 4px; }
.tab { flex: 1; text-align: center; padding: 8px 4px; border-radius: var(--radius-full); font-size: 0.78rem; font-weight: 600; cursor: pointer; color: var(--text-medium); transition: background .15s ease-out, color .15s ease-out, transform .1s ease-out; }
.tab.active { background: var(--white); color: var(--brown); box-shadow: var(--shadow-sm); }
.tab:active { transform: scale(0.95); }

canvas.sparkline { width: 100%; height: 60px; display: block; }

/* Stat row (Garmin/Apple Health data-density pattern) */
.stat-row { display: flex; margin-top: 16px; border-top: 1px solid rgba(var(--ink-rgb),0.08); padding-top: 14px; }
.stat-row__item { flex: 1; text-align: center; border-right: 1px solid rgba(var(--ink-rgb),0.08); }
.stat-row__item:last-child { border-right: none; }
.stat-row__value { font-family: var(--font-heading); font-size: 1.2rem; font-weight: 700; color: var(--brown); letter-spacing: -0.015em; font-variant-numeric: tabular-nums; }
.stat-row__label { font-size: 0.7rem; color: var(--text-muted); text-transform: uppercase; letter-spacing: .05em; margin-top: 2px; }

/* Info grid — compact multi-column label/value pairs (ui.js's infoRow()).
   Replaces single-column stacked rows across Baby Basics, Vital Detail's
   Normal Range + About cards, and Reports' per-vital summaries, so a tall
   card of one-per-line facts becomes a couple of grid rows instead. */
.info-grid { display: grid; gap: 14px 16px; margin: 8px 0 4px; }
.info-grid__label { font-size: 0.7rem; color: var(--text-muted); text-transform: uppercase; letter-spacing: .04em; margin-bottom: 3px; }
.info-grid__value { font-size: 0.9rem; font-weight: 600; color: var(--brown); }

/* Disclosure — short summary line + native <details> "Learn more" toggle
   (ui.js's disclosure()). Keeps cards scannable instead of carrying a full
   paragraph up front. */
.disclosure__summary { font-size: 0.85rem; color: var(--text-medium); line-height: 1.5; margin: 0; }
.disclosure__details { margin-top: 8px; }
.disclosure__toggle {
  display: inline-flex; align-items: center; gap: 4px; font-size: 0.78rem; font-weight: 700;
  color: var(--brown-mid); cursor: pointer; list-style: none; user-select: none;
}
.disclosure__toggle::-webkit-details-marker { display: none; }
.disclosure__toggle::after { content: "›"; display: inline-block; transition: transform .15s ease-out; font-size: 0.95rem; line-height: 1; }
.disclosure__details[open] .disclosure__toggle::after { transform: rotate(90deg); }
.disclosure__detail { font-size: 0.82rem; color: var(--text-medium); line-height: 1.6; margin-top: 8px; animation: fadeIn .2s ease-out; }

/* Glass — sparing "expensive" chrome accent (topbar, bottom nav, modal/sheet
   already wired directly; available here for the rare content moment that
   should feel frosted rather than solid). Not the default card treatment. */
.glass {
  background: var(--glass-surface) !important;
  border: 1px solid var(--glass-border) !important;
  backdrop-filter: blur(var(--glass-blur)) saturate(1.4);
  -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(1.4);
}

/* Empty state — icon gets a soft tinted circular backdrop (matches the
   "icon in a soft blob" motif used throughout the export) and an optional
   title reads as a real heading, not just a bolded message line. Still just
   one honest sentence, no fabricated progress metrics. */
.empty-state { text-align: center; padding: 48px 24px; }
.empty-state__icon {
  display: inline-flex; align-items: center; justify-content: center; width: 76px; height: 76px;
  border-radius: 50%; background: var(--sky-blue-tint); color: var(--sky-blue); margin: 0 auto 18px;
}
.empty-state__title { font-family: var(--font-heading); font-weight: 700; font-size: 1.2rem; color: var(--brown); margin-bottom: 6px; }
.empty-state__message { font-size: 0.85rem; color: var(--text-muted); line-height: 1.55; max-width: 280px; margin: 0 auto; }

/* Error state — no equivalent existed anywhere in the app before; genuinely
   new pattern for real error conditions (e.g. DeviceInterface signal loss /
   band removed), not a decorative variant of the empty state. Warning-toned
   rather than critical-toned by default since most of this app's "error"
   conditions are recoverable connectivity states, not emergencies. */
.error-state { text-align: center; padding: 40px 20px; }
.error-state__icon { display: flex; justify-content: center; margin-bottom: 10px; color: var(--warning); }
.error-state__title { font-family: var(--font-heading); font-weight: 700; font-size: 1rem; color: var(--brown); margin-bottom: 4px; }
.error-state__message { font-size: 0.82rem; color: var(--text-medium); line-height: 1.5; max-width: 280px; margin: 0 auto; }
.error-state__retry { margin-top: 16px; }

.list-item { display: flex; gap: 12px; padding: 14px 0; border-bottom: 1px solid rgba(var(--ink-rgb),0.06); }
.list-item:last-child { border-bottom: none; }

/* Settings — grouped iOS-style list (Apple HIG pattern) */
.settings-group { margin-bottom: 22px; }
.settings-group__title { font-size: 0.7rem; font-weight: 700; text-transform: uppercase; letter-spacing: .06em; color: var(--text-muted); padding: 0 4px 8px; }
.settings-row {
  display: flex; align-items: center; gap: 13px; background: var(--white); padding: 15px 16px;
  border-bottom: 1px solid rgba(var(--ink-rgb),0.06); font-size: 0.9rem;
}
.settings-row--clickable { cursor: pointer; transition: background .15s ease-out, transform .1s ease-out; }
.settings-row--clickable:active { background: var(--cream); transform: scale(0.99); }
@media (hover: hover) and (pointer: fine) {
  .settings-row--clickable:hover { background: rgba(var(--ink-rgb),0.025); }
}
.settings-row__label { flex: 1; color: var(--brown); }
.settings-row__label--danger { color: var(--critical); }
.settings-row__value { color: var(--text-muted); font-size: 0.82rem; }
.settings-row__chevron { color: var(--text-muted); font-size: 1.1rem; }
.settings-back { background: none; border: none; color: var(--brown-mid); font-weight: 600; font-size: 0.88rem; padding: 8px 4px; margin-bottom: 8px; cursor: pointer; }

/* Reset native <button> chrome so a real, keyboard-operable <button> used
   for clickable rows/cards (see ui.js's row()/card() builders) is visually
   identical to the plain <div> it replaces. */
button.settings-row, button.card, button.list-item, .profile-row-open {
  width: 100%; text-align: left; font-family: var(--font-body); border: none; cursor: pointer; appearance: none;
}
button.settings-row { background: var(--white); }
button.card { background: var(--surface); display: block; }
.profile-row-open { background: none; padding: 0; color: inherit; }

/* Modal — centered confirmation dialog + backdrop. Genuinely new: before
   this, "Reset Simulation" and "Forget Device" executed immediately with no
   confirmation at all — a real gap for destructive actions, not just a
   missing design-system entry.
   `position: fixed` (not absolute) for the same reason as .bottom-nav and
   .tour-root above: dashboard content routinely exceeds one viewport, so
   #app-shell's height grows past 100vh and the page scrolls natively — an
   `absolute` backdrop anchors to that tall content-height box instead of the
   visible viewport, rendering the dialog off-screen below the fold unless
   the page happens to already be scrolled to the very bottom. Desktop
   preview overrides back to absolute (min-width:431px block) since there
   #app-shell is a centered frame narrower than the browser window. */
.modal-backdrop {
  position: fixed; inset: 0; background: rgba(0,0,0,0.45); z-index: 100;
  display: flex; align-items: center; justify-content: center; padding: 24px;
  animation: overlayFadeIn .2s ease-out;
  backdrop-filter: blur(4px); -webkit-backdrop-filter: blur(4px);
}
.modal {
  background: var(--glass-surface); border-radius: var(--radius-lg); padding: 28px 24px 24px;
  max-width: 320px; width: 100%; box-shadow: var(--shadow-lg); text-align: center;
  border: 1px solid var(--glass-border);
  backdrop-filter: blur(var(--glass-blur)) saturate(1.4);
  -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(1.4);
  animation: modalIn .25s cubic-bezier(.2,.8,.2,1);
}
.modal__icon { display: flex; justify-content: center; margin-bottom: 14px; }
.modal__title { font-family: var(--font-heading); font-size: var(--text-lg); font-weight: 700; color: var(--brown); margin-bottom: 8px; }
.modal__message { font-size: var(--text-base); color: var(--text-medium); line-height: 1.5; margin-bottom: 20px; }
.modal__actions { display: flex; gap: 10px; }
.modal__actions .btn { flex: 1; }

/* Bottom sheet — same confirmation pattern, slides up from the bottom edge.
   `position: fixed` for the same reason as .modal-backdrop above (desktop
   override lives in the same min-width:431px block). */
.sheet-backdrop {
  position: fixed; inset: 0; background: rgba(0,0,0,0.4); z-index: 100;
  display: flex; align-items: flex-end; animation: overlayFadeIn .2s ease-out;
}
.sheet {
  background: var(--glass-surface); border-radius: var(--radius-xl) var(--radius-xl) 0 0;
  padding: 10px 22px calc(26px + env(safe-area-inset-bottom)); width: 100%; text-align: center;
  border: 1px solid var(--glass-border); border-bottom: none;
  backdrop-filter: blur(var(--glass-blur)) saturate(1.4);
  -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(1.4);
  box-shadow: var(--shadow-lg); animation: sheetIn .28s cubic-bezier(.2,.8,.2,1);
}
.sheet__handle { width: 36px; height: 4px; border-radius: var(--radius-full); background: rgba(var(--ink-rgb),0.15); margin: 0 auto 16px; }
.sheet .modal__icon { margin-top: 4px; }
.sheet__title { font-family: var(--font-heading); font-size: var(--text-lg); font-weight: 700; color: var(--brown); margin-bottom: 8px; }
.sheet__message { font-size: var(--text-base); color: var(--text-medium); line-height: 1.5; margin-bottom: 20px; }
.sheet__actions { display: flex; flex-direction: column; gap: 10px; }

@keyframes overlayFadeIn { from { opacity: 0; } to { opacity: 1; } }
@keyframes modalIn { from { opacity: 0; transform: scale(0.92) translateY(8px); } to { opacity: 1; transform: scale(1) translateY(0); } }
@keyframes sheetIn { from { transform: translateY(100%); } to { transform: translateY(0); } }

/* Dashboard Tour — first-run spotlight walkthrough (dashboardTour.js).
   `position: fixed` (not absolute) for the same reason as .bottom-nav above:
   dashboard content is routinely taller than one viewport, so #app-shell's
   own height grows past 100vh and the page scrolls natively. An `absolute`
   tour-card would anchor to #app-shell's tall content-height bottom edge and
   render off-screen below the fold instead of the visible viewport. Desktop
   preview overrides back to absolute (min-width:431px block, alongside
   .bottom-nav) since there #app-shell is a centered frame narrower than the
   browser window. The "spotlight" is a single element whose oversized
   box-shadow doubles as the dim backdrop — no separate darkening layer
   needed, so the cut-out target never has a seam. */
.tour-root { position: fixed; inset: 0; z-index: 160; animation: overlayFadeIn .2s ease-out; }
.tour-root--closing { opacity: 0; transition: opacity .18s ease-out; }
.tour-backdrop { position: absolute; inset: 0; pointer-events: auto; }
/* Position/size are set directly (no transition on them — animating
   top/left/width/height is layout-thrashing). The step-to-step "move" is
   done as a fade-out/reposition/fade-in crossfade in dashboardTour.js
   instead, so only `opacity` (a compositor-only property) ever animates. */
.tour-spot {
  position: absolute; border-radius: 16px; pointer-events: none; opacity: 0;
  box-shadow: 0 0 0 9999px rgba(0,0,0,0.55), 0 0 0 2px rgba(255,255,255,0.7);
  transition: opacity .2s ease-out;
}
.tour-card {
  position: absolute; left: 50%; bottom: calc(20px + env(safe-area-inset-bottom));
  transform: translateX(-50%); width: calc(100% - 40px); max-width: 360px;
  background: var(--glass-surface); border: 1px solid var(--glass-border);
  border-radius: var(--radius-xl); padding: 18px 20px calc(16px + env(safe-area-inset-bottom));
  box-shadow: var(--shadow-lg); text-align: left; pointer-events: auto;
  backdrop-filter: blur(var(--glass-blur)) saturate(1.4); -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(1.4);
  animation: sheetIn .3s cubic-bezier(.2,.8,.2,1);
}
.tour-card__eyebrow { font-size: 0.7rem; font-weight: 700; text-transform: uppercase; letter-spacing: .06em; color: var(--text-muted); }
.tour-card__title { font-family: var(--font-heading); font-size: 1.05rem; font-weight: 700; color: var(--brown); margin-top: 4px; }
.tour-card__body { font-size: 0.85rem; color: var(--text-medium); line-height: 1.5; margin-top: 6px; }
.tour-card__dots { display: flex; gap: 5px; margin-top: 14px; }
.tour-dot { width: 6px; height: 6px; border-radius: 50%; background: rgba(var(--ink-rgb),0.15); transition: background .2s, width .2s; }
.tour-dot--active { background: var(--brown); width: 16px; border-radius: var(--radius-full); }
.tour-card__actions { display: flex; justify-content: space-between; align-items: center; margin-top: 16px; gap: 10px; }
.tour-card__actions .btn--primary { flex: 1; }

/* Toggle switch (visual only where disabled — stub prototype controls) */
.toggle { position: relative; display: inline-block; width: 42px; height: 24px; }
.toggle input { opacity: 0; width: 0; height: 0; }
.toggle__track { position: absolute; inset: 0; background: rgba(var(--ink-rgb),0.15); border-radius: var(--radius-full); transition: background .2s; }
.toggle__track::before { content: ""; position: absolute; width: 18px; height: 18px; left: 3px; top: 3px; background: var(--toggle-knob); border-radius: 50%; transition: transform .2s; box-shadow: var(--shadow-sm); }
.toggle input:checked + .toggle__track { background: var(--sky-blue); }
.toggle input:checked + .toggle__track::before { transform: translateX(18px); }

/* On the desktop preview, #app-shell is a centered, narrower-than-viewport
   frame — a viewport-fixed nav/overlay would ignore that frame and pin to
   the full browser window instead. This has to come after each element's
   own base `position: fixed` rule (.bottom-nav, .tour-root, .modal-backdrop,
   .sheet-backdrop) to actually win the cascade — an earlier copy of this
   exact override, near the top of the file, was silently dead for that
   reason (equal specificity, later unconditional rule always won regardless
   of viewport width), so every desktop preview screenshot showed the nav/
   modals bleeding past the phone frame to the edge of the browser window. */
@media (min-width: 431px) {
  .bottom-nav { position: absolute; }
  .tour-root { position: absolute; }
  .modal-backdrop { position: absolute; }
  .sheet-backdrop { position: absolute; }
  .checkin-nudge { position: absolute; }
  .onboarding-screen--photo { position: absolute; }
}

/* Insights: five domains, progressive disclosure (founder review round 3). */
.insights-intro { font-size: 0.86rem; color: var(--text-medium); margin: 2px 2px 14px; line-height: 1.5; }
.domain-list { display: flex; flex-direction: column; gap: 10px; }
button.card.domain-row { display: flex; align-items: center; gap: 12px; text-align: left; margin-bottom: 0; width: 100%; }
.domain-row .icon-badge { width: 38px; height: 38px; flex-shrink: 0; }
.domain-row__text { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.domain-row__label { font-size: 0.7rem; font-weight: 700; text-transform: uppercase; letter-spacing: .05em; color: var(--text-muted); }
.domain-row__preview { font-family: var(--font-heading); font-weight: 700; font-size: 0.98rem; color: var(--brown); margin-top: 2px; line-height: 1.3; }
.domain-row__preview--muted { color: var(--text-muted); font-weight: 600; }
.domain-row__chev { color: var(--text-muted); flex-shrink: 0; display: inline-flex; }
.insights-report-link {
  display: flex; align-items: center; justify-content: center; flex-wrap: wrap; gap: 6px; width: 100%;
  background: none; border: none; cursor: pointer; padding: 16px 4px 4px;
  font-size: 0.78rem; color: var(--text-muted); font-family: var(--font-body); text-align: center;
}
.insights-report-link span { color: var(--sky-blue); font-weight: 700; }
.domain-detail__head { display: flex; align-items: center; gap: 12px; margin: 4px 0 14px; }
.domain-detail__head .icon-badge { width: 44px; height: 44px; flex-shrink: 0; }
.domain-detail__title { font-size: 1.25rem; line-height: 1.25; margin-top: 2px; }
.domain-detail .insight-card__section:first-child { margin-top: 0; }
.domain-detail .insight-card__section { margin-top: 14px; }
.domain-detail .insight-card__section p { font-size: 0.88rem; }

/* You tab (founder review round 3, items 15-18). */
.checkin-card { padding-bottom: 16px; }
.checkin-card__question { font-family: var(--font-heading); font-size: 1.02rem; font-weight: 700; line-height: 1.35; margin: 8px 0 12px; }
.checkin-card--quiet { display: flex; flex-direction: column; align-items: flex-start; gap: 10px; }
.checkin-card__quiet { font-size: 0.86rem; color: var(--text-medium); line-height: 1.5; }
.checkin-log__turn--you .checkin-log__text { color: var(--text-medium); }
.chip { color: var(--brown); }
.parent-insight { margin-bottom: 10px; }
.parent-insight__title { font-family: var(--font-heading); font-weight: 700; font-size: 0.98rem; line-height: 1.35; }
.parent-insight__body { font-size: 0.84rem; color: var(--text-medium); line-height: 1.5; margin-top: 6px; }
.parent-insight--empty .parent-insight__body { margin-top: 0; }
.parent-insight__action {
  display: flex; gap: 7px; align-items: flex-start; margin-top: 10px; padding: 10px 12px;
  background: var(--sage-green-tint); border-radius: var(--radius-md); font-size: 0.82rem; line-height: 1.45;
}
.parent-insight__action svg { flex-shrink: 0; margin-top: 2px; color: var(--sage-green); }
.history-list { padding-top: 6px; padding-bottom: 6px; }
.history-row { display: flex; flex-direction: column; gap: 2px; padding: 10px 0; border-bottom: 1px solid rgba(var(--ink-rgb),0.07); }
.history-row:last-child { border-bottom: none; }
.history-row__day { font-size: 0.7rem; font-weight: 700; text-transform: uppercase; letter-spacing: .05em; color: var(--text-muted); display: flex; align-items: center; gap: 6px; }
.history-row__text { font-size: 0.84rem; line-height: 1.45; }
.support-card { display: flex; flex-direction: column; gap: 14px; }
.support-row { display: flex; gap: 12px; align-items: flex-start; text-align: left; background: none; border: none; padding: 0; font-family: var(--font-body); color: inherit; }
button.support-row { cursor: pointer; }
button.support-row:disabled { cursor: default; opacity: 0.8; }
.support-row .icon-badge { width: 34px; height: 34px; flex-shrink: 0; }
.support-row__text { display: flex; flex-direction: column; }
.support-row__title { font-weight: 700; font-size: 0.88rem; }
.support-row__sub { font-size: 0.78rem; color: var(--text-muted); margin-top: 2px; line-height: 1.45; }
/* iOS Safari zooms the page on focus for inputs under 16px. */
input, select, textarea { font-size: max(16px, 1em); }

/* Welcome screen (founder review round 3): the real band photo edge to
   edge across the top, framed on the band, above a clean white sheet with
   the logo and three points. Fixed to the viewport so nothing shows around
   the photo; the sheet scrolls on short phones (iPhone SE). */
.onboarding-cover {
  position: fixed; inset: 0; z-index: 20; display: flex; flex-direction: column;
  background: var(--surface); overflow-y: auto; -webkit-overflow-scrolling: touch;
}
.onboarding-cover__photo { flex: 0 0 auto; height: 38dvh; min-height: 220px; max-height: 360px; overflow: hidden; background: #efe9e2; }
.onboarding-cover__photo img { display: block; width: 100%; height: 100%; object-fit: cover; object-position: 26% 62%; }
.onboarding-cover__sheet {
  flex: 1 0 auto; position: relative; margin-top: -22px; background: var(--surface);
  border-radius: 24px 24px 0 0; padding: 22px 24px calc(20px + env(safe-area-inset-bottom));
  display: flex; flex-direction: column; align-items: center; text-align: center;
}
.onboarding-cover__logo { width: 170px; max-width: 60%; height: auto; }
.onboarding-cover__headline { font-family: var(--font-heading); font-weight: 700; font-size: 1.12rem; line-height: 1.3; color: var(--brown); margin: 10px 0 16px; max-width: 310px; }
.onboarding-points { list-style: none; margin: 0 0 20px; padding: 0; display: flex; flex-direction: column; gap: 14px; text-align: left; width: 100%; max-width: 360px; }
.onboarding-point { display: flex; gap: 12px; align-items: flex-start; }
.onboarding-point .icon-badge { width: 36px; height: 36px; flex-shrink: 0; }
.onboarding-point strong { display: block; font-size: 0.9rem; color: var(--brown); }
.onboarding-point span span { display: block; font-size: 0.8rem; color: var(--text-medium); line-height: 1.45; margin-top: 2px; }
.onboarding-cover .btn--full { width: 100%; max-width: 360px; }
.onboarding-cover__fine { font-size: 0.7rem; color: var(--text-muted); margin-top: 12px; }
@media (min-width: 431px) { .onboarding-cover { position: absolute; } }

/* Add your baby (founder review round 3, items 4-5): three fields, fits an
   iPhone screen without scrolling. */
.new-profile { display: flex; flex-direction: column; padding-top: max(24px, 6dvh); max-width: 420px; margin: 0 auto; }
.new-profile__icon {
  width: 68px; height: 68px; border-radius: 50%; display: flex; align-items: center; justify-content: center;
  margin: 0 auto 16px; background: linear-gradient(135deg, var(--sky-blue), var(--blush-pink)); box-shadow: var(--shadow-md);
}
.new-profile h2 { text-align: center; }
.new-profile__sub { text-align: center; color: var(--text-medium); font-size: 0.86rem; margin: 6px 0 22px; line-height: 1.45; }
.new-profile .btn--full { margin-top: 8px; }
.choice-row { display: flex; gap: 8px; }
.choice {
  flex: 1; min-height: 44px; padding: 8px 6px; border-radius: var(--radius-full); cursor: pointer;
  border: 1.5px solid rgba(var(--ink-rgb),0.15); background: var(--surface); color: var(--brown);
  font-family: var(--font-body); font-size: 0.84rem; font-weight: 600; line-height: 1.2;
  transition: background .15s ease-out, border-color .15s ease-out, color .15s ease-out;
}
.choice.active { background: var(--sky-blue); border-color: var(--sky-blue); color: #fff; }

/* Pairing (founder review round 3, items 6-7). */
.pairing-wizard { max-width: 420px; margin: 0 auto; }
.pairing-wizard__progress { margin: 4px 0 8px; }
.pairing-wizard .breathing-circle--device { margin: 28px auto 20px; }
.pairing-wizard .breathing-circle__device-img { width: 78%; height: 78%; }
.pairing-wizard__title { font-size: 1.45rem; line-height: 1.2; }
.pairing-wizard__body { color: var(--text-medium); font-size: 0.9rem; line-height: 1.5; margin: 10px auto 20px; max-width: 320px; }
.pairing-wizard__note { font-size: 0.72rem; color: var(--text-muted); margin-top: 12px; line-height: 1.45; }
/* Scanning: a Bluetooth glyph with soft rings expanding outward. */
.scan-pulse { position: relative; width: 168px; height: 168px; margin: 28px auto 20px; display: flex; align-items: center; justify-content: center; }
.scan-pulse__core {
  position: relative; z-index: 1; width: 76px; height: 76px; border-radius: 50%;
  background: var(--sky-blue); color: #fff; display: flex; align-items: center; justify-content: center;
  box-shadow: 0 8px 22px rgba(var(--sky-blue-rgb),0.35);
}
.scan-pulse__ring {
  position: absolute; inset: 0; margin: auto; width: 76px; height: 76px; border-radius: 50%;
  border: 2px solid var(--sky-blue); opacity: 0; animation: scanRing 2.4s cubic-bezier(.2,.6,.3,1) infinite;
}
.scan-pulse__ring:nth-child(2) { animation-delay: .8s; }
.scan-pulse__ring:nth-child(3) { animation-delay: 1.6s; }
@keyframes scanRing { 0% { transform: scale(1); opacity: .55; } 100% { transform: scale(2.2); opacity: 0; } }
@media (prefers-reduced-motion: reduce) { .scan-pulse__ring { animation: none; opacity: .2; } }
.found-card { display: flex; align-items: center; gap: 14px; text-align: left; margin: 18px 0 16px; }
.found-card img { width: 56px; height: 56px; object-fit: contain; flex-shrink: 0; }
.found-card__title { font-weight: 700; font-size: 0.98rem; }
.found-card__sub { font-size: 0.8rem; color: var(--text-muted); margin-top: 2px; }
.band-swatches { display: grid; grid-template-columns: repeat(5, 1fr); gap: 6px; }
.band-swatch {
  display: flex; flex-direction: column; align-items: center; gap: 4px; padding: 8px 2px; min-height: 44px;
  border-radius: var(--radius-md); border: 1.5px solid transparent; background: none; cursor: pointer;
  font-family: var(--font-body); font-size: 0.7rem; font-weight: 600; color: var(--text-muted); line-height: 1.15;
  transition: border-color .15s ease-out, background .15s ease-out;
}
.band-swatch img { width: 48px; height: 48px; object-fit: contain; }
.band-swatch.active { border-color: var(--sky-blue); background: var(--sky-blue-tint); color: var(--brown); }
.paired-check {
  width: 76px; height: 76px; border-radius: 50%; margin: 30px auto 18px; background: var(--sage-green); color: #fff;
  display: flex; align-items: center; justify-content: center; box-shadow: 0 8px 22px rgba(var(--ink-rgb),0.14);
}
.paired-card { display: flex; flex-direction: column; gap: 12px; text-align: left; margin-top: 18px; }
.paired-card__row { display: flex; align-items: center; justify-content: space-between; font-size: 0.9rem; }
.paired-card__ok { display: inline-flex; align-items: center; gap: 6px; font-weight: 700; color: var(--sage-green); }
.paired-card__ok .status-dot { width: 8px; height: 8px; box-shadow: none; background: var(--sage-green); }
.paired-card__note { font-size: 0.76rem; color: var(--text-muted); line-height: 1.45; }

/* iPhone audit (founder review round 3, item 4): comfortable tap targets. */
.tab { min-height: 40px; display: flex; align-items: center; justify-content: center; }
.topbar .profile-switch { min-height: 40px; padding: 8px 16px; }
.dash-header__baby { min-height: 36px; padding-right: 8px; }
.onboarding-screen { margin: 0 auto; max-width: 440px; padding: 28px 4px 20px; min-height: calc(100dvh - 140px); }
/* Short phones (iPhone SE and similar): keep the welcome CTA above the fold. */
@media (max-height: 720px) {
  .onboarding-cover__photo { height: 27dvh; min-height: 160px; }
  .onboarding-cover__sheet { padding-top: 16px; }
  .onboarding-cover__logo { width: 140px; }
  .onboarding-cover__headline { font-size: 1.02rem; margin: 6px 0 12px; }
  .onboarding-points { gap: 9px; margin-bottom: 14px; }
  .onboarding-point span span { font-size: 0.76rem; }
  .onboarding-cover__fine { margin-top: 8px; }
}
/* Desktop phone-frame preview: the shell grows with its content, so an
   absolutely positioned pop-up would sit at the bottom of the whole page,
   off-screen. Pin it to the visible viewport, centered on the frame. */
@media (min-width: 431px) {
  .checkin-nudge { position: fixed; left: 50%; right: auto; width: min(386px, calc(100vw - 28px)); margin-left: calc(min(386px, calc(100vw - 28px)) / -2); bottom: 24px; }
}

/* Health Report: next checkup, per-period views, bar charts. */
button.card.visit-card--closed { display: flex; align-items: center; gap: 12px; width: 100%; text-align: left; }
.visit-card .icon-badge { width: 38px; height: 38px; flex-shrink: 0; }
.visit-card__head { display: flex; align-items: center; gap: 12px; width: 100%; background: none; border: none; padding: 0; text-align: left; cursor: pointer; font-family: var(--font-body); color: inherit; }
.visit-card__text { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.visit-card__title { font-family: var(--font-heading); font-weight: 700; font-size: 1.02rem; color: var(--brown); margin-top: 2px; }
.visit-card__when { font-size: 0.76rem; color: var(--text-muted); margin-top: 2px; }
.visit-card__cta { display: inline-flex; align-items: center; gap: 2px; font-size: 0.78rem; font-weight: 700; color: var(--sky-blue); flex-shrink: 0; }
.visit-section { margin-top: 16px; padding-top: 14px; border-top: 1px solid rgba(var(--ink-rgb),0.07); }
.visit-section__label { display: flex; align-items: center; gap: 5px; font-size: 0.7rem; font-weight: 700; text-transform: uppercase; letter-spacing: .05em; color: var(--text-muted); margin-bottom: 6px; }
.visit-section ul, .visit-section ol, .report-list { margin: 0; padding-left: 18px; display: flex; flex-direction: column; gap: 6px; font-size: 0.84rem; line-height: 1.45; color: var(--text-medium); }
.visit-section__vax { font-size: 0.82rem; line-height: 1.5; color: var(--text-medium); margin-top: 10px; }
.visit-section__vax strong { color: var(--brown); }
.visit-section__note { font-size: 0.76rem; color: var(--text-muted); line-height: 1.45; margin-top: 8px; }
.visit-private { margin-top: 16px; padding: 12px 14px; border-radius: var(--radius-md); background: var(--blush-pink-tint); }
.visit-private p { font-size: 0.82rem; line-height: 1.5; color: var(--text-medium); }
.brief-row { display: flex; gap: 10px; align-items: flex-start; font-size: 0.84rem; line-height: 1.4; padding: 6px 0; }
.brief-row .icon-badge { width: 26px; height: 26px; flex-shrink: 0; }
.brief-row strong { color: var(--brown); }
.report-headline { font-family: var(--font-heading); font-weight: 700; font-size: 1rem; line-height: 1.35; color: var(--brown); margin-bottom: 4px; }
.report-insight { padding-top: 10px; margin-top: 10px; border-top: 1px solid rgba(var(--ink-rgb),0.07); }
.report-insight__title { font-weight: 700; font-size: 0.88rem; line-height: 1.4; }
.report-insight__detail { font-size: 0.82rem; color: var(--text-medium); line-height: 1.5; margin-top: 3px; }
.bar-chart__plot { position: relative; display: flex; align-items: flex-end; gap: 2px; height: 120px; border-bottom: 1px solid rgba(var(--ink-rgb),0.12); }
.bar-chart__band { position: absolute; left: 0; right: 0; background: var(--sage-green-tint); border-top: 1px dashed rgba(var(--ink-rgb),0.15); border-bottom: 1px dashed rgba(var(--ink-rgb),0.15); }
.bar-chart__col { position: relative; flex: 1; height: 100%; display: flex; align-items: flex-end; justify-content: center; background: none; border: none; padding: 0; cursor: pointer; }
.bar-chart__bar { display: block; width: 62%; max-width: 30px; border-radius: 4px 4px 0 0; transition: opacity .15s ease-out; }
.bar-chart__col.active .bar-chart__bar { opacity: 1; box-shadow: 0 0 0 2px var(--surface), 0 0 0 3px var(--brown); }
.bar-chart__plot:has(.active) .bar-chart__col:not(.active) .bar-chart__bar { opacity: .55; }
.bar-chart__labels { display: flex; gap: 2px; margin-top: 6px; }
.bar-chart__labels span { flex: 1; text-align: center; font-size: 0.7rem; color: var(--text-muted); white-space: nowrap; }
.bar-chart__readout { font-size: 0.76rem; color: var(--text-medium); margin-top: 8px; min-height: 2.4em; line-height: 1.45; }

/* Sleep Report, readable at a glance. */
.sleep-summary__headline { font-family: var(--font-heading); font-weight: 700; font-size: 1.35rem; line-height: 1.2; color: var(--brown); margin-top: 6px; }
.sleep-summary__sub { font-size: 0.82rem; color: var(--text-medium); line-height: 1.45; margin-top: 4px; }
.sleep-stats { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; margin-top: 16px; }
.sleep-stats div { background: var(--sky-blue-tint); border-radius: var(--radius-md); padding: 12px 8px; text-align: center; display: flex; flex-direction: column; gap: 3px; }
.sleep-stats strong { font-family: var(--font-heading); font-size: 1.4rem; color: var(--brown); line-height: 1; }
.sleep-stats span { font-size: 0.7rem; color: var(--text-medium); line-height: 1.25; }
.sleep-day__track { position: relative; height: 34px; border-radius: 10px; background: var(--cream-dark); overflow: hidden; }
.sleep-day__asleep { position: absolute; top: 0; bottom: 0; background: var(--sky-blue); }
.sleep-day__tick { position: absolute; top: 0; bottom: 0; width: 1px; background: rgba(255,255,255,0.7); }
.sleep-day__labels { position: relative; height: 16px; margin-top: 5px; }
.sleep-day__labels span { position: absolute; transform: translateX(-50%); font-size: 0.7rem; color: var(--text-muted); white-space: nowrap; }
.sleep-day__legend { display: flex; flex-wrap: wrap; gap: 14px; align-items: center; margin-top: 10px; font-size: 0.74rem; color: var(--text-medium); }
.sleep-day__legend span { display: inline-flex; align-items: center; gap: 6px; }
.sleep-day__key { display: inline-block; width: 12px; height: 12px; border-radius: 3px; background: var(--cream-dark); }
.sleep-day__key--asleep { background: var(--sky-blue); }
.sleep-day__range { margin-left: auto; color: var(--text-muted); }
.sleep-split { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-top: 16px; padding-top: 14px; border-top: 1px solid rgba(var(--ink-rgb),0.07); }
.sleep-split div { display: flex; flex-direction: column; gap: 4px; }
.sleep-split__label { display: inline-flex; align-items: center; gap: 5px; font-size: 0.72rem; color: var(--text-muted); }
.sleep-split strong { font-size: 0.95rem; color: var(--brown); }
.sleep-meaning__title { font-family: var(--font-heading); font-weight: 700; font-size: 1.02rem; color: var(--brown); line-height: 1.3; }
.sleep-meaning__body { font-size: 0.86rem; color: var(--text-medium); line-height: 1.55; margin-top: 6px; }
.sleep-try { display: flex; gap: 8px; align-items: flex-start; margin-top: 12px; padding: 12px; border-radius: var(--radius-md); background: var(--sage-green-tint); font-size: 0.84rem; line-height: 1.5; }
.sleep-try svg { flex-shrink: 0; margin-top: 3px; color: var(--sage-green); }
.sleep-try__label { display: block; font-size: 0.7rem; font-weight: 700; text-transform: uppercase; letter-spacing: .05em; color: var(--text-muted); margin-bottom: 2px; }
button.card.sleep-moment { display: flex; align-items: center; gap: 12px; width: 100%; text-align: left; background: linear-gradient(135deg, var(--sky-blue), var(--blush-pink)); color: #fff; }
.sleep-moment .icon-badge { width: 32px; height: 32px; flex-shrink: 0; background: rgba(255,255,255,0.25); color: #fff; }
.sleep-moment__text { flex: 1; display: flex; flex-direction: column; }
.sleep-moment__eyebrow { font-size: 0.7rem; font-weight: 700; text-transform: uppercase; letter-spacing: .06em; color: rgba(255,255,255,0.85); }
.sleep-moment__q { font-weight: 700; font-size: 0.86rem; margin-top: 3px; line-height: 1.35; }

/* Welcome screen spacing pass (founder: "too cramped, even out spacing").
   Three groups (brand, points, call to action) spread evenly down the
   sheet, with more breathing room between points. */
.onboarding-cover__photo { height: 28dvh; min-height: 180px; max-height: 300px; }
.onboarding-cover__sheet {
  justify-content: space-evenly; gap: 18px;
  padding: 26px 28px calc(20px + env(safe-area-inset-bottom));
}
.onboarding-cover__brand { display: flex; flex-direction: column; align-items: center; }
.onboarding-cover__logo { width: 150px; }
.onboarding-cover__headline { font-size: 1.14rem; line-height: 1.35; margin: 14px 0 0; max-width: 300px; }
.onboarding-points { gap: 18px; margin: 0; max-width: 340px; }
.onboarding-point { gap: 14px; align-items: center; }
.onboarding-point .icon-badge { width: 42px; height: 42px; }
.onboarding-point strong { font-size: 0.95rem; }
.onboarding-point span span { font-size: 0.82rem; line-height: 1.5; margin-top: 3px; }
.onboarding-cover__cta { width: 100%; max-width: 340px; display: flex; flex-direction: column; align-items: center; }
.onboarding-cover__cta .btn--full { width: 100%; }
.onboarding-cover__fine { margin-top: 12px; }
@media (max-height: 720px) {
  .onboarding-cover__photo { height: 24dvh; min-height: 140px; }
  .onboarding-cover__sheet { gap: 12px; padding-top: 22px; justify-content: space-between; }
  .onboarding-cover__logo { width: 124px; }
  .onboarding-cover__headline { font-size: 1.02rem; margin-top: 10px; }
  .onboarding-points { gap: 12px; }
  .onboarding-point .icon-badge { width: 36px; height: 36px; }
  .onboarding-point span span { font-size: 0.77rem; line-height: 1.4; }
}
/* Desktop preview: the shell grows with its content, so an absolutely
   positioned tour would sit at the bottom of the whole page. Pin it to the
   visible viewport, over the phone frame (430px wide, centered). */
@media (min-width: 431px) {
  .tour-root { position: fixed; top: 0; bottom: 0; left: 50%; right: auto; width: 430px; margin-left: -215px; }
}

/* Insights day picker: today, yesterday, and earlier days. */
.day-strip { display: flex; gap: 8px; overflow-x: auto; margin: 0 -16px 14px; padding: 2px 16px 6px; scrollbar-width: none; -webkit-overflow-scrolling: touch; }
.day-strip::-webkit-scrollbar { display: none; }
.day-chip {
  flex-shrink: 0; min-height: 40px; padding: 8px 14px; border-radius: var(--radius-full); cursor: pointer; white-space: nowrap;
  border: 1.5px solid rgba(var(--ink-rgb),0.14); background: var(--surface); color: var(--brown);
  font-family: var(--font-body); font-size: 0.82rem; font-weight: 600;
}
.day-chip.active { background: var(--brown); border-color: var(--brown); color: var(--surface); }

/* Check-in: multi-select "what's changed in you" and urgent tips. */
.chip-row--multi { gap: 8px; }
.chip--toggle.active { background: var(--brown); border-color: var(--brown); color: var(--surface); }
.checkin-multi-done { margin-top: 14px; }
.checkin-multi-done:disabled { opacity: .45; cursor: default; }
.checkin-log__tip--urgent { background: var(--blush-pink-tint); }
.checkin-log__tip--urgent svg { color: var(--blush-pink); }

/* Test scenarios: banner, guidance card, Settings screen. */
.demo-banner {
  display: flex; align-items: center; gap: 12px; margin-bottom: 14px; padding: 12px 14px;
  border-radius: var(--radius-lg); background: var(--warning-tint); border: 1.5px dashed rgba(var(--ink-rgb),0.18);
}
.demo-banner__icon { width: 32px; height: 32px; border-radius: 50%; flex-shrink: 0; display: flex; align-items: center; justify-content: center; background: rgba(255,255,255,0.6); color: var(--brown); }
.demo-banner__text { flex: 1; min-width: 0; }
.demo-banner__title { font-weight: 700; font-size: 0.86rem; color: var(--brown); }
.demo-banner__why { font-size: 0.76rem; color: var(--text-medium); line-height: 1.4; margin-top: 1px; }
.scenario-card { border-left: 4px solid var(--warning); }
.scenario-card--urgent { border-left-color: var(--critical); background: var(--blush-pink-tint); }
.scenario-card__title { font-family: var(--font-heading); font-weight: 700; font-size: 1.02rem; color: var(--brown); line-height: 1.3; margin-top: 6px; }
.scenario-card__body { font-size: 0.84rem; color: var(--text-medium); line-height: 1.5; margin-top: 6px; }
.scenario-card__watch { font-size: 0.8rem; color: var(--brown); line-height: 1.5; margin-top: 10px; padding-top: 10px; border-top: 1px solid rgba(var(--ink-rgb),0.08); }
.scenario-hint { font-size: 0.8rem; color: var(--text-medium); line-height: 1.5; }
.scenario-hint--miss { margin-top: 10px; color: var(--text-muted); }
.scenario-preview { margin-top: 12px; padding: 12px 14px; border-radius: var(--radius-md); background: var(--sky-blue-tint); }
.scenario-preview--urgent { background: var(--blush-pink-tint); }
.scenario-preview__title { font-weight: 700; font-size: 0.92rem; color: var(--brown); margin: 4px 0; }
.scenario-active { border-left: 4px solid var(--sage-green); }
.scenario-active__title { font-family: var(--font-heading); font-weight: 700; font-size: 1rem; color: var(--brown); margin: 4px 0; }

/* Final UX pass (2026-09-27): minimum 11px text, 40px+ tap targets,
   readable secondary text. */
.bottom-nav__item { font-size: 0.7rem; }
.disclosure__toggle { min-height: 40px; padding: 8px 0; }
.dash-header__baby { min-height: 40px; min-width: 48px; padding: 4px 10px 4px 0; }
.chip { min-height: 40px; }
.btn--sm { min-height: 40px; }
.report-vital-card__value span { color: var(--text-muted); }
.domain-row__label, .sleep-try__label, .sleep-moment__eyebrow, .sleep-day__labels span,
.visit-section__label, .insight-card__section-label, .history-row__day, .checkin-log__who,
.checkin-nudge__eyebrow, .tour-card__eyebrow, .stat-row__label, .bar-chart__labels span { font-size: 0.7rem; }
.brief-row__label { display: block; font-size: 0.7rem; font-weight: 700; text-transform: uppercase; letter-spacing: .05em; color: var(--text-muted); margin-bottom: 1px; }
/* Settings rows mix <div> and <button> tags, so :first-of-type/:last-of-type
   rounded each tag type separately and split groups visually. Round by
   position in the group instead. */
.settings-group .settings-row { border-radius: 0; border-bottom: 1px solid rgba(var(--ink-rgb),0.06); }
.settings-group .settings-group__title + .settings-row, .settings-group > .settings-row:first-child { border-radius: var(--radius-md) var(--radius-md) 0 0; }
.settings-group .settings-row:last-child { border-radius: 0 0 var(--radius-md) var(--radius-md); border-bottom: none; }
.settings-group .settings-group__title + .settings-row:last-child, .settings-group > .settings-row:only-child { border-radius: var(--radius-md); }
.card > .tabs:last-child, .card > .tabs:only-child { margin-bottom: 0; }
/* Dark mode: the white 60% wash behind tile icons turned them into blank
   bubbles (light icon on light circle). Use a subtle dark-friendly wash. */
[data-theme="dark"] .vital-tile .icon-badge,
[data-theme="dark"] .report-vital-card .icon-badge,
[data-theme="dark"] .demo-banner__icon { background: rgba(255,255,255,0.1); }
/* Tour card placement: moves to the top when it would cover the highlighted
   item, and centers for the welcome and finish steps. */
.tour-card--top { bottom: auto; top: calc(20px + env(safe-area-inset-top)); }
.tour-card--center { bottom: auto; top: 50%; transform: translate(-50%, -50%); }
@media (max-height: 720px) {
  .tour-card { padding: 14px 16px calc(12px + env(safe-area-inset-bottom)); }
  .tour-card__title { font-size: 0.98rem; }
  .tour-card__body { font-size: 0.8rem; line-height: 1.45; margin-top: 4px; }
  .tour-card__dots { margin-top: 10px; }
  .tour-card__actions { margin-top: 12px; }
}
.tour-spot--dim-only { box-shadow: 0 0 0 9999px rgba(0,0,0,0.55); }

/* Silk-smooth walkthrough: the highlight and the card are moved by the tour
   engine on one eased clock (transforms, no layout jumps); only opacity is
   animated in CSS. */
.tour-spot {
  top: 0; left: 0; opacity: 1; will-change: transform, width, height;
  box-shadow: 0 0 0 9999px rgba(0,0,0,0.55), 0 0 0 2px rgba(255,255,255,0.75);
  transition: box-shadow .3s ease;
}
.tour-spot--dim-only { box-shadow: 0 0 0 9999px rgba(0,0,0,0.55), 0 0 0 2px rgba(255,255,255,0); }
.tour-card { top: 0; bottom: auto; will-change: transform; animation: tourCardIn .35s ease-out; }
@keyframes tourCardIn { from { opacity: 0; } to { opacity: 1; } }
.tour-card__content { transition: opacity .16s ease; }
.tour-card__content--out { opacity: 0; }
.tour-dot { transition: background .35s ease, width .35s cubic-bezier(.22,1,.36,1); }
.tour-root { animation: overlayFadeIn .3s ease-out; }
.tour-root--closing { opacity: 0; transition: opacity .25s ease-out; }
@media (prefers-reduced-motion: reduce) {
  .tour-card, .tour-root { animation: none; }
  .tour-card__content { transition: none; }
}

/* Home header: parent greeting in the same rose-brown as the baby's name;
   baby's name a little larger (greeting stays the most prominent). */
.dash-header__greeting { color: var(--blush-pink); }
.dash-header__baby { font-size: 1.35rem; }

/* Walkthrough tab changes: the page cross-fades under the dimmed overlay
   instead of the new screen popping in, jumping to its scroll position,
   and replaying its own entrance animation. */
#screen, #topbar-slot { transition: opacity .24s ease; }
body.tour-switching #screen, body.tour-switching #topbar-slot { opacity: 0; }
body.tour-switching .fade-in, body.tour-settling .fade-in,
body.tour-switching .slide-in-right, body.tour-settling .slide-in-right { animation: none; }
@media (prefers-reduced-motion: reduce) { #screen, #topbar-slot { transition: none; } }
/* Parent greeting scaled down to sit in proportion with the larger baby name. */
.dash-header__greeting { font-size: 1.4rem; }

/* Lock the app horizontally on phones (founder: "moves a little from left
   to right"). iOS lets the whole page rubber-band sideways whenever anything
   is even briefly wider than the screen (slide-in animations, edge-bleed
   scrollers, centered labels at the edges), so: clip horizontal overflow at
   every level, stop sideways overscroll, and only allow vertical panning on
   the page itself (horizontal scrollers opt back in). */
html { overflow-x: hidden; overscroll-behavior-x: none; }
body { overflow-x: clip; max-width: 100%; touch-action: pan-y pinch-zoom; }
#app-shell, #screen { overflow-x: clip; }
.day-strip, .band-wheel { touch-action: pan-x pan-y; overscroll-behavior-x: contain; }
/* Slide-ins now start from 8px instead of 18-20px, and never widen the page. */
@keyframes slideInRight { from { opacity: 0; transform: translateX(8px); } to { opacity: 1; transform: translateX(0); } }
@keyframes onboardingSlideIn { from { opacity: 0; transform: translateX(8px); } to { opacity: 1; transform: translateX(0); } }
/* Day picker bleed matched to the smallest page padding (14px). */
.day-strip { margin: 0 -14px 14px; padding: 2px 14px 6px; }
/* Timeline clock labels at the very edges stay inside the card. */
.sleep-day__labels { overflow: hidden; }
.sleep-day__labels span:first-child { transform: translateX(-20%); }
.sleep-day__labels span:last-child { transform: translateX(-80%); }
/* While the walkthrough runs: the overlay swallows touch so a finger can't
   fight the tour's own scrolling. */
.tour-root { touch-action: none; overscroll-behavior: contain; }
.tour-spot, .tour-card { backface-visibility: hidden; }


/* App-like frame (founder: "still moves side to side and up and down").
   The document itself never scrolls: body is pinned to the visible screen,
   the app shell fills exactly that height, and only #screen scrolls, with
   its bounce kept inside it. The top bar and tab bar therefore never move,
   the page can't rubber-band up/down or sideways, and pinch/double-tap zoom
   are off so the layout can't be nudged out of place. */
html, body { height: 100%; overflow: hidden; overscroll-behavior: none; }
body { position: fixed; inset: 0; width: 100%; touch-action: pan-y; }
#app-shell { height: 100vh; height: 100dvh; min-height: 0; overflow: hidden; }
#topbar-slot { flex: 0 0 auto; }
#screen {
  flex: 1 1 auto; min-height: 0; overflow-y: auto; overflow-x: hidden;
  overscroll-behavior: contain; -webkit-overflow-scrolling: touch; touch-action: pan-y;
}
@media (min-width: 431px) {
  body { padding: 24px 0; overflow: hidden; }
  #app-shell { height: min(900px, calc(100dvh - 48px)); min-height: 0; }
}

/* Walkthrough card: one fixed place for every step. Anchored to the bottom
   (not moved by script), with a text area tall enough for the longest step
   so the card never grows or shrinks between steps. While the tour runs the
   content gets extra room below it, so any highlight can scroll up clear of
   the card. */
.tour-card {
  top: auto; bottom: calc(16px + env(safe-area-inset-bottom));
  transform: translateX(-50%) !important; transition: none !important;
}
.tour-card__content { min-height: 8.4rem; }
@media (max-height: 720px) { .tour-card__content { min-height: 7.7rem; } }
body.tour-active #screen { padding-bottom: 75vh; }
