/* Snusee — components and screens.
 * Every value comes from tokens.css. Mobile-first; target 390x844, must also
 * survive 360x800 and 430x932.
 *
 * V4 changes that touch everything below, stated once here rather than
 * repeated at forty call sites:
 *
 *  1. TEXT USES INKS, FILLS USE PASTELS. --pink-deep, --mint, --peach and
 *     friends paint things; --pink-ink, --mint-ink, --ink-soft and --ink-quiet
 *     set type on them. Anywhere a pastel was used as a text colour it has
 *     been swapped. See the header of tokens.css for the measurements.
 *
 *  2. THE APP IS A COLUMN. #app is capped at --app-max and centred, and every
 *     fixed element aligns to that same column rather than to the viewport.
 *     Before this the app was a phone layout stretched across a 1400px window.
 *
 *  3. SOS MOVED INTO THE TAB BAR. It was a floating pill that covered content
 *     on every screen -- a stat value on home, an island label on the map, a
 *     phase chip in the health list. It is now the raised centre item of a
 *     five-slot bar: still in the thumb zone, still never scrolled away, and
 *     no longer sitting on top of the thing you are reading.
 */

/* ------------------------------------------------------------- reset ---- */
*, *::before, *::after { box-sizing: border-box; }
* { margin: 0; }

html, body { height: 100%; }

/* SIDEWAYS SCROLL IS OFF ON BOTH ELEMENTS, NOT JUST ON BODY.
 *
 * `overflow-x: hidden` on <body> alone does nothing on a phone: when the root
 * element is not also constrained the viewport propagates the overflow back up
 * to <html>, which keeps its own horizontal scroll. One card a few pixels too
 * wide -- a long unbroken word in a note, a 24-column chart -- and the whole
 * page pans left and right under the thumb while trying to scroll down. It is
 * declared twice on purpose: `clip` is the correct value (it refuses the scroll
 * without making a scroll container, so `position: sticky` inside still works)
 * and `hidden` is what a browser that has never heard of it falls back to.
 *
 * touch-action: manipulation kills the double-tap-to-zoom gesture. It is the
 * reason a second tap anywhere within ~300ms zoomed the page in; pinch-to-zoom
 * is untouched, so nothing about accessibility changes. */
html {
  overflow-x: hidden;
  overflow-x: clip;
  touch-action: manipulation;
}

body {
  font-family: var(--font-body);
  font-size: 15px;
  line-height: 1.55;
  color: var(--ink);
  background: var(--cream);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow-x: hidden;               /* the page body never scrolls sideways */
  overflow-x: clip;
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
}

/* Everything tappable, again and explicitly: Safari applies the double-tap
 * gesture per element, and the root declaration alone does not reach a button
 * inside a scrolling card. */
button, a, input, textarea, select, label,
[role="button"], [role="tab"] { touch-action: manipulation; }

/* Anything the user typed can contain a word longer than the column. Without
 * this one rule a single pasted URL in a diary note widens the page and brings
 * the horizontal pan back on a screen that is otherwise fine. */
.tl__note, .tl__title, .record__note, .record__title, .checkin__note,
.prose p, .card__note, .toast span { overflow-wrap: anywhere; }

button, input, textarea, select { font: inherit; color: inherit; }
button { background: none; border: 0; cursor: pointer; }
ul, ol { list-style: none; padding: 0; }
a { color: var(--pink-ink); text-decoration: none; }
svg { display: block; flex: none; }

h1, h2, h3 { font-family: var(--font-display); font-weight: 700; line-height: 1.25; }

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

.sr-only {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap;
}

/* ------------------------------------------------------------ layout ---- */
/* The column. On a phone this is a no-op; on a laptop it is the difference
 * between an app and a stretched screenshot. --app-max is also what every
 * fixed element below aligns itself to, so the tab bar, the toast and the
 * sheet all sit under the same 480px of content rather than spanning a
 * desktop display. */
#app {
  position: relative;
  z-index: var(--z-content);
  min-height: 100%;
  max-width: var(--app-max);
  margin-inline: auto;
}

/* Only once there is something on either side of the column is there an edge
 * worth drawing. Below this width the rule would paint a hairline down the
 * side of a phone screen for no reason. */
@media (min-width: 560px) {
  #app {
    min-height: 100svh;
    background: linear-gradient(180deg,
      color-mix(in srgb, var(--cream) 55%, transparent),
      color-mix(in srgb, var(--cream-deep) 55%, transparent));
    box-shadow: var(--shadow-column);
  }
}

.screen {
  /* svh, not dvh. The DYNAMIC unit changes value every time the browser's own
   * chrome collapses or reappears, which on a phone is on every scroll -- so
   * the screen's own min-height was being recalculated mid-gesture and the
   * page grew and shrank by the height of the address bar under the thumb.
   * The SMALL unit is the one value that never changes during a session, so
   * the layout stops moving on its own. */
  min-height: 100svh;
  /* Bottom padding clears the tab bar. The old value also reserved room for
   * the floating SOS button; that button is now IN the bar, so the allowance
   * went with it. The raised centre item overhangs the bar by 22px, which
   * this padding still covers. */
  padding:
    calc(var(--sp-4) + env(safe-area-inset-top))
    var(--screen-pad)
    calc(var(--tabbar-h) + var(--sp-8) + env(safe-area-inset-bottom));
  display: flex;
  flex-direction: column;
  gap: var(--card-gap);
}
.screen--plain {   /* no tab bar: onboarding, auth, SOS */
  padding-bottom: calc(var(--sp-6) + env(safe-area-inset-bottom));
}

/* Background motifs sit behind everything and mean nothing.
 *
 * V3 adds two soft colour blooms under the tiled motif. The page was a flat
 * cream wash, and against a flat wash a cream card has nothing to be lighter
 * than -- the blooms give the cards something to sit on. Fixed, so they do
 * not scroll and never read as content. It covers the whole viewport, not
 * just the column, so the space beside the column on a laptop is the same
 * lit page rather than a grey gutter. */
.motif {
  position: fixed; inset: 0;
  z-index: var(--z-motif);
  pointer-events: none;
  background: var(--grad-page-lit);
}
.motif::after {
  content: ''; position: absolute; inset: 0;
  background-image: var(--motif-image, none);
  background-size: 320px;
  opacity: .14;
}

.screen-head { margin-bottom: var(--sp-1); }
.screen-head h1 { font-size: 22px; }
.screen-head p  { font-size: 13px; color: var(--ink-soft); line-height: 1.4; }
.screen-head--center { text-align: center; }

.topbar {
  display: flex; align-items: center; gap: var(--sp-3);
  margin-bottom: var(--sp-2);
}
.topbar .screen-head { flex: 1; }

/* -------------------------------------------------------------- card ---- */
/* Raised surfaces all wear the same three-part treatment: a gradient for the
 * light across the face, --shadow-edge for the lit top rim and hairline ring,
 * and a drop shadow for the lift. Defined once here and reused, so nothing in
 * the app can end up half-lit. */
.card {
  background: var(--grad-card);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-soft), var(--shadow-edge);
  padding: var(--card-pad);
}
/* Card titles are real headings now. They always looked like headings; they
 * were <div>s, so the home screen shipped with no document structure at all
 * and a screen reader had nothing to navigate by. */
.card__title,
h2.card__title {
  font-family: var(--font-display);
  font-size: 16px; font-weight: 600;
  display: flex; align-items: center; gap: var(--sp-2);
  margin-bottom: var(--sp-3);
  line-height: 1.3;
}
.card__note { font-size: 12px; color: var(--ink-quiet); margin-top: var(--sp-2); }

.zdroj {
  font-size: 12px; color: var(--ink-quiet);
  margin-top: var(--sp-3); line-height: 1.45;
  padding-top: var(--sp-3);
  border-top: 1px solid var(--card-sunk);
}

/* ------------------------------------------------------------ button ---- */
.btn {
  display: flex; align-items: center; justify-content: center; gap: var(--sp-2);
  width: 100%; min-height: 52px; padding: 0 var(--sp-5);
  font-family: var(--font-display); font-size: 17px; font-weight: 600;
  border-radius: var(--radius-btn);
  transition: transform var(--dur-fast) var(--ease-spring),
              box-shadow var(--dur-fast) var(--ease-soft);
}
.btn:active { transform: scale(.96); }

.btn--primary {
  background: var(--grad-primary); color: var(--on-pink);
  box-shadow: var(--shadow-btn);
}
.btn--primary:active { box-shadow: var(--shadow-press); }

.btn--secondary {
  background: transparent; color: var(--pink-ink);
  border: 1.5px solid var(--pink-deep);
}
.btn--ghost { color: var(--ink-soft); font-size: 15px; min-height: 44px; }

.btn[disabled] { opacity: .5; pointer-events: none; }

.btn-icon {
  width: 44px; height: 44px; flex: none;
  display: grid; place-items: center;
  border-radius: var(--radius-chip);
  color: var(--ink);
  background: var(--grad-card);
  box-shadow: var(--shadow-lift), var(--shadow-edge);
  transition: transform var(--dur-fast) var(--ease-spring);
}
.btn-icon:active { transform: scale(.92); }

/* A quiet full-width row that goes somewhere. Used for the crisis-line link
 * on the SOS screen and the knowledge-base links on home and in settings --
 * places where the destination matters but the row must not compete with the
 * primary action on that screen. */
.link-row {
  display: flex; align-items: center; gap: var(--sp-3);
  width: 100%; min-height: 48px; padding: var(--sp-3) var(--sp-4);
  text-align: left; font-size: 14px; color: var(--ink);
  background: var(--grad-card); border-radius: var(--radius-input);
  box-shadow: var(--shadow-lift), var(--shadow-edge);
  transition: transform var(--dur-fast) var(--ease-spring);
}
.link-row:active { transform: scale(.985); }
/* The label, not every span in the row -- `.link-row > span { flex: 1 }` also
 * matched a span wrapping the trailing chevron, which then took half the row
 * and pushed the label off centre. */
.link-row__label { flex: 1; min-width: 0; }
.link-row svg { color: var(--pink-ink); }
/* Needs the element in the selector to outrank `.link-row svg` above it; as a
 * bare class it lost the cascade and the chevron stayed accent-coloured, which
 * put the same emphasis on "there is more this way" as on what the row is. */
.link-row svg.link-row__chevron { color: var(--ink-quiet); }

/* ------------------------------------------------------------- input ---- */
.field { display: flex; flex-direction: column; gap: var(--sp-2); }
.field > label { font-size: 13px; color: var(--ink-soft); padding-left: var(--sp-2); }

.input-wrap { position: relative; display: flex; align-items: center; }
.input-wrap > svg:first-child {
  position: absolute; left: var(--sp-4); color: var(--ink-quiet);
}
.input-wrap:has(> svg:first-child) .input { padding-left: calc(var(--sp-4) * 2 + 20px); }

.input {
  width: 100%; min-height: 52px;
  padding: 0 var(--sp-4);
  background: var(--grad-sunk);
  border: 1.5px solid transparent;
  border-radius: var(--radius-input);
  box-shadow: var(--shadow-well);
  color: var(--ink);
  transition: border-color var(--dur-fast) var(--ease-soft);
}
.input::placeholder { color: var(--ink-quiet); }
.input:focus { outline: none; border-color: var(--pink-ink); }

textarea.input { min-height: 110px; padding: var(--sp-4); line-height: 1.5; resize: vertical; }

/* A native datetime-local renders its own text in the browser's locale, which
 * on a machine set to English shows a Slovak user 08/06/2026 10:25 PM. The
 * control stays -- it is the right control -- and screens that use it echo the
 * value back in Slovak underneath (see .field-echo). */
.field-echo {
  font-size: 12px; color: var(--ink-soft);
  padding-left: var(--sp-2); margin-top: calc(var(--sp-1) * -1);
}

.input-toggle {
  position: absolute; right: var(--sp-4);
  color: var(--ink-quiet); display: grid; place-items: center;
  width: 44px; height: 44px; justify-content: center;
}

/* Errors never use red -- a peach accent and plain words instead. */
.form-error {
  display: flex; gap: var(--sp-3); align-items: flex-start;
  padding: var(--sp-3) var(--sp-4);
  background: var(--peach-wash);
  border-left: 3px solid var(--peach-ink);
  border-radius: var(--radius-input);
  font-size: 14px;
}

/* -------------------------------------------------------------- chip ---- */
.chips { display: flex; flex-wrap: wrap; gap: var(--sp-2); }

.chip {
  display: inline-flex; align-items: center; gap: var(--sp-2);
  min-height: 40px; padding: 0 var(--sp-4);
  background: var(--grad-card);
  border: 1.5px solid transparent;
  border-radius: var(--radius-chip);
  box-shadow: var(--shadow-lift), var(--shadow-edge);
  font-size: 14px;
  transition: transform var(--dur-fast) var(--ease-spring),
              background-color var(--dur-fast) var(--ease-soft);
}
.chip:active { transform: scale(.94); }
.chip[aria-pressed="true"] {
  background: var(--pink-wash);
  border-color: var(--pink-ink);
  color: var(--pink-ink);
  font-weight: 700;
}

.pill {
  display: inline-flex; align-items: center; gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-3);
  background: var(--pink-wash); color: var(--pink-ink);
  border-radius: var(--radius-chip);
  font-size: 12px; font-weight: 700;
}

/* ------------------------------------------------------------- timer ---- */
.timer { display: flex; align-items: center; gap: var(--sp-1); }

.timer__tile {
  flex: 1;
  display: flex; flex-direction: column; align-items: center;
  gap: 2px; padding: var(--sp-3) var(--sp-1);
  border-radius: var(--radius-tile);
  box-shadow: var(--shadow-lift), var(--shadow-edge);
  min-width: 0;
  /* The tint is the tile's own accent; this overlay is the same light every
   * other raised surface gets, so the four tiles read as a row of objects
   * rather than four flat swatches. */
  background-image: linear-gradient(168deg,
    rgba(255, 255, 255, .85) 0%, rgba(255, 255, 255, 0) 62%);
}
.timer__value {
  font-family: var(--font-display);
  font-size: 30px; font-weight: 700; line-height: 1;
  font-variant-numeric: tabular-nums;
}
.timer__label {
  font-size: 10px; font-weight: 700; letter-spacing: .08em;
  text-transform: uppercase; color: var(--ink-soft);
}
.timer__sep {
  font-family: var(--font-display); font-size: 22px; font-weight: 700;
  color: var(--ink-quiet); padding-bottom: 14px;
}

/* In dark mode the white sheen would blow the tinted tiles out, so it is
 * dialled right back rather than removed -- the tiles still need to read as
 * objects with light on them. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .timer__tile {
    background-image: linear-gradient(168deg,
      rgba(255, 255, 255, .07) 0%, rgba(255, 255, 255, 0) 62%);
  }
}
:root[data-theme="dark"] .timer__tile {
  background-image: linear-gradient(168deg,
    rgba(255, 255, 255, .07) 0%, rgba(255, 255, 255, 0) 62%);
}

/* Four digits, four inks. Every one of these was under the large-text
 * threshold before V4; see tokens.css. */
.timer__tile--days    { background: var(--pink-wash); }
.timer__tile--days    .timer__value { color: var(--pink-strong); }
.timer__tile--hours   { background: var(--lavender-wash); }
.timer__tile--hours   .timer__value { color: var(--lavender-ink); }
.timer__tile--minutes { background: var(--mint-wash); }
.timer__tile--minutes .timer__value { color: var(--mint-ink); }
.timer__tile--seconds { background: var(--peach-wash); }
.timer__tile--seconds .timer__value { color: var(--peach-ink); }

/* Transform-only so the tick never reflows the row. */
@keyframes tick { 0% { transform: scale(1); } 40% { transform: scale(1.06); } 100% { transform: scale(1); } }
.timer__tile--seconds .timer__value.is-ticking {
  animation: tick var(--dur-fast) var(--ease-spring);
}

/* -------------------------------------------------------------- stats --- */
.stat-row { display: grid; grid-template-columns: 1fr 1fr; gap: var(--card-gap); }

.stat {
  background: var(--grad-card); border-radius: var(--radius-card);
  box-shadow: var(--shadow-soft), var(--shadow-edge); padding: var(--sp-4);
  display: flex; flex-direction: column; gap: var(--sp-1);
}
.stat__label {
  font-size: 12px; color: var(--ink-soft);
  display: flex; align-items: center; gap: var(--sp-2);
}
.stat__value {
  font-family: var(--font-display); font-size: 26px; font-weight: 700;
  line-height: 1.1; overflow-wrap: anywhere;
}
.stat__note { font-size: 11px; color: var(--ink-quiet); }

/* The two numbers a relapse never takes away. They used to appear for the
 * first time on the relapse screen, which is the worst possible moment to
 * introduce a reassurance -- by then the user is already deciding. They now
 * sit on home, quietly, all the time. */
.kept-row {
  display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-3);
  margin-top: var(--sp-3);
}
.kept-row .kept__item { margin: 0; }

/* ----------------------------------------------------------- progress --- */
.progress { display: flex; flex-direction: column; gap: var(--sp-2); }
.progress__track {
  position: relative; height: 10px;
  background: var(--card-sunk); box-shadow: var(--shadow-well);
  border-radius: var(--radius-chip);
}
.progress__fill {
  height: 100%; border-radius: var(--radius-chip);
  background: var(--grad-progress);
  transition: width var(--dur-slow) var(--ease-soft);
}
.progress__knob {
  position: absolute; top: 50%; translate: -50% -50%;
  width: 26px; height: 26px;
  transition: left var(--dur-slow) var(--ease-soft);
}
.progress__meta {
  display: flex; justify-content: space-between;
  font-size: 12px; color: var(--ink-soft);
}

/* ------------------------------------------------------------ tabbar ---- */
/* Five slots. The middle one is the craving button, raised out of the bar.
 *
 * It was a floating pill anchored to the bottom right, which meant a fixed
 * element sat permanently on top of whatever was underneath it -- on home it
 * covered a stat value and a card heading, on the map an island label, in the
 * health list a phase chip. Reserving a gutter for it would have cost every
 * screen 90px of width. Putting it in the bar costs nothing and it is still
 * the largest, warmest, most reachable thing on screen. */
.tabbar {
  position: fixed; bottom: 0;
  left: 50%; translate: -50% 0;
  width: min(100%, var(--app-max));
  z-index: var(--z-tabbar);
  display: grid; grid-template-columns: repeat(5, 1fr);
  background: linear-gradient(180deg, var(--white) 0%, var(--card) 100%);
  box-shadow: 0 -6px 20px rgba(214, 164, 178, .16),
              inset 0 1px 0 rgba(255, 255, 255, .95);
  padding-bottom: env(safe-area-inset-bottom);
  border-radius: var(--radius-card) var(--radius-card) 0 0;
  transition: transform var(--dur-base) var(--ease-soft);
}

/* THE BAR GETS OUT OF THE WAY WHILE THE KEYBOARD IS UP.
 *
 * A fixed bottom bar and an on-screen keyboard have no agreed behaviour
 * between engines: Android leaves it stranded partway up the screen, iOS
 * leaves it under the keyboard, and both jitter while the keyboard animates.
 * None of those are worth fixing, because a tab bar is useless during typing
 * anyway -- there is nowhere the user wants to navigate mid-sentence. So it
 * leaves while the keyboard is up. app.js measures the keyboard and sets the
 * class; see watchKeyboard() for why it is measured rather than inferred.
 *
 * `transition: none` on the way OUT is deliberate and is the fix for what V7
 * shipped first. An animated exit means 240 ms during which the bar is still
 * on screen, in the wrong place, sliding -- which is exactly the "buggy
 * navbar" being complained about. It disappears on the frame the keyboard
 * starts opening. Coming back it may animate: nothing is wrong with the screen
 * at that point, and a bar that pops into existence reads as a glitch. */
body.is-keyboard .tabbar {
  transform: translateY(130%);
  pointer-events: none;
  transition: none;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .tabbar {
    box-shadow: 0 -8px 24px rgba(0, 0, 0, .48),
                inset 0 1px 0 rgba(255, 255, 255, .06);
  }
}
:root[data-theme="dark"] .tabbar {
  box-shadow: 0 -8px 24px rgba(0, 0, 0, .48),
              inset 0 1px 0 rgba(255, 255, 255, .06);
}

.tabbar__item {
  height: var(--tabbar-h);
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 3px; font-size: 11px; color: var(--ink-soft);
  transition: color var(--dur-fast) var(--ease-soft);
}
.tabbar__item[aria-current="page"] { color: var(--pink-ink); font-weight: 700; }
/* Five equal columns only stay equal while every label is one line. A wrapped
 * label makes its own tab taller than the other four and drags the raised
 * middle button out of alignment with them. */
.tabbar__item > span { white-space: nowrap; }

/* The craving button. Overhangs the bar by 22px, which the screens' bottom
 * padding already clears, so it never lands on content. */
.tabbar__item--sos {
  position: relative;
  justify-content: flex-end;
  padding-bottom: 8px;
  color: var(--pink-ink);
  font-weight: 700;
}
.tabbar__fab {
  position: absolute; top: -22px; left: 50%; translate: -50% 0;
  width: 58px; height: 58px;
  border-radius: 50%;
  background: var(--grad-primary); color: var(--on-pink);
  display: grid; place-items: center;
  box-shadow: var(--shadow-btn);
  transition: transform var(--dur-fast) var(--ease-spring);
}
.tabbar__item--sos:active .tabbar__fab { transform: scale(.92); }

/* ----------------------------------------------------------- journey ---- */
.journey { position: relative; padding: var(--sp-4) 0 var(--sp-10); }
.journey__path { position: absolute; inset: 0; width: 100%; height: 100%; z-index: 0; }
.journey__stops { position: relative; z-index: 1; display: flex; flex-direction: column; gap: var(--sp-6); }

.stop { display: flex; align-items: center; gap: var(--sp-3); max-width: 78%; }
.stop--right { align-self: flex-end; flex-direction: row-reverse; text-align: right; }

.stop__bubble {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-4);
  background: var(--grad-card); border-radius: var(--radius-card);
  box-shadow: var(--shadow-soft), var(--shadow-edge);
  transition: transform var(--dur-fast) var(--ease-spring);
}
.stop__bubble:active { transform: scale(.97); }
.stop__when { font-size: 12px; color: var(--ink-soft); }
.stop__title { font-family: var(--font-display); font-size: 15px; font-weight: 600; }

.stop--locked .stop__bubble { background: var(--locked-bg); box-shadow: none; }
.stop--locked .stop__title  { color: var(--ink-quiet); }
.stop--locked .stop__when   { color: var(--ink-quiet); }

.stop__icon {
  width: 42px; height: 42px; flex: none;
  border-radius: var(--radius-chip);
  display: grid; place-items: center;
  background: var(--pink-wash);
  box-shadow: var(--shadow-edge);
}
.stop--locked .stop__icon { background: var(--locked); opacity: .55; }

.you-are-here {
  align-self: center;
  display: inline-flex; align-items: center; gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-4);
  background: var(--grad-primary); color: var(--on-pink);
  border-radius: var(--radius-chip);
  box-shadow: var(--shadow-btn);
  font-family: var(--font-display); font-size: 12px; font-weight: 700;
  letter-spacing: .06em;
}

.journey__segment {
  font-family: var(--font-display); font-size: 13px; font-weight: 700;
  color: var(--ink-soft); text-align: center;
  margin-top: var(--sp-4);
}

/* --------------------------------------------------------------- log ---- */
.tabs { display: flex; gap: var(--sp-2); background: var(--card-sunk);
        padding: var(--sp-1); border-radius: var(--radius-chip);
        box-shadow: var(--shadow-well); }
.tabs__item {
  flex: 1; min-height: 42px; border-radius: var(--radius-chip);
  font-size: 14px; color: var(--ink-soft);
  transition: background-color var(--dur-fast) var(--ease-soft);
}
.tabs__item[aria-selected="true"] {
  background: var(--card); color: var(--ink); font-weight: 700;
  box-shadow: var(--shadow-lift);
}

.faces { display: grid; grid-template-columns: repeat(5, 1fr); gap: var(--sp-2); }
.face {
  display: flex; flex-direction: column; align-items: center; gap: var(--sp-2);
  padding: var(--sp-2) 0;
  transition: transform var(--dur-fast) var(--ease-spring);
}
.face:active { transform: scale(.92); }
.face__dot {
  width: 46px; height: 46px; border-radius: 50%;
  display: grid; place-items: center;
  /* The glyph inside sits on a pastel and used to inherit --ink, which on
   * --scale-5 measured 2.75:1. --on-scale clears 4.5:1 on every step of the
   * ramp without moving the ramp itself. */
  color: var(--on-scale);
  box-shadow: var(--shadow-lift), var(--shadow-edge);
  transition: transform var(--dur-base) var(--ease-spring),
              box-shadow var(--dur-base) var(--ease-soft);
}
.face__label { font-size: 10px; color: var(--ink-soft); text-align: center; line-height: 1.2; }
.face[aria-pressed="true"] .face__dot { transform: scale(1.16); box-shadow: var(--shadow-btn); }
.face[aria-pressed="true"] .face__label { color: var(--ink); font-weight: 700; }

.face--1 .face__dot { background: var(--scale-1); }
.face--2 .face__dot { background: var(--scale-2); }
.face--3 .face__dot { background: var(--scale-3); }
.face--4 .face__dot { background: var(--scale-4); }
.face--5 .face__dot { background: var(--scale-5); }

/* A compact five-step scale, for the symptom rows.
 *
 * The symptom tab used to be twenty-one identical range sliders sitting at
 * zero with an em-dash for a readout -- an undifferentiated wall that took a
 * drag per row to fill in, so nobody filled it in. Five dots is one tap, uses
 * the craving scale's own colours, and fits on the same row as the name. */
.scale5 { display: flex; gap: 6px; flex: none; }
.scale5__dot {
  width: 30px; height: 30px; border-radius: 50%;
  display: grid; place-items: center;
  background: var(--card-sunk);
  box-shadow: var(--shadow-well);
  font-size: 11px; font-weight: 700; color: var(--ink-quiet);
  transition: transform var(--dur-fast) var(--ease-spring);
}
.scale5__dot:active { transform: scale(.9); }
.scale5__dot[aria-pressed="true"] {
  color: var(--on-scale);
  box-shadow: var(--shadow-lift), var(--shadow-edge);
}
.scale5__dot--1[aria-pressed="true"] { background: var(--scale-1); }
.scale5__dot--2[aria-pressed="true"] { background: var(--scale-2); }
.scale5__dot--3[aria-pressed="true"] { background: var(--scale-3); }
.scale5__dot--4[aria-pressed="true"] { background: var(--scale-4); }
.scale5__dot--5[aria-pressed="true"] { background: var(--scale-5); }

.severity { display: flex; align-items: center; gap: var(--sp-3); }

/* The range input is styled explicitly rather than left to accent-color.
 * accent-color only tints the filled portion and the thumb -- the unfilled
 * track keeps the platform default, which is near-black here and is the one
 * dark bar in an app that has no dark bars. At severity 0 the whole track is
 * unfilled, so the default was ALL the user saw.
 *
 * Still used by the edit sheet, where a precise value is being corrected
 * rather than entered. */
.severity input[type="range"] {
  flex: 1;
  -webkit-appearance: none; appearance: none;
  height: 24px; background: transparent; cursor: pointer;
}
.severity input[type="range"]::-webkit-slider-runnable-track {
  height: 8px; border-radius: var(--radius-chip);
  background: var(--card-sunk); box-shadow: var(--shadow-well);
}
.severity input[type="range"]::-moz-range-track {
  height: 8px; border-radius: var(--radius-chip);
  background: var(--card-sunk); box-shadow: var(--shadow-well);
}
.severity input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none;
  width: 20px; height: 20px; margin-top: -6px;
  border-radius: 50%; border: 2px solid var(--card);
  background: var(--pink-ink); box-shadow: var(--shadow-lift);
}
.severity input[type="range"]::-moz-range-thumb {
  width: 20px; height: 20px;
  border-radius: 50%; border: 2px solid var(--card);
  background: var(--pink-ink); box-shadow: var(--shadow-lift);
}
.severity input[type="range"]::-moz-range-progress {
  height: 8px; border-radius: var(--radius-chip); background: var(--pink-ink);
}
.severity input[type="range"]:focus-visible { outline: 2px solid var(--pink-ink); outline-offset: 4px; }

.symptom-row {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--sp-3); padding: var(--sp-3) 0;
  border-bottom: 1px solid var(--card-sunk);
}
.symptom-row:last-child { border-bottom: 0; }
.symptom-row__name {
  flex: 1; min-width: 0; text-align: left;
  font-weight: 600; font-size: 14px;
  overflow-wrap: anywhere;
}
.symptom-row__phase {
  font-size: 9.5px; font-weight: 700; letter-spacing: .06em;
  text-transform: uppercase; color: var(--ink-quiet);
  display: block; margin-top: 2px;
}

/* ------------------------------------------------------------ badges ---- */
.badge-hero { display: flex; flex-direction: column; align-items: center; gap: var(--sp-2); }
.badge-hero__art { position: relative; width: 190px; height: 150px; display: grid; place-items: center; }
.badge-hero__medal { width: 96px; height: 96px; }
.badge-hero__wreath { position: absolute; inset: 0; width: 100%; height: 100%; }

.badge-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--sp-3); }

.badge {
  display: flex; flex-direction: column; align-items: center; gap: var(--sp-2);
  padding: var(--sp-3) var(--sp-1);
  background: var(--grad-card); border-radius: var(--radius-tile);
  box-shadow: var(--shadow-lift), var(--shadow-edge);
  transition: transform var(--dur-fast) var(--ease-spring);
}
.badge:active { transform: scale(.94); }
.badge__art { width: 46px; height: 46px; }
.badge__name { font-size: 9.5px; text-align: center; line-height: 1.2; color: var(--ink-soft); }

/* One art asset per badge: the locked state is CSS, not a second image. */
.badge--locked { background: var(--locked-bg); box-shadow: none; }
.badge--locked .badge__art { filter: grayscale(1) opacity(.45); }
/* Was --ink-faint on --locked-bg: 1.61:1, the lowest number in the app. */
.badge--locked .badge__name { color: var(--locked-ink); }
.badge__lock { position: absolute; color: var(--locked); }
.badge__wrap { position: relative; display: grid; place-items: center; }

/* ------------------------------------------------------------- sheet ---- */
.sheet-backdrop {
  position: fixed; inset: 0; z-index: var(--z-sheet);
  /* A sheet must sit ABOVE the keyboard, not behind it. The panel is anchored
   * to the bottom of this box, so with a keyboard up the panel -- and whatever
   * field was just tapped -- ends up underneath it. --kb is the measured
   * keyboard height (app.js watchKeyboard) and is 0px the rest of the time, so
   * this costs nothing when there is no keyboard. It has to come after `inset`
   * rather than before it. */
  bottom: var(--kb, 0px);
  background: rgba(43, 30, 36, .42);
  display: flex; align-items: flex-end; justify-content: center;
  animation: fade var(--dur-base) var(--ease-soft);
}
@keyframes fade { from { opacity: 0; } to { opacity: 1; } }

.sheet {
  position: relative;
  width: 100%; max-width: var(--app-max);
  /* Measured against what is actually visible: the backdrop already stops at
   * the top of the keyboard, and a sheet that still thought it had 86% of the
   * window would scroll its own content behind it. */
  max-height: calc(86dvh - var(--kb, 0px)); overflow-y: auto;
  background: var(--grad-card);
  border-radius: 32px 32px 0 0;
  box-shadow: var(--shadow-deep), inset 0 1px 0 rgba(255, 255, 255, .95);
  padding: var(--sp-5) var(--screen-pad)
           calc(var(--sp-6) + env(safe-area-inset-bottom));
  animation: rise var(--dur-base) var(--ease-soft);
}
@keyframes rise { from { transform: translateY(6%); } to { transform: translateY(0); } }

.sheet__grab {
  width: 42px; height: 5px; border-radius: 3px;
  background: var(--card-sunk); margin: 0 auto var(--sp-4);
}

/* Escape closed a sheet and a backdrop tap closed a sheet, and neither of
 * those exists on a phone thumb's mental model. There is a button now. It is
 * sticky rather than absolute so it stays reachable in a long sheet -- the
 * symptom detail runs well past a screen. */
.sheet__close {
  position: sticky; top: 0; float: right;
  width: 40px; height: 40px; margin: -6px -6px 0 0;
  display: grid; place-items: center;
  border-radius: var(--radius-chip);
  color: var(--ink-soft);
  background: var(--card-sunk);
  box-shadow: var(--shadow-edge);
  z-index: 2;
}
.sheet__close:active { transform: scale(.92); }

/* ------------------------------------------------------------- toast ---- */
.toast {
  position: fixed; left: 50%; translate: -50% 0;
  bottom: calc(var(--tabbar-h) + var(--sp-6) + env(safe-area-inset-bottom));
  z-index: var(--z-takeover);
  max-width: min(calc(100vw - var(--sp-8)), calc(var(--app-max) - var(--sp-8)));
  padding: var(--sp-3) var(--sp-5);
  background: var(--grad-card); border-radius: var(--radius-chip);
  box-shadow: var(--shadow-deep), var(--shadow-edge);
  font-size: 14px; text-align: center;
  animation: rise var(--dur-base) var(--ease-spring);
}

/* A toast that can be acted on. Used for the one action in the app that could
 * not be taken back: recording a pouch restarts the clock, and until V4 there
 * was no way to say "that was a mistake". */
.toast--action {
  display: flex; align-items: center; gap: var(--sp-3);
  text-align: left; padding-right: var(--sp-2);
}
.toast__undo {
  flex: none; padding: var(--sp-2) var(--sp-4);
  border-radius: var(--radius-chip);
  background: var(--pink-wash); color: var(--pink-ink);
  font-weight: 700; font-size: 14px;
}

/* -------------------------------------------------------- onboarding ---- */
.wizard { flex: 1; display: flex; flex-direction: column; gap: var(--card-gap); }
.wizard__body { flex: 1; display: flex; flex-direction: column; gap: var(--card-gap); }

.dots { display: flex; gap: var(--sp-2); justify-content: center; }
.dots span {
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--ink-faint);
  transition: width var(--dur-base) var(--ease-spring),
              background-color var(--dur-base) var(--ease-soft);
}
.dots span.is-on { width: 20px; border-radius: 4px; background: var(--pink-deep); }

.stepper { display: flex; align-items: center; justify-content: center; gap: var(--sp-5); }
.stepper__btn {
  width: 52px; height: 52px; border-radius: 50%;
  background: var(--grad-card); box-shadow: var(--shadow-lift), var(--shadow-edge);
  display: grid; place-items: center; color: var(--pink-ink);
  transition: transform var(--dur-fast) var(--ease-spring);
}
.stepper__btn:active { transform: scale(.9); }
.stepper__value {
  font-family: var(--font-display); font-size: 46px; font-weight: 700;
  min-width: 92px; text-align: center; font-variant-numeric: tabular-nums;
}
/* Ten taps to say "twenty a day" is ten taps too many. */
.stepper__presets { display: flex; gap: var(--sp-2); justify-content: center; margin-top: var(--sp-4); }

.option {
  display: flex; align-items: center; gap: var(--sp-3);
  width: 100%; padding: var(--sp-4);
  background: var(--grad-card); border-radius: var(--radius-card);
  border: 1.5px solid transparent;
  box-shadow: var(--shadow-lift), var(--shadow-edge);
  text-align: left;
  transition: transform var(--dur-fast) var(--ease-spring);
}
.option:active { transform: scale(.98); }
.option[aria-pressed="true"] { border-color: var(--pink-ink); background: var(--pink-wash); }
.option__title { font-family: var(--font-display); font-size: 16px; font-weight: 600; }
.option__sub { font-size: 12px; color: var(--ink-soft); }

/* --------------------------------------------------------------- sos ---- */
.sos {
  position: fixed; inset: 0; z-index: var(--z-takeover);
  /* Lit like the rest: this screen covers .motif completely, so with the plain
   * page gradient it was the one flat surface in the app -- and it is the one
   * screen somebody opens at their worst moment. */
  background: var(--grad-page-lit);
  display: flex; flex-direction: column;
  /* Content is held to the app column on a wide window without the takeover
   * itself shrinking -- the background has to cover everything, because this
   * screen's whole job is to be the only thing there is. */
  padding:
    calc(var(--sp-5) + env(safe-area-inset-top))
    max(var(--screen-pad), calc((100vw - var(--app-max)) / 2))
    calc(var(--sp-5) + env(safe-area-inset-bottom));
  gap: var(--card-gap);
  overflow-y: auto;
}
.sos__lead {
  font-family: var(--font-display); font-size: 20px; font-weight: 700;
  text-align: center; line-height: 1.35;
}

.breath { position: relative; display: grid; place-items: center; height: 220px; flex: none; }
.breath__circle {
  width: 150px; height: 150px; border-radius: 50%;
  background: var(--grad-primary); opacity: .22;
  animation: breathe 10s var(--ease-soft) infinite;
}
.breath__inner {
  position: absolute; display: grid; place-items: center; gap: var(--sp-1);
  text-align: center;
}
.breath__phase { font-family: var(--font-display); font-size: 19px; font-weight: 700; }
.breath__count { font-size: 13px; color: var(--ink-soft); font-variant-numeric: tabular-nums; }

/* 4s in, 6s out. */
@keyframes breathe {
  0%   { transform: scale(.72); }
  40%  { transform: scale(1.12); }
  100% { transform: scale(.72); }
}

/* With reduced motion the circle holds still; the phase label and countdown
 * keep updating, so the guide still works. */
@media (prefers-reduced-motion: reduce) {
  .breath__circle { transform: scale(1); opacity: .18; }
}

.ring { --p: 0; width: 74px; height: 74px; flex: none; }
.ring circle { fill: none; stroke-width: 7; stroke-linecap: round; }
.ring__track { stroke: var(--card-sunk); }
.ring__fill  { stroke: var(--pink-ink); transition: stroke-dashoffset 1s linear; }

/* The countdown read "Ostáva 297 s". Five minutes as a raw second count, on
 * the screen where the reader is least able to do arithmetic. It is m:ss now,
 * and tabular so the digits do not jitter. */
.sos__clock {
  font-family: var(--font-display); font-size: 22px; font-weight: 700;
  font-variant-numeric: tabular-nums;
}
/* The honest win happens at three minutes, not five -- the lead sentence says
 * so and the timer used to run to the maximum without ever acknowledging it. */
.sos__clock-note { font-size: 12px; color: var(--ink-soft); margin-top: 2px; }
.sos.is-past-three .ring__fill { stroke: var(--chart-good); }

.reason {
  background: var(--pink-wash);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-edge);
  padding: var(--card-pad);
}
.reason__label {
  font-size: 12px; color: var(--pink-ink); font-weight: 700;
  text-transform: uppercase; letter-spacing: .06em; margin-bottom: var(--sp-2);
}
.reason__text { font-family: var(--font-display); font-size: 17px; line-height: 1.45; }

/* ----------------------------------------------------------- relapse ---- */
.kept {
  display: grid; grid-template-columns: 1fr 1fr; gap: var(--card-gap);
}
.kept__item {
  background: var(--pink-wash); border-radius: var(--radius-tile);
  box-shadow: var(--shadow-edge);
  padding: var(--sp-4); text-align: center;
}
.kept__value {
  font-family: var(--font-display); font-size: 24px; font-weight: 700;
  color: var(--pink-strong);
}
.kept__label { font-size: 11px; color: var(--ink-soft); }

/* ------------------------------------------------------ knowledge base -- */
.kb-item {
  width: 100%; display: flex; align-items: center; gap: var(--sp-3);
  padding: var(--sp-4);
  background: var(--grad-card); border-radius: var(--radius-card);
  box-shadow: var(--shadow-lift), var(--shadow-edge); text-align: left;
  transition: transform var(--dur-fast) var(--ease-spring);
}
.kb-item:active { transform: scale(.98); }
.kb-item__title { flex: 1; font-family: var(--font-display); font-size: 15px; font-weight: 600; }

.prose h3 {
  font-size: 15px; margin: var(--sp-4) 0 var(--sp-2);
  color: var(--pink-ink);
}
.prose p { margin-bottom: var(--sp-3); }
.prose strong { font-weight: 700; }

.callout {
  background: var(--mint-wash); border-radius: var(--radius-card);
  box-shadow: var(--shadow-edge);
  padding: var(--card-pad); display: flex; flex-direction: column; gap: var(--sp-2);
}
.callout--urgent { background: var(--pink-wash); }
.callout__title { font-family: var(--font-display); font-size: 16px; font-weight: 700; }

.tel {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--sp-3); padding: var(--sp-3) var(--sp-4);
  background: var(--grad-card); border-radius: var(--radius-input);
  box-shadow: var(--shadow-lift), var(--shadow-edge);
}
.tel__number { font-family: var(--font-display); font-size: 18px; font-weight: 700; }
.tel__meta { font-size: 11px; color: var(--ink-soft); }

/* ------------------------------------------------------------ splash ---- */
.splash {
  min-height: 100svh; display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: var(--sp-5);
  padding: calc(var(--sp-6) + env(safe-area-inset-top)) var(--screen-pad)
           calc(var(--sp-6) + env(safe-area-inset-bottom));
  text-align: center;
}
.splash__mascot { width: 128px; height: 128px; }
/* `height: auto` is load-bearing: art.js ships the intrinsic width AND height
   as attributes so the browser can reserve the right box before the image
   decodes, and those land as presentational hints. Without this the wordmark
   renders 210 wide by its intrinsic 130 tall -- stretched to twice its ratio. */
.splash__wordmark { width: 210px; max-width: 60vw; height: auto; }
.splash__tagline { font-family: var(--font-display); font-size: 17px; color: var(--ink-soft); }
.splash__actions { width: 100%; display: flex; flex-direction: column; gap: var(--sp-3); margin-top: var(--sp-4); }

/* ----------------------------------------------------------- landing ---- */
/* What a signed-out visitor sees at the root.
 *
 * Until V4 that was the wizard's splash: a mascot, a wordmark and two
 * buttons. Nothing said what the app was, who it was for, or why anything it
 * claims about your body should be believed -- which is the one thing this
 * app has that the others do not. */
.landing { gap: var(--sp-6); }
.landing__hero {
  display: flex; flex-direction: column; align-items: center; gap: var(--sp-3);
  text-align: center; padding-top: var(--sp-4);
}
.landing__title {
  font-family: var(--font-display); font-size: 30px; font-weight: 700;
  line-height: 1.18; text-wrap: balance;
}
.landing__sub {
  font-size: 15px; color: var(--ink-soft); line-height: 1.5;
  max-width: 34ch; text-wrap: balance;
}
.landing__points { display: flex; flex-direction: column; gap: var(--sp-3); }
.landing__point {
  display: flex; gap: var(--sp-3); align-items: flex-start;
  padding: var(--sp-4);
  background: var(--grad-card); border-radius: var(--radius-card);
  box-shadow: var(--shadow-lift), var(--shadow-edge);
}
.landing__point svg { color: var(--pink-ink); margin-top: 2px; }
.landing__point-title { font-family: var(--font-display); font-size: 15px; font-weight: 700; }
.landing__point-body { font-size: 13.5px; color: var(--ink-soft); line-height: 1.5; margin-top: 2px; }
.landing__foot { font-size: 12px; color: var(--ink-quiet); text-align: center; line-height: 1.5; }

/* ------------------------------------------------------------ states ---- */
.empty {
  display: flex; flex-direction: column; align-items: center; gap: var(--sp-3);
  padding: var(--sp-8) var(--sp-4); text-align: center; color: var(--ink-soft);
}
/* Every other illustration is an object on a keyed-out field, so it needs no
   frame. `empty_journey` is a full-bleed landscape -- there is no backdrop to
   key, and it would otherwise land as a bare rectangle on the page. Rounded and
   rimmed, it reads as a vignette instead. */
.empty > img {
  border-radius: var(--radius-tile);
  box-shadow: var(--shadow-edge);
}
.offline-bar {
  position: fixed; top: env(safe-area-inset-top); left: 50%; translate: -50% 0;
  z-index: var(--z-takeover);
  padding: var(--sp-2) var(--sp-4); margin-top: var(--sp-2);
  background: var(--peach-wash); color: var(--ink);
  border-radius: var(--radius-chip); box-shadow: var(--shadow-lift);
  font-size: 12px;
}

.skeleton {
  background: var(--card-sunk); border-radius: var(--radius-tile);
  animation: pulse 1.4s var(--ease-soft) infinite;
}
@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: .55; } }

/* ---------------------------------------------------------- arrival ----- */
/* A milestone was crossed since the last visit.
 *
 * The app knew every one of these and celebrated none of them: the timer just
 * kept counting and the island quietly changed. This is the one moment the
 * cat is genuinely FOR -- 72 hours in particular, which is the single most
 * useful fact this app holds and the point at which most people quit quitting.
 *
 * Shown once, dismissible, and derived from a stored key rather than from a
 * notification, so it works with no server, no permission prompt and no
 * background scheduler. */
.arrival {
  position: relative;
  background: var(--grad-hero);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-soft), var(--shadow-edge);
  padding: var(--card-pad);
  display: flex; align-items: center; gap: var(--sp-4);
  animation: arrive var(--dur-slow) var(--ease-spring);
}
@keyframes arrive {
  from { opacity: 0; transform: translateY(10px) scale(.98); }
  to   { opacity: 1; transform: none; }
}
.arrival__body { flex: 1; min-width: 0; }
.arrival__eyebrow {
  font-size: 11px; font-weight: 700; letter-spacing: .12em;
  text-transform: uppercase; color: var(--pink-ink);
}
.arrival__title {
  font-family: var(--font-display); font-size: 18px; font-weight: 700;
  line-height: 1.25; margin-top: 2px; text-wrap: balance;
}
.arrival__note { font-size: 12.5px; color: var(--ink-soft); margin-top: var(--sp-2); line-height: 1.45; }
.arrival__close {
  position: absolute; top: var(--sp-2); right: var(--sp-2);
  width: 36px; height: 36px; display: grid; place-items: center;
  border-radius: var(--radius-chip); color: var(--ink-soft);
}

/* ============================================================== V2: sea === */
/* The Cesta tab. The only screen in the app that is not on cream -- it is the
 * open sea, so it gets its own ground and the page motif is hidden behind it.
 *
 * Motion here is decoration in the strict sense: with prefers-reduced-motion
 * every animation below stops (tokens.css), and the screen still says exactly
 * where the boat is, because that comes from the transform JS sets, never from
 * an animation. */

/* V3: the sea is water, not blue paper.
 *
 * The depth gradient REPEATS every --sea-band-h instead of being stretched
 * over the whole map. Stretched over four thousand pixels it resolves to one
 * unchanging blue -- which is exactly what the old screen looked like. Repeated,
 * the eye gets a horizon rhythm no matter how far down the map you are.
 *
 * The sun bloom is a single non-repeating layer pinned to the top, because
 * light comes from one place. */
.screen--sea {
  background-color: var(--sea);
  background-image: var(--sea-sun), var(--sea-band);
  background-size: 100% 620px, 100% var(--sea-band-h);
  background-repeat: no-repeat, repeat-y;
  padding-bottom: calc(var(--tabbar-h) + var(--sp-8) + env(safe-area-inset-bottom));
}
.screen--sea .screen-head h1,
.screen--sea .screen-head p { color: var(--ink); }

.sea-status {
  position: sticky; top: var(--sp-2); z-index: 3;
  background: color-mix(in srgb, var(--card) 88%, transparent);
  backdrop-filter: blur(6px);
  border-radius: var(--radius-tile);
  padding: var(--sp-3) var(--sp-4);
  box-shadow: var(--shadow-lift);
  margin-bottom: var(--sp-4);
}
.sea-status__line {
  display: flex; align-items: center; gap: var(--sp-2);
  font-family: var(--font-display); font-size: 15px; font-weight: 600;
}
.sea-status__line svg { flex: none; color: var(--pink-ink); }
.sea-status__meta { font-size: 12px; color: var(--ink-soft); margin-top: var(--sp-1); }

.sea-progress {
  height: 6px; border-radius: var(--radius-chip); background: var(--card-sunk);
  box-shadow: var(--shadow-well);
  overflow: hidden; margin-top: var(--sp-2);
}
.sea-progress__fill {
  height: 100%; border-radius: var(--radius-chip);
  background: var(--grad-progress);
  transition: width var(--dur-slow) var(--ease-soft);
}

.sea-map { position: relative; margin: 0 calc(var(--screen-pad) * -1); }

/* ---- water surface ----
 * Crests, troughs and foam glints, on their own layer so the whole thing can
 * drift by TRANSFORM. Animating background-position instead would repaint a
 * four-thousand-pixel element sixty times a second on a phone; a transform on
 * a composited layer costs nothing. Inset negatively so the drift never
 * exposes an unpainted edge.
 *
 * V4 widened the swell tile from 67px to 96px (see tokens.css) because at
 * phone width the old repeat resolved as ruled paper. */
.sea-water {
  position: absolute; inset: -80px -60px;
  z-index: 0; pointer-events: none;
  background-image: var(--sea-glint), var(--sea-swell);
  background-size: 92px 78px, 100% 96px;
  animation: sea-drift 14s var(--ease-soft) infinite alternate;
  will-change: transform;
}
@keyframes sea-drift {
  from { transform: translate3d(-16px, 0, 0); }
  to   { transform: translate3d(16px, 6px, 0); }
}

/* ---- route ---- */
.sea-route { position: absolute; inset: 0; width: 100%; z-index: 1; pointer-events: none; }
/* The route ahead: a dashed line, clearly visible but plainly "not yet". */
.sea-route__line {
  fill: none; stroke: var(--sea-route); stroke-width: 4;
  stroke-linecap: round; stroke-dasharray: 2 11; opacity: .9;
}
/* The wake behind: solid, warm, and wider. The contrast between the two IS
 * the progress indicator on this screen, so they must not look alike. */
.sea-route__wake {
  fill: none; stroke: var(--wake); stroke-width: 6;
  stroke-linecap: round; opacity: 1;
  filter: drop-shadow(0 1px 2px rgba(120, 150, 175, .25));
  transition: stroke-dashoffset var(--dur-slow) linear;
}

/* ---- islands ---- */
.sea-islands { position: absolute; inset: 0; z-index: 2; }

/* The button is exactly the artwork, so translate(-50%,-50%) centres the
 * ISLAND on its route point. The text hangs off the side absolutely and does
 * not affect that centring. */
.sea-island {
  position: absolute; translate: -50% -50%;
  width: var(--island-size);
  display: grid; place-items: center;
  background: none; border: 0; padding: 0; cursor: pointer;
  transition: transform var(--dur-base) var(--ease-spring);
}
.sea-island:active { transform: scale(.95); }

/* ---- the label ----
 * V3 rebuild. The old label was a 12px word on a 82%-opaque pill, which on
 * open water read as a caption that had come loose from its island: too small
 * to be a title, too transparent to be an object, and unattached to anything.
 *
 * It is now a card in its own right -- the same lit-rim treatment every raised
 * surface in the app wears, plus a short connector to the island so the pairing
 * is stated rather than implied. */
.sea-island__text {
  position: absolute; top: 50%; translate: 0 -50%;
  left: calc(100% + 14px);
  width: 132px; text-align: left;
  display: flex; flex-direction: column; align-items: flex-start; gap: var(--sp-1);
}
.sea-island--flip .sea-island__text {
  left: auto; right: calc(100% + 14px);
  text-align: right; align-items: flex-end;
}

/* The connector. Sits in the 14px gap, vertically centred on the label. */
.sea-island__text::before {
  content: ''; position: absolute; top: 50%; translate: 0 -50%;
  left: -14px; width: 14px; height: 2px;
  background: var(--sea-foam); opacity: .8;
  border-radius: var(--radius-chip);
}
.sea-island--flip .sea-island__text::before { left: auto; right: -14px; }

.sea-island__art {
  /* Not yet reached: hazy and pale, like something on the horizon. Not
   * greyscale -- a grey island in a colour sea reads as broken, and locking
   * the user out of looking ahead is not the point.
   *
   * V3 pulled the haze back from .62 to .78. At .62, against a sea that now
   * has visible texture running behind it, a distant island stopped reading as
   * distant and started reading as a half-loaded image -- which is exactly the
   * complaint that turned out to be a real bug in the ART for the boat. Once
   * one thing on the screen is transparent by mistake, everything transparent
   * on purpose becomes suspect, so the deliberate version has to be
   * unmistakably deliberate. */
  filter: saturate(.6) brightness(1.04) blur(.3px);
  opacity: .78;
  transition: filter var(--dur-slow) var(--ease-soft), opacity var(--dur-slow) var(--ease-soft);
}
.sea-island.is-reached .sea-island__art { filter: none; opacity: 1; }

/* Labels sit on open water, so they get their own surface rather than relying
 * on a text-shadow to survive whatever passes behind them. */
.sea-island__label {
  font-family: var(--font-display);
  font-size: 13px; font-weight: 700;
  color: var(--ink); line-height: 1.25;
  text-wrap: balance;                 /* "Hladina nikotínu začína klesať" */
  padding: 7px 11px;
  border-radius: 15px;
  background: var(--grad-card);
  box-shadow: var(--shadow-label);
  /* A left accent bar carries the state, so reached / not reached is legible
   * without relying on a colour difference in the text itself. */
  border-left: 3px solid var(--sea-deep);
}
.sea-island--flip .sea-island__label {
  border-left: 0; border-right: 3px solid var(--sea-deep);
}

.sea-island:not(.is-reached) .sea-island__label {
  color: var(--ink-soft);
  background: color-mix(in srgb, var(--card) 84%, transparent);
  box-shadow: 0 2px 8px rgba(20, 44, 62, .22);
  border-left-color: var(--ink-quiet);
}
.sea-island--flip:not(.is-reached) .sea-island__label { border-right-color: var(--ink-quiet); }

.sea-island.is-reached .sea-island__label { border-left-color: var(--mint-ink); }
.sea-island--flip.is-reached .sea-island__label { border-right-color: var(--mint-ink); }

/* The countdown is a chip, not a second line of label: it is a different kind
 * of fact and it changes every second. */
.sea-island__when {
  font-size: 10.5px; font-weight: 700; letter-spacing: .01em;
  color: var(--pink-ink);
  padding: 3px 9px; border-radius: var(--radius-chip);
  background: var(--pink-wash);
  box-shadow: var(--shadow-lift);
  white-space: nowrap;
}
.sea-island__when:empty { display: none; }

/* The island the cat is at right now: lifted, with a soft halo, and the one
 * label in the app that inverts -- it is the answer to "where am I". */
.sea-island.is-current { z-index: 3; }
.sea-island.is-current .sea-island__art {
  animation: island-bob 4.5s var(--ease-soft) infinite;
  filter: drop-shadow(0 6px 14px rgba(214, 164, 178, .45));
}
.sea-island.is-current .sea-island__label {
  font-size: 14px;
  background: var(--grad-primary);
  color: var(--on-pink);
  border-left-color: var(--white);
  box-shadow: var(--shadow-btn), inset 0 1px 0 rgba(255, 255, 255, .35);
}
.sea-island--flip.is-current .sea-island__label { border-right-color: var(--white); }
.sea-island.is-current .sea-island__text::before { background: var(--pink); opacity: 1; }

@keyframes island-bob {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(-5px); }
}

/* ---- the boat ---- */
.sea-boat-wrap {
  position: absolute; left: 0; top: 0; z-index: 4;
  width: 64px; height: 64px;
  display: grid; place-items: center;
  pointer-events: none;
  /* No transition on transform: JS sets an absolute position derived from the
   * clock, and easing between two truths would put the boat somewhere it is
   * not. The bob below supplies the movement instead. */
  animation: boat-bob 3.2s var(--ease-soft) infinite;
}
.sea-boat-wrap.is-docked { animation-duration: 5s; }
.sea-boat { width: 64px; height: auto; transition: transform var(--dur-slow) var(--ease-soft); }

@keyframes boat-bob {
  0%, 100% { margin-top: 0; }
  50%      { margin-top: -6px; }
}

/* ---- dressing ---- */
.sea-deco-layer { position: absolute; inset: 0; z-index: 0; pointer-events: none; overflow: hidden; }
.sea-deco { position: absolute; opacity: .75; }
.sea-deco--cloud { animation: deco-drift 26s linear infinite; opacity: .8; }
.sea-deco--bird  { animation: deco-drift 19s linear infinite; opacity: .7; }
.sea-deco--wave  { animation: deco-swell 5.5s var(--ease-soft) infinite; opacity: .55; }
.sea-deco--fish  { animation: deco-jump 7s var(--ease-soft) infinite; opacity: .8; }

@keyframes deco-drift {
  0%   { transform: translateX(-14px); }
  50%  { transform: translateX(14px); }
  100% { transform: translateX(-14px); }
}
@keyframes deco-swell {
  0%, 100% { transform: translateY(0) scaleX(1); }
  50%      { transform: translateY(-4px) scaleX(1.06); }
}
@keyframes deco-jump {
  0%, 70%, 100% { transform: translateY(0) rotate(0); }
  80%           { transform: translateY(-12px) rotate(-12deg); }
  90%           { transform: translateY(-4px) rotate(6deg); }
}

/* ======================================================= V2: records ===== */
.records { display: flex; flex-direction: column; }

.record {
  display: flex; align-items: flex-start; gap: var(--sp-3);
  width: 100%; padding: var(--sp-3) 0; text-align: left;
  background: none; border: 0; border-bottom: 1px solid var(--card-sunk);
  cursor: pointer; font: inherit; color: inherit;
}
.record:last-child { border-bottom: 0; }
.record:active { opacity: .7; }

.record__face {
  flex: none; width: 34px; height: 34px; border-radius: 50%;
  display: grid; place-items: center; color: var(--on-scale);
  font-family: var(--font-display); font-weight: 700; font-size: 14px;
}
.record__face--1 { background: var(--scale-1); }
.record__face--2 { background: var(--scale-2); }
.record__face--3 { background: var(--scale-3); }
.record__face--4 { background: var(--scale-4); }
.record__face--5 { background: var(--scale-5); }

.record__body { flex: 1; min-width: 0; }
.record__when  { font-size: 11px; color: var(--ink-soft); }
.record__title { font-family: var(--font-display); font-size: 14px; font-weight: 600; }
.record__tags  { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 4px; }
.record__tag {
  font-size: 10px; padding: 2px var(--sp-2); border-radius: var(--radius-chip);
  background: var(--card-sunk); color: var(--ink-soft);
}
.record__note {
  font-size: 12px; color: var(--ink-soft); margin-top: 4px;
  overflow: hidden; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;
}
.record__side {
  flex: none; display: flex; align-items: center; gap: var(--sp-2);
  color: var(--ink-quiet); align-self: center;
}
.record__flag {
  font-size: 10px; padding: 2px var(--sp-2); border-radius: var(--radius-chip);
  background: var(--peach-wash); color: var(--peach-ink); font-weight: 700;
}

/* Recording a pouch. Distinct from the primary action and never beside it:
 * this is the one button on the screen with a consequence, and it sits alone.
 *
 * V4: this is now the ONLY treatment for the action, in both places it
 * appears. The /znova screen used to render the identical action as the pink
 * primary CTA with the escape as grey ghost text underneath, so the app had
 * two visual answers for one event and the more attractive of them reset the
 * user's clock. */
.btn--fail {
  background: var(--card); color: var(--peach-ink);
  border: 1.5px solid var(--peach-ink); box-shadow: var(--shadow-lift);
}
.btn--fail:active { background: var(--peach-wash); }

/* ==================================================== V2: statistics ===== */
/* Marks follow one spec: thin bars, 4px rounded data-ends anchored to the
 * baseline, a 2px surface gap between neighbours, recessive axis text, and a
 * value printed only on the peak. Every chart here is a SINGLE series, so
 * every bar in it is the same colour -- identity comes from the label. */

.chart {
  display: flex; align-items: flex-end; gap: 2px;
  height: 108px; margin-top: var(--sp-2);
  padding-top: 18px;                 /* room for the peak's value label */
  border-bottom: 1px solid var(--chart-grid);
}
.chart__col {
  flex: 1; min-width: 0; height: 100%;
  display: flex; flex-direction: column; justify-content: flex-end; align-items: center;
  position: relative;
}
.chart__bar {
  width: 100%; border-radius: 4px 4px 0 0;
  background: var(--chart-bar); opacity: .55;
  transition: opacity var(--dur-base) var(--ease-soft);
}
.chart__bar.is-peak { opacity: 1; }
.chart__col:hover .chart__bar { opacity: .85; }

.chart__value {
  position: absolute; top: -18px; left: 50%; translate: -50% 0;
  font-family: var(--font-display); font-size: 11px; font-weight: 700;
  color: var(--ink); white-space: nowrap;
}
.chart__tick {
  font-size: 9px; color: var(--ink-quiet); margin-top: 3px;
  white-space: nowrap; line-height: 1;
}

/* Ranked horizontal bars, where the label is a word. */
.hbars { display: flex; flex-direction: column; gap: var(--sp-2); margin-top: var(--sp-2); }
.hbar { display: flex; align-items: center; gap: var(--sp-3); }
.hbar__label {
  flex: 0 0 34%; font-size: 12px; color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.hbar__track {
  flex: 1; height: 12px; background: var(--card-sunk);
  box-shadow: var(--shadow-well); border-radius: var(--radius-chip);
}
.hbar__fill  { height: 100%; border-radius: var(--radius-chip); background: var(--chart-bar); }
.hbar__value {
  flex: none; font-size: 11px; color: var(--ink-soft);
  font-variant-numeric: tabular-nums;
}

/* The one categorical pair in the app. Both halves carry a word and a number,
 * so the colour is never the only thing telling them apart. */
.split { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-3); margin-top: var(--sp-2); }
.split__part {
  padding: var(--sp-3); border-radius: var(--radius-tile); text-align: center;
  box-shadow: var(--shadow-edge);
  border-top: 3px solid transparent;
}
.split__part--good { background: var(--mint-wash);  border-top-color: var(--chart-good); }
.split__part--bad  { background: var(--peach-wash); border-top-color: var(--chart-bad); }
.split__value { font-family: var(--font-display); font-size: 24px; font-weight: 700; color: var(--ink); }
.split__label { font-size: 11px; color: var(--ink-soft); }

.stat-line {
  display: flex; align-items: center; gap: var(--sp-2);
  font-size: 13px; color: var(--ink); margin-bottom: var(--sp-2);
}
.stat-line svg { flex: none; color: var(--pink-ink); }

/* ============================================================ V3: hero === */
/* The first thing on the home screen. It used to be the word "Dobrý deň" and
 * nothing else -- a greeting is not a reason to keep going, and the reason to
 * keep going is what someone opening this at 2am is looking for.
 *
 * The sentence changes with elapsed time (see BANDS in home.js): what it says
 * in hour two and what it says on day two hundred are about different
 * situations, because the person reading them is in a different one.
 *
 * V4 put the cat in it. The app had six mascot poses and used them as
 * scenery; the screen someone opens forty times a day had no cat on it at
 * all. It is now chosen by the clock and by state -- asleep in the small
 * hours, sad on the peak day, cheering after a craving is beaten. That is the
 * difference between a sticker and company, and company is the entire reason
 * to pick this app over a note in your phone. */
.hero {
  position: relative;
  background: var(--grad-hero);
  box-shadow: var(--shadow-soft), var(--shadow-edge);
  overflow: hidden;
}
.hero__greet {
  font-size: 11px; font-weight: 700;
  letter-spacing: .12em; text-transform: uppercase;
  color: var(--pink-ink);
  padding-top: var(--sp-2);
}
.hero__line {
  font-family: var(--font-display);
  font-size: 25px; font-weight: 700; line-height: 1.24;
  color: var(--ink);
  text-wrap: balance;
  margin-top: var(--sp-3);
  /* Room for the cat, which sits in the bottom-right corner of the card.
   * Reserved for the sprite's full width plus its inset rather than for how
   * much of it happens to overlap today -- text-wrap: balance decides the line
   * breaks, and one long Slovak compound is all it takes to push a word under
   * the cat's chin. */
  padding-right: 108px;
}
/* Only on a change, never on first paint -- see paint() in home.js. */
@keyframes hero-in {
  from { opacity: 0; transform: translateY(7px); }
  to   { opacity: 1; transform: none; }
}
.hero__line.is-new { animation: hero-in var(--dur-slow) var(--ease-soft); }

.hero__mascot {
  position: absolute; right: var(--sp-3); bottom: calc(var(--sp-3) * -1);
  width: 92px; pointer-events: none;
  animation: mascot-breathe 6s var(--ease-soft) infinite;
}
.hero__mascot img, .hero__mascot > div { width: 92px !important; height: auto !important; }
@keyframes mascot-breathe {
  0%, 100% { transform: translateY(0) rotate(0deg); }
  50%      { transform: translateY(-4px) rotate(-1.5deg); }
}

/* ---------------------------------------------- home: island closeup ---- */
.home-head {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: var(--sp-3);
}
.home-head .screen-head,
.home-head .hero__greet { flex: 1; }

.closeup {
  display: block; width: 100%; text-align: left;
  border: 0; cursor: pointer; font: inherit; color: inherit;
}
.closeup:active { transform: scale(.99); }

.closeup__scene {
  position: relative; height: 180px; overflow: hidden;
  border-radius: var(--radius-tile);
  background: var(--grad-sea);
  box-shadow: var(--shadow-well);
  margin-bottom: var(--sp-3);
}

/* The same crest-and-glint layer the map uses, so the little scene and the
 * full map are recognisably the same ocean. Confined to the water half. */
.closeup__scene::after {
  content: ''; position: absolute; inset: 42% -30px 0 -30px;
  pointer-events: none;
  background-image: var(--sea-glint), var(--sea-swell);
  background-size: 72px 60px, 100% 72px;
  opacity: .9;
  animation: sea-drift 11s var(--ease-soft) infinite alternate;
}

.closeup__sky {
  position: absolute; inset: 0 0 55% 0;
  background:
    var(--sea-sun),
    linear-gradient(180deg, var(--peach-wash) 0%, var(--sea-far) 100%);
}

/* The island the cat is on, and the next one small on the horizon. */
.closeup__island { position: absolute; left: 10px; bottom: 4px; z-index: 2; }
.closeup__next {
  position: absolute; right: 12px; bottom: 62px; z-index: 1;
  opacity: .5; filter: saturate(.5) blur(.6px);
}

.closeup__cat {
  position: absolute; z-index: 3;
  left: 85px; bottom: 66px; translate: -50% 0;
  animation: boat-bob 3.4s var(--ease-soft) infinite;
}
/* Sailing: crosses the gap between the two islands. `--sail` is the real leg
 * progress, set by home.js from the same voyage maths the map uses, so this
 * little scene cannot claim different progress from the Cesta tab. */
.closeup__cat.is-sailing {
  bottom: 20px;
  left: calc(30% + var(--sail, 0) * 48%);
}

/* A row of foam bumps along the shoreline.
 *
 * The stops fade to rgba(255,255,255,0), NOT to `transparent`. The keyword
 * interpolates through rgba(0,0,0,0), which paints a grey halo around every
 * bump -- which is exactly what it did here: four dirty grey semicircles
 * instead of white foam. */
.closeup__waves {
  position: absolute; inset: auto 0 0 0; height: 20px; z-index: 4;
  background: radial-gradient(circle at 50% 100%,
    rgba(255, 255, 255, .9) 0 9px, rgba(255, 255, 255, 0) 10px);
  background-size: 32px 20px;
  background-repeat: repeat-x;
  background-position: bottom center;
  animation: deco-swell 4.5s var(--ease-soft) infinite;
}

.closeup__label {
  font-family: var(--font-display); font-size: 15px; font-weight: 600; color: var(--ink);
}
.closeup__meta { font-size: 12px; color: var(--ink-soft); margin-top: 2px; margin-bottom: var(--sp-2); }

/* ========================================================== V3: health === */
/* Twenty-one symptoms, each shown against its own clock.
 *
 * THE TRACK IS THE WHOLE IDEA. A plain progress bar answers "how far through",
 * which is not what anybody wants to know at hour seventy-one. The question is
 * "is this as bad as it gets", so the track is the symptom's ENTIRE lifetime
 * end to end, with the peak marked on it and the user's own position marked
 * against that peak. Whether the worst is ahead or behind is then readable
 * without reading a word -- which matters, because the people most in need of
 * this screen are the least able to concentrate on it.
 *
 * V4 STOPPED PRINTING ALL TWENTY-ONE OF THEM AT ONCE. On day three seventeen
 * are active, and five consecutive rows carried the identical sentence
 * "Silnejšie ako teraz to už nebude". Repetition at that density stops reading
 * as reassurance and starts reading as wallpaper -- the eye skips the block,
 * which means the best content in the app was the content nobody read. The
 * three loudest are inline; the rest are one tap away, and the shared sentence
 * is printed once above the group instead of on every row. */

.health__lead { font-size: 13px; color: var(--ink-soft); line-height: 1.45; }
.health { display: flex; flex-direction: column; gap: var(--sp-5); margin-top: var(--sp-4); }
.health__group { display: flex; flex-direction: column; gap: var(--sp-2); }

.health__heading {
  display: flex; align-items: center; gap: var(--sp-2);
  font-family: var(--font-display); font-size: 14px; font-weight: 700;
}
.health__heading::before {
  content: ''; width: 9px; height: 9px; flex: none;
  border-radius: 50%; background: var(--ink-faint);
  box-shadow: var(--shadow-edge);
}
.health__group--now   .health__heading::before { background: var(--sym-peak); }
.health__group--ahead .health__heading::before { background: var(--sym-ahead); }
.health__group--past  .health__heading::before { background: var(--sym-done); }

.health__hint {
  font-size: 11.5px; color: var(--ink-quiet); line-height: 1.4;
  margin-bottom: var(--sp-1);
}

/* The shared sentence, hoisted out of the rows that all said it. */
.health__shared {
  font-size: 12px; font-weight: 700; color: var(--mint-ink);
  display: flex; align-items: center; gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-3);
  background: var(--mint-wash); border-radius: var(--radius-input);
  box-shadow: var(--shadow-edge);
}

/* The disclosure. It says how many and what kind, so the thing behind it is
 * never a mystery box. */
.health__more {
  display: flex; align-items: center; justify-content: center; gap: var(--sp-2);
  width: 100%; min-height: 46px; padding: 0 var(--sp-4);
  border-radius: var(--radius-chip);
  background: var(--card-sunk); box-shadow: var(--shadow-well);
  font-size: 13px; font-weight: 700; color: var(--ink-soft);
  transition: transform var(--dur-fast) var(--ease-spring);
}
.health__more:active { transform: scale(.985); }
.health__more svg { transition: transform var(--dur-base) var(--ease-soft); }
.health__more[aria-expanded="true"] svg { transform: rotate(180deg); }

.health__rest[hidden] { display: none; }
.health__rest { display: flex; flex-direction: column; gap: var(--sp-2); }

/* ---- one symptom ---- */
.sym {
  display: block; width: 100%; text-align: left;
  padding: var(--sp-3) var(--sp-4);
  border: 0; font: inherit; color: inherit;
  border-radius: var(--radius-tile);
  background: var(--grad-card);
  box-shadow: var(--shadow-lift), var(--shadow-edge);
  transition: transform var(--dur-fast) var(--ease-spring);
}
.sym:active { transform: scale(.985); }

.sym__head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--sp-2);
}
.sym__name { font-family: var(--font-display); font-size: 14.5px; font-weight: 600; }

.sym__phase {
  flex: none;
  font-size: 9.5px; font-weight: 700; letter-spacing: .06em;
  text-transform: uppercase; white-space: nowrap;
  padding: 3px 9px; border-radius: var(--radius-chip);
  background: var(--card-sunk); color: var(--ink-soft);
}

/* The rail holds the peak marker above the track, so the two never collide.
 * They collide on precisely the day the marker matters most. */
.sym__rail { position: relative; margin: var(--sp-4) 0 var(--sp-2); }

.sym__track {
  position: relative; height: 10px;
  border-radius: var(--radius-chip);
  /* The gradient is set inline per row -- it encodes where THAT symptom peaks,
   * which is the one thing about it CSS cannot know. */
  box-shadow: var(--shadow-well);
}

/* Everything still ahead of them, greyed over the curve. Not transparent:
 * the point is that the shape underneath is visible but plainly not yet. */
.sym__veil {
  position: absolute; top: 0; right: 0; bottom: 0;
  border-radius: 0 var(--radius-chip) var(--radius-chip) 0;
  background: var(--card-sunk);
  opacity: .88;
  transition: left var(--dur-slow) var(--ease-soft);
}

/* Where the worst of it is: a notch above the track, pointing at the spot.
 * Above rather than on, because at the peak the position knob sits exactly
 * here and would hide it. */
.sym__peak {
  position: absolute; bottom: 12px; translate: -50% 0;
  width: 0; height: 0;
  border-left: 5px solid transparent;
  border-right: 5px solid transparent;
  border-top: 6px solid var(--sym-peak);
}

/* Where they are. The only round thing on the row, so the eye finds it first. */
.sym__here {
  position: absolute; top: 50%; translate: -50% -50%;
  width: 15px; height: 15px; border-radius: 50%;
  background: var(--card);
  border: 3.5px solid var(--pink-ink);
  box-shadow: var(--shadow-lift);
  transition: left var(--dur-slow) var(--ease-soft);
}

.sym__meta { font-size: 11.5px; color: var(--ink-soft); line-height: 1.4; }

/* ---- per phase ----
 * The curve runs through the craving scale's own hues and stops before red,
 * for the reason that scale stops: a body doing exactly what the literature
 * says it will do on day three is not an error state. */

/* Not started: the whole curve is under the veil anyway, so the marker and the
 * notch are what carry the state. */
.sym--ahead .sym__peak { border-top-color: var(--sym-ahead); }
.sym--past  .sym__peak { opacity: .45; }

.sym--rising  .sym__phase { background: var(--peach-wash);    color: var(--peach-ink); }
.sym--peak    .sym__phase { background: var(--pink-wash);     color: var(--pink-ink); }
.sym--easing  .sym__phase { background: var(--mint-wash);     color: var(--mint-ink); }
.sym--ongoing .sym__phase { background: var(--lavender-wash); color: var(--lavender-ink); }
.sym--past    .sym__phase { background: var(--mint-wash);     color: var(--mint-ink); }
.sym--ahead   .sym__phase { background: var(--locked-bg);     color: var(--locked-ink); }

/* At its worst, a symptom gets a ring. It is the row that explains how the
 * person feels at the moment they are reading, so it stops being a list item. */
.sym--peak {
  box-shadow: var(--shadow-soft),
              inset 0 0 0 1.5px var(--pink-deep),
              inset 0 1px 0 rgba(255, 255, 255, .92);
}

/* Not yet, and already done: recessed rather than raised. Same information,
 * plainly not the part that is about right now. */
.sym--ahead, .sym--past {
  background: var(--grad-sunk);
  box-shadow: var(--shadow-edge);
}
.sym--ahead .sym__name, .sym--past .sym__name { color: var(--ink-soft); }
.sym--ahead .sym__here { border-color: var(--ink-quiet); }
.sym--past  .sym__here { border-color: var(--sym-done); }
/* Nothing left ahead, so nothing to veil -- and a zero-width veil still paints
 * its rounded right cap over the end of the curve. */
.sym--past .sym__veil, .sym--ongoing .sym__veil { display: none; }

/* ---- the detail sheet ---- */
.sym-sheet__head {
  display: flex; flex-direction: column; align-items: flex-start;
  gap: var(--sp-2); margin-bottom: var(--sp-4);
}
.sym__phase--lg { font-size: 10.5px; padding: 4px 12px; }


/* ==========================================================================
   V6 — two modes, a diary and a set of goals
   ========================================================================== */

/* ---------------------------------------------------------------- charts --
 *
 * Three shapes, all inline SVG built in charts.js. No chart library: the
 * alternative is tens of kilobytes of dependency for a bar chart with fourteen
 * bars in it.
 *
 * There is still NO RED. Over the limit is --chart-bad, a burnt orange, and the
 * day says so in words as well as in colour -- somebody logging their worst day
 * must not be shown an error colour for telling the truth. */

.ring-wrap { position: relative; display: grid; place-items: center; margin: var(--sp-2) 0 var(--sp-4); }
.ring { display: block; }
.ring__centre {
  position: absolute; inset: 0;
  display: grid; place-content: center; text-align: center; gap: 2px;
  pointer-events: none;
}
.ring__value {
  font-family: var(--font-display); font-weight: 700; font-size: 46px;
  line-height: 1; color: var(--ink);
  /* Tabular figures: the count changes while the ring is on screen, and
   * proportional digits make the whole number jump sideways when it does. */
  font-variant-numeric: tabular-nums;
}
.ring__label { font-size: 13px; color: var(--ink-soft); }
.ring__sub   { font-size: 12px; color: var(--ink-quiet); }

.bars-wrap, .spark-wrap { width: 100%; margin: var(--sp-3) 0 var(--sp-2); }
/* preserveAspectRatio="none" on the SVG plus a fixed height here is what lets
 * fourteen bars fill any phone width without the bars themselves distorting --
 * they are rects, and a stretched rect is still a rect. The dashed limit line
 * carries vector-effect: non-scaling-stroke for the same reason. */
.bars, .spark { display: block; width: 100%; height: 100%; }

.chart-empty {
  padding: var(--sp-5) 0; text-align: center;
  font-size: 13px; color: var(--ink-quiet);
}

/* ------------------------------------------------------- reduction home --
 *
 * THE DAY COUNTER.
 *
 * It replaced a ring, and the class names are deliberately nothing like
 * `.ring` -- that name was already owned by the SOS countdown, whose
 * `width: 74px` was silently squashing the home screen's 168px ring while a
 * 46px number went on being absolutely positioned over the wreckage. That was
 * the whole of "the numbers are out of centre". See charts.js.
 *
 * Everything here is in normal flow. Nothing is positioned on top of anything
 * else, so there is no centring left to get wrong. */

.daycount { margin: var(--sp-2) 0 var(--sp-4); }

.daycount__head {
  display: flex; align-items: baseline; justify-content: center;
  gap: var(--sp-2); margin-bottom: var(--sp-3);
}
.daycount__value {
  font-family: var(--font-display); font-weight: 700;
  font-size: 54px; line-height: 1; color: var(--ink);
  /* The count changes while the card is on screen; proportional digits make
   * the whole row jump sideways when it does. */
  font-variant-numeric: tabular-nums;
}
.daycount__value.is-over { color: var(--chart-bad); }
.daycount__of {
  font-family: var(--font-display); font-size: 17px; font-weight: 600;
  color: var(--ink-soft);
}
.daycount__sub {
  margin-top: var(--sp-2); text-align: center;
  font-size: 12px; color: var(--ink-quiet);
}

/* One box per pouch the user allowed themselves. flex-wrap matters: a limit of
 * twenty on a 360px phone wraps to a second row rather than shrinking every box
 * below a countable width. */
.daycount__track {
  display: flex; flex-wrap: wrap; justify-content: center;
  gap: 6px; min-height: 22px;
}
.daycount__seg {
  flex: 1 1 18px; max-width: 34px; height: 22px;
  border-radius: 7px;
  background: var(--card-sunk);
  box-shadow: var(--shadow-well);
  transition: background var(--dur-base) var(--ease-soft);
}
.daycount__seg.is-filled { background: var(--chart-good); box-shadow: var(--shadow-edge); }
/* Past the limit. Burnt orange, never red, and the sentence under the counter
 * says it in words as well -- colour is never the only cue. */
.daycount__seg.is-over           { background: var(--peach-wash); }
.daycount__seg.is-over.is-filled { background: var(--chart-bad); }

/* The fallback for a limit too large to draw as boxes. */
.daycount__bar {
  flex: 1 1 100%; height: 22px; border-radius: var(--radius-chip);
  background: var(--card-sunk); box-shadow: var(--shadow-well);
  overflow: hidden;
}
.daycount__bar-fill {
  display: block; height: 100%; border-radius: var(--radius-chip);
  background: var(--chart-good);
  transition: width var(--dur-base) var(--ease-soft);
}
.daycount__bar-fill.is-over { background: var(--chart-bad); }

/* A third column, for the "dalo sa mu vyhnúť" breakdown on the analytics
 * screen, where "nepovedal/a som" is a real answer and needs its own box. */
.split--three { grid-template-columns: repeat(3, 1fr); }
.split--three .split__value { font-size: 20px; }
.split--three .split__label { font-size: 10px; }

/* The quick log's two choices. The same weight as each other, like every other
 * pair of options in this app -- neither answer is the one we want. */
.quick__choices {
  display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-3);
  margin-bottom: var(--sp-4);
}
.quick__choice {
  min-height: 116px; padding: var(--sp-4) var(--sp-3);
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 6px; text-align: center;
  border-radius: var(--radius-input);
  border: 1.5px solid var(--pink-ink);
  background: var(--card); color: var(--pink-ink);
}
.quick__choice:active { transform: scale(.98); }
.quick__choice.is-busy { opacity: .6; }
.quick__choice-icon {
  width: 44px; height: 44px; border-radius: 50%;
  display: grid; place-items: center;
  background: var(--pink-wash);
}
.quick__choice-label { font-family: var(--font-display); font-size: 15px; font-weight: 700; }
.quick__choice-hint  { font-size: 11px; color: var(--ink-soft); line-height: 1.25; }

.today__ring { margin-top: var(--sp-2); }
.today__note {
  text-align: center; font-size: 14px; color: var(--ink-soft);
  margin: 0 0 var(--sp-2);
}
.today__since {
  text-align: center; font-size: 12px; color: var(--ink-quiet);
  margin-bottom: var(--sp-4);
}

/* The one control this screen exists for. Larger than a normal secondary
 * button and full width, because it is tapped several times a day and often
 * one-handed. */
.btn--log {
  width: 100%; min-height: 54px;
  display: flex; align-items: center; justify-content: center; gap: var(--sp-2);
  border-radius: var(--radius-btn);
  background: var(--grad-primary); color: var(--on-pink);
  font-family: var(--font-display); font-size: 17px; font-weight: 600;
  box-shadow: var(--shadow-btn);
  margin-bottom: var(--sp-3);
}
.btn--log:active { transform: scale(.985); box-shadow: var(--shadow-press); }

/* ------------------------------------------------------------ the pouch --
 *
 * The two buttons on the decision are DELIBERATELY IDENTICAL. V4 had to undo
 * the opposite mistake -- a pink primary under the word "Zlyhanie" while the
 * way out was small grey text -- and making "keep counting" the pretty one
 * would be the same error with the opposite politics. Two equal buttons; the
 * words decide. */
.pouch__field { margin-bottom: var(--sp-5); }
.pouch__field .card__title { margin-bottom: var(--sp-2); }

.pouch__decide {
  margin-top: var(--sp-5); padding-top: var(--sp-4);
  border-top: 1px solid var(--chart-grid);
}
.pouch__choices {
  display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-3);
  margin-top: var(--sp-3);
}
.btn--choice {
  min-height: 58px; padding: var(--sp-2) var(--sp-3);
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 4px; text-align: center;
  border-radius: var(--radius-input);
  border: 1.5px solid var(--pink-ink);
  background: var(--card);
  color: var(--pink-ink);
  font-family: var(--font-display); font-size: 14px; font-weight: 600;
  line-height: 1.15;
}
.btn--choice:active { transform: scale(.98); }
.btn--choice[disabled] { opacity: .45; }
.btn--choice.is-busy { opacity: .6; }

/* ---------------------------------------------------------- the timeline -- */

.diary__actions {
  display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-3);
  margin-bottom: var(--card-gap);
}

.timeline { display: flex; flex-direction: column; gap: var(--card-gap); }

.tl__day {
  background: var(--grad-card); border-radius: var(--radius-card);
  padding: var(--card-pad); box-shadow: var(--shadow-soft);
}
.tl__head {
  display: flex; align-items: center; gap: var(--sp-2);
  padding-bottom: var(--sp-3); margin-bottom: var(--sp-2);
  border-bottom: 1px solid var(--chart-grid);
}
.tl__date {
  font-family: var(--font-display); font-weight: 600; font-size: 15px;
  flex: 1; min-width: 0;
}
.tl__count {
  font-size: 12px; font-weight: 700; color: var(--ink-soft);
  background: var(--card-sunk); border-radius: var(--radius-chip);
  padding: 3px 10px; white-space: nowrap;
}
.tl__count.is-under { color: var(--chart-good); background: var(--mint-wash); }
.tl__count.is-over  { color: var(--chart-bad);  background: var(--peach-wash); }
.tl__mood { color: var(--ink-quiet); display: flex; }

.tl__rows { display: flex; flex-direction: column; }

.tl__row {
  display: grid; grid-template-columns: 46px 24px 1fr;
  align-items: start; gap: var(--sp-2);
  padding: var(--sp-3) 0;
  border-bottom: 1px solid var(--chart-grid);
  text-align: left; width: 100%;
}
.tl__row:last-child { border-bottom: 0; }
button.tl__row:active { opacity: .7; }

.tl__time {
  font-size: 12px; color: var(--ink-quiet);
  font-variant-numeric: tabular-nums; padding-top: 3px;
}
.tl__dot {
  width: 24px; height: 24px; border-radius: 50%;
  display: grid; place-items: center;
  background: var(--card-sunk); color: var(--ink-soft);
}
.tl__row--pouch   .tl__dot { background: var(--peach-wash); color: var(--peach-ink); }
.tl__row--craving .tl__dot { background: var(--mint-wash);  color: var(--mint-ink); }
.tl__row--checkin .tl__dot { background: var(--lavender-wash); color: var(--lavender-ink); }
.tl__row--event   .tl__dot { background: var(--sky-wash); color: var(--lavender-ink); }

.tl__body { min-width: 0; }
.tl__title { font-size: 14px; font-weight: 700; }
.tl__meta  { font-size: 12px; color: var(--ink-soft); margin-top: 2px; }
.tl__note  {
  font-size: 13px; color: var(--ink-soft); margin-top: var(--sp-2);
  /* Long notes wrap rather than stretching the grid column, which would push
   * the time and the dot off the left of a 320px screen. */
  overflow-wrap: anywhere;
}
.tl__row .record__tags { margin-top: var(--sp-2); }

/* An editable row carries a fourth column for the pencil. The glyph is quiet
 * on purpose -- it is an affordance, not a call to action, and the whole row is
 * the tap target anyway. */
.tl__row--edit { grid-template-columns: 46px 24px 1fr 18px; }
.tl__edit { color: var(--ink-faint); margin-top: 4px; }
button.tl__row--edit:active .tl__edit { color: var(--pink-ink); }

/* ---------------------------------------------------------------- goals -- */

.goals__row { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-3); }

/* The taper wizard's live preview: what the three answers add up to, before
 * anything is saved. */
.taper__preview {
  margin-top: var(--sp-2); padding: var(--sp-3) var(--sp-4);
  background: var(--card-sunk); border-radius: var(--radius-input);
  box-shadow: var(--shadow-well);
}
.taper__preview-line {
  font-family: var(--font-display); font-size: 15px; font-weight: 600;
  color: var(--ink);
}

.family { margin-top: var(--sp-5); }
.family:first-of-type { margin-top: var(--sp-3); }
.family__head {
  display: flex; align-items: baseline; gap: var(--sp-2);
  margin-bottom: var(--sp-3);
}
.family__title {
  font-family: var(--font-display); font-size: 14px; font-weight: 600;
  flex: 1; min-width: 0;
}
.family__count {
  font-size: 12px; font-weight: 700; color: var(--ink-quiet);
  font-variant-numeric: tabular-nums;
}

/* ------------------------------------------------------ knowledge cards -- */

.know-card {
  display: flex; align-items: flex-start; gap: var(--sp-3);
  text-align: left; width: 100%;
}
.know-card:active { transform: scale(.99); }
.know-card__icon {
  width: 40px; height: 40px; flex: none; border-radius: 50%;
  display: grid; place-items: center;
  background: var(--sky-wash); color: var(--lavender-ink);
}
.know-card__body { flex: 1; min-width: 0; }
.know-card__title {
  font-family: var(--font-display); font-size: 15px; font-weight: 600;
  margin-bottom: 2px;
}
/* One paragraph of preview, clamped. The sheet has the rest, and a card that
 * prints four paragraphs is not a card. */
.know-card__body .card__note {
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden;
}

/* ------------------------------------------------------------- check-in -- */

.checkin__q { margin-bottom: var(--sp-5); }
.checkin__q .card__title { margin-bottom: var(--sp-1); }

.checkin__row { display: flex; gap: var(--sp-5); margin: var(--sp-3) 0; }
.checkin__stat {
  display: flex; align-items: center; gap: var(--sp-2);
  color: var(--ink-soft);
}
.checkin__stat-label { font-size: 13px; }
.checkin__note {
  font-size: 13px; color: var(--ink-soft); margin: 0 0 var(--sp-3);
  overflow-wrap: anywhere;
}

/* ------------------------------------------------------------- oddments -- */

/* The mode picker on onboarding step 1: two options that each need room for a
 * sentence, not just a label. */
.option--tall { align-items: flex-start; padding: var(--sp-4); }
.option--tall svg { margin-top: 2px; }

/* A record row with no craving score. Neutral rather than any point on the
 * 1-5 ramp, because "not reported" is not a quiet 1. */
.record__face--none { background: var(--card-sunk); color: var(--ink-soft); }

/* The slip line under the timer. Hidden entirely when there are none, rather
 * than rendering an empty paragraph that still takes its margin. */
.timer__slips { margin-top: var(--sp-3); }
.timer__slips[hidden] { display: none; }

/* ---------------------------------------------------------- narrow, V6 -- */
@media (max-width: 370px) {
  .ring__value      { font-size: 38px; }
  .daycount__value  { font-size: 44px; }
  .daycount__seg    { height: 18px; border-radius: 6px; }
  .quick__choice    { min-height: 104px; }
  .split--three     { gap: var(--sp-2); }
  .pouch__choices   { grid-template-columns: 1fr; }
  .goals__row       { grid-template-columns: 1fr; }
  .diary__actions   { grid-template-columns: 1fr; }
  .tl__row          { grid-template-columns: 40px 22px 1fr; }
  .tl__row--edit    { grid-template-columns: 40px 22px 1fr 16px; }
}

/* ---------------------------------------------------------- narrow ------ */
@media (max-width: 370px) {
  :root { --screen-pad: 16px; --card-pad: 16px; }
  .timer__value { font-size: 26px; }
  .stat__value  { font-size: 22px; }
  .badge-grid   { grid-template-columns: repeat(3, 1fr); }
  .hero__line   { font-size: 22px; padding-right: 66px; }
  .hero__mascot { width: 78px; right: var(--sp-2); }
  .hero__mascot img, .hero__mascot > div { width: 78px !important; }
  .sea-island__text { width: 112px; }
  .landing__title { font-size: 26px; }
  .scale5__dot { width: 27px; height: 27px; }
  .tabbar__item { font-size: 10px; }
}
