/* ---------------------------------------------------------------------------
   Design system "Shirogane". Mobile first, laid out for a 390pt iPhone and
   capped at 480px so it stays a single readable column on anything wider.

   The rules of this system, in order of how often they decide something:

   1. Structure comes from SURFACE, not from outline. An app screen (today,
      history, settings, plans) is a #F6F7F9 wash with white cards floating on
      it, and those cards carry no border at all: a whisper shadow
      (0 1px 3px rgba(26,29,33,.05)) and the tone change do the work a hairline
      used to. Design v4 amendment, and it replaces the "borders not shadows"
      rule this system opened with: the hairline measured 1.15:1 on white and
      on a phone, in daylight, it was not there. Outside the app screens (the
      landing page, onboarding, the legal pages) the canvas is still white and
      structure is still a 1px border, which is why the landing page's 料金 card
      is the only bordered card left on it. The dock, the camera button and the
      controls that float over photography keep their own deeper shadows.
   2. Things you act on are near-black ink pills. There is no coloured accent.
      The vermilion this app used to carry is gone.
   3. Colour belongs to DATA and to nothing else. Three macro hues (P, F, C)
      and a five step health ramp, and every one of them was validated against
      a white surface rather than picked by eye. The figures are printed beside
      every coloured mark, so colour is never the only thing carrying a value.
   4. Hierarchy comes from WEIGHT and SIZE, not from colour. 700/800 against
      400, and one step down in size, does the work a second colour would.

   Numbers are tabular so a figure does not jump around as it changes.

   No webfont: the Japanese system stack renders instantly and costs no bytes on
   a phone. Hiragino Sans is what an iPhone actually draws.
   --------------------------------------------------------------------------- */

:root {
  /* --- surfaces ---------------------------------------------------------- */
  --canvas: #ffffff;
  /* The grouping tone. Used for bands behind white cards, for input wells and
     for the ring's track. Never for text. */
  --wash: #f6f7f9;
  --line: #edeff2;

  /* --- ink --------------------------------------------------------------- */
  --ink: #1a1d21; /* 16.9:1 on the canvas */
  --ink-muted: #6b7280; /* 4.8:1 on the canvas, 4.5:1 on the wash */
  /* A second muted step, one shade darker (7.6:1 on the canvas, 7.1:1 on the
     wash). It belongs to the small labels that sit directly against a figure:
     next to 44px of 800-weight ink, --ink-muted reads as switched off, and a
     label you cannot read is not subordinate, it is missing. Everything a
     sentence long stays on --ink-muted. */
  --ink-muted-2: #4b5563;

  /* --- action ------------------------------------------------------------ */
  /* Was the accent. It is now ink, which is why it has its own token: the thing
     you act on is still a single decision to re-theme. */
  --action: #1a1d21;
  --action-ink: #ffffff;

  /* --- data colours ------------------------------------------------------
     P たんぱく質, F 脂質, C 炭水化物. Validated as a categorical palette against
     a #FFFFFF surface with the dataviz validator: lightness band, chroma floor,
     CVD separation, normal-vision floor and contrast vs surface all pass.
     Contrast on white is 3.96 / 3.34 / 3.42 to 1, above the 3:1 floor for marks.
     The deutan separation of the P/C pair lands at 7.9 (the 6-8 floor band),
     which is legal here because every mark ships with its own label and figure:
     nobody has to tell two hues apart to read a number.

     Do not brighten these back up. The bright amber this started from
     (#F5A623) measures 1.97:1 on white, which is a bar you cannot see. */
  --macro-p: #e04b54;
  --macro-f: #4a90d9;
  --macro-c: #ce8500;

  /* --- status ------------------------------------------------------------
     Errors only. The fill and rule use the P hue; error TEXT uses a darker step
     of the same hue because 14px type needs 4.5:1 and #E04B54 gives 3.96. */
  --danger: #e04b54;
  --danger-ink: #b4232e; /* 6.5:1 on the canvas */
  --danger-tint: #fdf2f3;

  /* --- geometry ----------------------------------------------------------
     Four radii and no more. Every rounded thing in this app is one of:
     12 (inputs, tiles, buttons), 16 (cards and rows), 999 (pills and chips),
     24 (sheets). The in-between values this started with (14, 18) were the
     difference nobody could see and everybody had to pick from. */
  --radius-sm: 12px; /* inputs, tiles, buttons */
  --radius: 12px; /* kept as the name most rules already use */
  --radius-lg: 16px; /* cards and rows */
  --radius-sheet: 24px; /* bottom sheets, and the top of the photo sheet */
  /* The only shadow a card gets. A whisper: it is the wash behind it that
     separates the card from the page, and this keeps the edge from dissolving
     where the two tones nearly meet. */
  --card-shadow: 0 1px 3px rgba(26, 29, 33, 0.05);
  --tap: 44px;
  --tabbar-h: 58px;
  /* The floating tab dock: pill height, and the gap between it and the bottom
     safe area. The camera button is the same height as the pill and sits on the
     same line, so one number sizes both. */
  --dock-h: 56px;
  --dock-gap: 12px;

  /* The photo screen's fixed action bar, measured rather than guessed: 52px of
     button plus its 10px of padding top and bottom. The scrolling sheet above it
     reserves this plus 20px, so nothing on that screen can ever be bisected by
     the bar the way the macro tiles were on the owner's phone. Change the bar's
     button height or padding and change this in the same edit. */
  --phbar-h: 72px;

  /* The calorie ring's circumference: 2 * pi * r, with r = 78 in the SVG's
     200x200 viewBox. Kept here so the ring can be resized by editing the
     radius in views/today.ejs and this one number. */
  --ring-c: 490.09px;

  /* Every SMALL ring in the app (the day hero's, the three macro cards', the
     seven week-strip days') is drawn from the same circle: r = 44 in a 100x100
     viewBox, so one circumference covers all three. The rendered size is set in
     CSS per family, and the stroke width with it; the arithmetic that turns a
     percent into a dash offset is the single .ring-arc rule below and does not
     care how big the ring ends up. 2 * pi * 44. */
  --ring-c-sm: 276.46px;

  --font: "Hiragino Sans", "Noto Sans JP", "Yu Gothic UI", sans-serif;

  /* The page is a committed light design. Declaring it keeps iOS from tinting
     form controls for a dark system setting behind our back. */
  color-scheme: light;
}

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

[hidden] {
  display: none !important;
}

html {
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  background: var(--canvas);
  color: var(--ink);
  font-family: var(--font);
  font-size: 16px;
  line-height: 1.7;
  -webkit-font-smoothing: antialiased;
}

/* An app screen. The wash is the page and the white cards float on it: see rule
   1 at the top of this file. Set for every screen inside the app, dock or no dock
   (the paywall is one of these and has no dock). */
body.app {
  background: var(--wash);
}

/* Room for the floating dock, the gap under it, and the home indicator, plus the
   56px that used to be missing: the dock is 56px of opaque pill sitting over the
   page, and on the day screen it was landing on the estimate disclaimer. Measured
   against the tallest thing that can be last on a screen. */
body.has-tabbar {
  padding-bottom: calc(var(--dock-h) + var(--dock-gap) + 56px + env(safe-area-inset-bottom));
}

/* An app screen starts at its own header rather than 24px below the status bar:
   今日 opens with the app name and the week, and the page should feel like it
   begins at the top of the phone. The landing page and onboarding keep the
   roomier column. */
body.app .wrap {
  padding-top: 12px;
}

/* A screen whose first element is the sticky app bar has no gap above it at
   all: the bar IS the top of the screen, and 12px of wash above a bar that is
   about to stick to y=0 reads as a seam. :has() is doing something load bearing
   here rather than clever: a browser without it keeps the 12px, which is the
   old layout and is fine. */
body.app .wrap:has(> .appbar) {
  padding-top: 0;
}

/* Borderless white cards on the wash, for every card on an app screen. The
   landing page, onboarding and the legal pages are on the white canvas and keep
   their 1px borders, which is what leaves the landing page's 料金 card the only
   bordered card on it. */
body.app .card,
body.app .macro-card,
body.app .mealrow,
body.app .macro-row li {
  border: 0;
  box-shadow: var(--card-shadow);
}

/* A full-screen photo moment (the result screen, the scan preview). No column,
   no top padding: the photograph runs to the top edge of the viewport, and the
   only things that dodge the notch are the floating buttons, which offset
   themselves with env(safe-area-inset-top). */
body.screen-photo {
  padding-bottom: 0;
  /* A rubber-band scroll on a screen whose photo is the background looks like
     the layout came apart. */
  overscroll-behavior-y: none;
}

.wrap {
  max-width: 480px;
  margin: 0 auto;
  padding: 24px 16px 40px;
  /* Landscape and the notch: keep text off the rounded edges. */
  padding-left: max(16px, env(safe-area-inset-left));
  padding-right: max(16px, env(safe-area-inset-right));
}

h1,
h2,
h3 {
  margin: 0 0 10px;
  line-height: 1.45;
  font-weight: 700;
  letter-spacing: 0.01em;
}

h1 {
  font-size: 26px;
  letter-spacing: -0.01em;
}

h2 {
  font-size: 19px;
}

h3 {
  font-size: 16px;
}

p {
  margin: 0 0 12px;
}

p:last-child {
  margin-bottom: 0;
}

a {
  color: var(--ink);
  text-decoration-color: #c9cfd6;
  text-underline-offset: 3px;
}

.num {
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}

.muted {
  color: var(--ink-muted);
  font-size: 14.5px;
}

.eyebrow {
  margin: 0 0 8px;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--ink-muted);
}

.lead {
  font-size: 16.5px;
}

.fineprint {
  margin: 10px 0 0;
  font-size: 13px;
  color: var(--ink-muted);
}

.note {
  margin: 14px 0 0;
  padding: 12px 14px;
  background: var(--wash);
  border-radius: var(--radius);
  font-size: 14px;
}

/* A wash note on a wash page is not a note, it is a paragraph. On an app screen
   it becomes a white card like every other surface there. */
body.app .note {
  background: var(--canvas);
  box-shadow: var(--card-shadow);
}

.disclaimer {
  margin: 20px 0 8px;
  font-size: 12.5px;
  color: var(--ink-muted);
}

/* On an app screen the floating dock is 56px of opaque pill over the bottom of
   the page, and this line is usually the last thing on it. It gets its own margin
   above the dock zone so it is never read through the dock's shadow. */
body.app .disclaimer {
  margin: 24px 0 0;
}

/* --- blocks --------------------------------------------------------------- */

.hero {
  margin-bottom: 22px;
}

.card {
  background: var(--canvas);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  padding: 20px 18px;
  margin-bottom: 14px;
}

.card h1 {
  margin-bottom: 8px;
}

/* --- buttons -------------------------------------------------------------- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 50px;
  padding: 12px 20px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--canvas);
  color: var(--ink);
  font: inherit;
  font-size: 16px;
  font-weight: 600;
  text-decoration: none;
  cursor: pointer;
  transition: opacity 0.15s ease, transform 120ms ease;
}

/* Press feedback. 3% for 120ms, on everything you can press: it is the one piece
   of motion that answers a finger rather than decorating a screen, and without it
   a web app on a phone feels like a web page. Turned off with the rest of the
   motion under prefers-reduced-motion. */
.btn:active {
  opacity: 0.82;
  transform: scale(0.97);
}

/* The one thing to do on the screen. */
.btn-primary {
  background: var(--action);
  border-color: var(--action);
  color: var(--action-ink);
  font-weight: 700;
}

.btn-quiet {
  background: var(--canvas);
  border-color: var(--line);
  color: var(--ink);
}

.btn-block {
  display: flex;
  width: 100%;
  margin-top: 16px;
}

:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 2px;
}

/* --- landing -------------------------------------------------------------- */

.steps {
  list-style: none;
  margin: 0;
  padding: 0;
}

.steps li {
  display: flex;
  gap: 12px;
  padding: 14px 0;
  border-top: 1px solid var(--line);
}

.steps li:first-child {
  border-top: 0;
  padding-top: 4px;
}

.step-n {
  flex: 0 0 26px;
  height: 26px;
  margin-top: 4px;
  border-radius: 50%;
  background: var(--wash);
  color: var(--ink);
  font-size: 13px;
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  text-align: center;
  line-height: 26px;
}

.steps h3 {
  margin-bottom: 2px;
}

.steps p {
  margin: 0;
  font-size: 14.5px;
  color: var(--ink-muted);
}

.plain-list {
  margin: 8px 0 0;
  padding-left: 1.15em;
}

.plain-list li {
  margin-bottom: 6px;
  font-size: 14.5px;
}

.plan {
  padding: 14px 0;
  border-top: 1px solid var(--line);
}

.plan:first-of-type {
  border-top: 0;
  padding-top: 2px;
}

.plan-price {
  margin: 0;
  font-size: 26px;
  font-weight: 800;
  letter-spacing: -0.01em;
  font-variant-numeric: tabular-nums;
}

.foot {
  margin-top: 28px;
  padding-top: 18px;
  border-top: 1px solid var(--line);
  color: var(--ink-muted);
  font-size: 13px;
}

.foot-brand {
  margin: 0 0 8px;
  font-weight: 600;
}

.foot-links {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 16px;
}

.foot-links a {
  color: var(--ink-muted);
  /* The tap target is padding, deliberately, not a min-height on a flex box:
     a flex item with align-items:center draws its underline along the bottom
     edge of the 44px box instead of under the text, which reads as a stray
     rule. 13px of line plus 15px above and below clears 44px. */
  padding: 15px 0;
}

/* --- numbers -------------------------------------------------------------- */

.kcal-block {
  margin: 18px 0 4px;
}

.kcal-figure {
  display: flex;
  align-items: baseline;
  gap: 6px;
  margin: 0;
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}

.kcal-figure .num {
  font-size: 52px;
  font-weight: 800;
  line-height: 1.05;
  letter-spacing: -0.03em;
}

.kcal-figure.small .num {
  font-size: 34px;
}

.kcal-figure .unit {
  font-size: 16px;
  font-weight: 700;
  color: var(--ink-muted);
}

.kcal-label {
  margin: 6px 0 0;
  font-size: 14px;
  color: var(--ink-muted);
}

/* --- the calorie ring ----------------------------------------------------- */
/* The day's headline. Ink arc on a wash track, and the arc completes and turns
   the P hue on a day that went over, with the label saying 超過 rather than
   pretending the number is still a remainder.

   The progress is a CLASS, not a style attribute: this app's
   Content-Security-Policy has no 'unsafe-inline' in style-src. public/style.css
   carries .ring-p-0 to .ring-p-100, each of which sets one custom property
   (--ring-p, a whole percent), and the single rule below turns that into a
   stroke-dashoffset. One class per percent and one arithmetic rule, rather than
   101 precomputed offsets, so the ring's radius stays editable in two places
   (the SVG and --ring-c) instead of 101. */
.ring {
  position: relative;
  width: 216px;
  height: 216px;
  margin: 8px auto 4px;
}

.ring-svg {
  display: block;
  width: 100%;
  height: 100%;
}

.ring-track {
  fill: none;
  stroke: var(--wash);
  stroke-width: 11;
}

/* The track needs an edge of its own on the small rings: at 34px a wash-coloured
   circle behind a coloured arc disappears, and the day circles in the week strip
   have to read as circles even on a day with nothing logged. */
.ring-sm .ring-track {
  stroke: var(--line);
}

.ring-arc {
  fill: none;
  stroke: var(--ink);
  stroke-width: 11;
  stroke-linecap: round;
  stroke-dasharray: var(--ring-c);
  stroke-dashoffset: calc(var(--ring-c) - var(--ring-c) * var(--ring-p, 0) / 100);
  transition: stroke-dashoffset 0.45s ease;
  /* Every arc in the app draws itself once on load. It is the only motion on the
     day screen and it is doing one job: the eye follows the arc to where the day
     actually is, instead of arriving at a shape that was always there. The
     keyframe has no `to`: the resting value is the declared one above, so this
     animation cannot disagree with the arithmetic. */
  animation: ring-draw 600ms cubic-bezier(0.2, 0.8, 0.2, 1);
}

@keyframes ring-draw {
  from {
    stroke-dashoffset: var(--ring-c);
  }
}

/* Nothing eaten yet. A grey donut at 0% is a heavy piece of furniture reporting
   nothing, so the ring thins to a dashed hairline and the arc is not drawn at
   all: the shape says "this is where the day goes" and stops there. */
/* Named per ring family rather than once, and it has to be: each family sets its own
   stroke width further down this file (the hero's 10, the macro cards' 10), and a
   two-class rule up here loses to a two-class rule down there. Three classes wins
   wherever it sits. */
.hero-ring.ring-zero .ring-track,
.mring.ring-zero .ring-track,
.wkring.ring-zero .ring-track {
  stroke: #d7dce2;
  stroke-width: 3;
  stroke-dasharray: 3 5;
}

.ring-zero .ring-arc {
  display: none;
}

/* Over the day's target. The ring is full, and the hue is the one this app
   already uses for protein: it is the only red in the system, so borrowing it
   for "over" adds no new colour. */
.ring-arc.ring-over {
  stroke: var(--macro-p);
}

/* Inset from the arc rather than filling the square: a four figure remainder
   ("1,367") is the widest thing that goes in here, and at the vertical middle
   the circle's inner edge is only about 68px either side of the centre. The
   padding is the guard rail, and the figure never wraps. */
.ring-center {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  padding: 0 14px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
}

.ring-num {
  margin: 0;
  font-size: 42px;
  font-weight: 800;
  line-height: 1.05;
  letter-spacing: -0.035em;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}

.ring-cap {
  margin: 4px 0 0;
  font-size: 13px;
  font-weight: 700;
  color: var(--ink-muted);
}

.ring-target {
  margin: 6px 0 0;
  font-size: 12px;
  color: var(--ink-muted);
  font-variant-numeric: tabular-nums;
}

.ring-note {
  margin: 8px 0 0;
  font-size: 13.5px;
  text-align: center;
  /* Deliberately NOT text-wrap: balance. Japanese wraps between kana, so
     balancing two lines here splits a word in the middle (のう / ち) instead of
     evening out a ragged edge. Natural fill is what a Japanese reader expects. */
}

/* --- macro cards ---------------------------------------------------------- */
/* Three tap sized cards in a row: dot, label, remaining grams, slim bar. The
   dot and the bar are the only coloured things, the grams are ink, and the
   label is spelled out, so the colour is a shortcut rather than the message. */
.macro-cards {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px;
  list-style: none;
  margin: 0 0 14px;
  padding: 0;
}

.macro-card {
  min-height: var(--tap);
  padding: 12px 10px 13px;
  background: var(--canvas);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
}

/* A label of 11.5px sitting directly above a 21px figure. At 600 and --ink-muted it
   read as switched off, so it takes the small-label treatment: 700, 0.02em of
   tracking (kana and Latin both need the air at this size), and the darker muted
   step. Everything a sentence long stays on --ink-muted. */
.macro-card-label {
  display: flex;
  align-items: center;
  gap: 5px;
  margin: 0;
  font-size: 11.5px;
  font-weight: 700;
  line-height: 1.3;
  color: var(--ink-muted-2);
  letter-spacing: 0.02em;
}

.macro-card-figure {
  display: flex;
  align-items: baseline;
  gap: 1px;
  margin: 7px 0 0;
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}

.macro-card-num {
  font-size: 21px;
  font-weight: 800;
  line-height: 1.1;
  letter-spacing: -0.02em;
}

.macro-card-unit {
  font-size: 12px;
  font-weight: 700;
  color: var(--ink-muted);
}

.macro-card-state {
  margin: 1px 0 0;
  font-size: 11px;
  line-height: 1.4;
  color: var(--ink-muted);
}

.macro-card .bar {
  margin: 9px 0 0;
}

/* The coloured dot. 8px, so it reads at arm's length without becoming
   decoration. */
.dot {
  flex: 0 0 8px;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--ink-muted);
}

.dot-p {
  background: var(--macro-p);
}

.dot-f {
  background: var(--macro-f);
}

.dot-c {
  background: var(--macro-c);
}

/* --- macro chips ---------------------------------------------------------- */
/* The per meal reading of the same three numbers. A row of chips rather than a
   divided three up box: a meal card is already a card, and a box inside a box
   needs a reason. */
.macro-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  list-style: none;
  margin: 14px 0 0;
  padding: 0;
}

.macro-chip {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 5px 10px;
  border: 1px solid var(--line);
  border-radius: 999px;
  font-size: 12.5px;
  white-space: nowrap;
}

.macro-chip-label {
  color: var(--ink-muted);
}

.macro-chip-value {
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}

.macro-chip-unit {
  font-size: 11px;
  font-weight: 700;
  color: var(--ink-muted);
}

/* The three up target readout on the onboarding result. Same grammar as the
   macro cards, without the bars: there is nothing to be a fraction of yet. */
.macro-row {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px;
  list-style: none;
  margin: 18px 0 0;
  padding: 0;
}

.macro-row li {
  padding: 12px 10px 13px;
  background: var(--canvas);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
}

/* And on the onboarding result's three-up readout. */
.macro-label {
  display: flex;
  align-items: center;
  gap: 5px;
  font-size: 11.5px;
  font-weight: 700;
  line-height: 1.3;
  color: var(--ink-muted-2);
  letter-spacing: 0.02em;
}

.macro-value {
  display: flex;
  align-items: baseline;
  gap: 1px;
  margin-top: 7px;
  font-size: 21px;
  font-weight: 800;
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
}

.macro-unit {
  font-size: 12px;
  font-weight: 700;
  color: var(--ink-muted);
}

.answer-list {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 6px 16px;
  margin: 0 0 14px;
  font-size: 15px;
}

.answer-list dt {
  color: var(--ink-muted);
}

.answer-list dd {
  margin: 0;
  font-weight: 700;
}

/* --- choice cards and form fields ----------------------------------------- */
/* Shared by 設定's profile form and by every selection screen in the onboarding
   flow. The rules the OLD stepper owned (.step-form, .step-count, .step,
   .step legend, .step-help, .step-nav) went with its markup: views/onboarding.ejs
   was replaced wholesale by the flow, and nothing renders them any more. */

.choices {
  display: grid;
  gap: 10px;
}

.choice {
  display: block;
  position: relative;
}

.choice input {
  position: absolute;
  top: 0;
  left: 0;
  width: 1px;
  height: 1px;
  opacity: 0;
}

.choice-body {
  display: block;
  min-height: 56px;
  padding: 14px 16px;
  background: var(--canvas);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  transition: border-color 0.15s ease, background-color 0.15s ease;
}

.choice-title {
  display: block;
  font-size: 16.5px;
  font-weight: 600;
}

.choice-desc {
  display: block;
  margin-top: 2px;
  font-size: 13.5px;
  line-height: 1.6;
  color: var(--ink-muted);
}

/* Selected. Ink border and a wash fill, which is the same pair the rest of the
   system uses for "this one": no colour is spent on saying yes. */
.choice input:checked + .choice-body {
  border-color: var(--ink);
  background: var(--wash);
  box-shadow: inset 0 0 0 1px var(--ink);
}

.choice input:focus-visible + .choice-body {
  outline: 2px solid var(--ink);
  outline-offset: 2px;
}

.field {
  display: block;
  margin-bottom: 4px;
}

.field-label {
  display: block;
  margin-bottom: 6px;
  font-size: 14px;
  color: var(--ink-muted);
}

.field-input {
  display: flex;
  align-items: center;
  gap: 8px;
}

.field input {
  width: 100%;
  min-height: 52px;
  padding: 12px 14px;
  background: var(--canvas);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  color: var(--ink);
  font-family: inherit;
  /* 17px, never below 16px: iOS Safari zooms the page in on a focused field
     with smaller text. */
  font-size: 17px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  appearance: none;
  -webkit-appearance: none;
}

.field input:focus {
  border-color: var(--ink);
  outline: none;
}

.field-unit {
  flex: 0 0 auto;
  font-size: 15px;
  font-weight: 700;
  color: var(--ink-muted);
}

.alert {
  margin: 0 0 16px;
  padding: 12px 14px;
  background: var(--danger-tint);
  border-left: 3px solid var(--danger);
  border-radius: var(--radius-sm);
  font-size: 14.5px;
}

.field-error {
  margin: 8px 0 0;
  color: var(--danger-ink);
  font-size: 14px;
  font-weight: 700;
}

/* --- the floating tab dock ------------------------------------------------- */
/* Two destinations in a floating white pill, and the camera as a separate ink
   circle on the same line. 設定 moved to the day screen's header: it is a place
   you go once a month, and giving it a third of the bar said otherwise.

   The dock and the camera button are the only things in this system allowed a
   shadow. They float over scrolling content, and a floating control with no
   shadow does not read as floating. */
.tabdock {
  position: fixed;
  left: 0;
  right: 0;
  bottom: calc(var(--dock-gap) + env(safe-area-inset-bottom));
  z-index: 20;
  display: flex;
  align-items: center;
  gap: 10px;
  max-width: 480px;
  margin: 0 auto;
  padding-left: max(16px, env(safe-area-inset-left));
  padding-right: max(16px, env(safe-area-inset-right));
  /* The dock is a strip across the screen; only the pill and the button in it
     are surfaces, so the gap between them lets the page through. */
  pointer-events: none;
}

.tabpill {
  flex: 1 1 auto;
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  height: var(--dock-h);
  background: var(--canvas);
  border: 1px solid var(--line);
  border-radius: calc(var(--dock-h) / 2);
  box-shadow: 0 6px 20px rgba(26, 29, 33, 0.1);
  pointer-events: auto;
}

.tab {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: calc(var(--dock-h) - 2px);
  border-radius: calc(var(--dock-h) / 2);
  color: var(--ink-muted);
  font-size: 14.5px;
  font-weight: 600;
  text-decoration: none;
}

/* Active is ink and a wash pill, not a colour. */
.tab.active {
  background: var(--wash);
  color: var(--ink);
  font-weight: 700;
}

/* The primary action, on the dock's line and clear of both tabs. */
.fab {
  flex: 0 0 var(--dock-h);
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--dock-h);
  height: var(--dock-h);
  border-radius: 50%;
  background: var(--action);
  color: var(--action-ink);
  text-decoration: none;
  box-shadow: 0 6px 18px rgba(26, 29, 33, 0.24);
  pointer-events: auto;
  transition: transform 120ms ease, opacity 120ms ease;
}

.fab:active {
  opacity: 0.9;
  transform: scale(0.97);
}

.fab-glyph {
  display: block;
  width: 25px;
  height: 25px;
}

/* --- the app screen's header ---------------------------------------------- */
/* One grammar for every app screen: the product name on the left, a chip or two
   in the middle, one icon button on the right. 今日, 履歴, 設定 and プラン all
   open with this bar, so moving between them does not move the furniture.

   It is sticky, and that is the point of it in v4: on a phone the old header
   scrolled away and left the screen with no top edge at all, which is what made
   the app look like a document rather than an app. The bar is full width (it
   cancels the column's own padding with negative margins) and it has NO bottom
   border until the page is scrolled: a rule under a bar sitting on unscrolled
   content is a line drawn for no reason. public/app.js adds .is-stuck when the
   1px sentinel above the bar leaves the viewport. */
.appbar {
  position: sticky;
  top: 0;
  z-index: 15;
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: 52px;
  margin: 0 calc(-1 * max(16px, env(safe-area-inset-right))) 14px
    calc(-1 * max(16px, env(safe-area-inset-left)));
  padding: env(safe-area-inset-top) max(16px, env(safe-area-inset-right)) 0
    max(16px, env(safe-area-inset-left));
  background: var(--canvas);
  /* Transparent rather than absent, so gaining the rule does not move the
     content below it by a pixel. */
  border-bottom: 1px solid transparent;
  transition: border-color 0.15s ease;
}

.appbar-fill {
  flex: 1 1 auto;
}

.appbar.is-stuck {
  border-bottom-color: var(--line);
}

/* The thing the observer watches. 1px tall, no paint. */
.appbar-sentinel {
  display: block;
  height: 1px;
}

/* The name does not stretch: a flexible spacer (.appbar-fill) does that, so a chip
   that NAMES the screen (履歴's date, 設定) sits next to the name on the left, and a
   chip you can act on (今日's scan count) sits on the right beside the icon button.
   Where a chip sits is what says whether it is a label or a control. */
.appbar-name {
  flex: 0 1 auto;
  min-width: 0;
  margin: 0;
  font-size: 17px;
  font-weight: 700;
  letter-spacing: 0.01em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* A chip in the bar. Wash pill, no border: on the white bar the tone is the
   edge, the same way the cards work now. */
.appbar-chip {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  min-height: 30px;
  margin: 0;
  padding: 4px 11px;
  background: var(--wash);
  border: 0;
  border-radius: 999px;
  color: var(--ink-muted-2);
  font-size: 12.5px;
  font-weight: 600;
  white-space: nowrap;
  text-decoration: none;
  font-variant-numeric: tabular-nums;
}

.appbar-chip .num {
  font-weight: 700;
  color: var(--ink);
}

/* Today's remaining scans, and it is a link: the chip is the only place in the app
   that says the free allowance out loud before it runs out, so the thing it says
   leads where it points. It is a real 44px target, not a 30px pill somebody has to
   aim at. */
a.appbar-chip {
  min-height: 44px;
}

a.appbar-chip:active {
  background: #eceef2;
}

/* 履歴's date. Secondary to the product name, which is why it is a chip and not
   the heading it used to be. */
.appbar-date {
  color: var(--ink);
  font-weight: 700;
}

/* An icon button in the header. 44px of touch target around a 21px glyph. */
.iconbtn {
  flex: 0 0 var(--tap);
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--tap);
  height: var(--tap);
  margin-right: -10px;
  border: 0;
  border-radius: 50%;
  background: none;
  color: var(--ink);
  text-decoration: none;
  cursor: pointer;
}

.iconbtn:active {
  background: var(--wash);
}

/* The escape from the plan screen: the same 44px button, filled with the wash so
   it reads as a control rather than as a glyph floating in the bar. */
.iconbtn-wash {
  margin-right: 0;
  background: var(--wash);
}

.iconbtn-wash:active {
  background: #eceef2;
}

.iconbtn-glyph {
  display: block;
  width: 21px;
  height: 21px;
}

/* --- the week strip ------------------------------------------------------- */
/* Seven days, each a thin ring of that day's calories against the target. A day
   over the target closes the ring in the P hue, the same borrowing the big ring
   does. The date is printed inside every circle, so the ring is a glance and
   never the only thing saying anything. */
.weekstrip {
  display: flex;
  align-items: center;
  gap: 2px;
  margin: 0 0 18px;
}

.weekdays {
  flex: 1 1 auto;
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 2px;
  list-style: none;
  margin: 0;
  padding: 0;
}

.wkday {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 5px;
  padding: 4px 0 10px;
  border-radius: var(--radius-lg);
  color: var(--ink-muted);
  text-decoration: none;
  transition: transform 120ms ease;
}

.wkday:active {
  transform: scale(0.97);
}

/* Three different things, said three different ways.

   WHICH DAY YOU ARE LOOKING AT (今日 on the day screen, the URL's date in 履歴) is
   an ink-filled circle with a white number in it. It used to be a wash pill behind
   the whole column, which on a wash page is nothing at all.

   The day's PROGRESS is the arc, on every day EXCEPT the selected one. The selected
   day's arc is not drawn: a 4px white arc on a 36px ink disc does not read as
   two-thirds of a day, it reads as a chipped edge, and the selected day's own
   progress is the 104px ring immediately below it saying 66%. The disc has one job
   and it does it plainly.

   WHICH DAY IS TODAY is a 3px ink dot under the number, so today stays findable
   while you are reading Tuesday. */
.wkday.is-selected .ring-track {
  fill: var(--ink);
  stroke: var(--ink);
}

.wkday.is-selected .ring-arc {
  display: none;
}

.wkday.is-selected .wkday-n {
  color: var(--canvas);
}

.wkday.is-selected .wkday-lab {
  color: var(--ink);
  font-weight: 700;
}

/* Nothing logged on that day. A dashed track, so an empty day reads as empty
   rather than as a day at zero: same distinction the big ring makes. */
.wkday.is-empty .ring-track {
  stroke-dasharray: 2 4;
}

/* The day you are looking at is a solid disc even when nothing is logged on it: the
   dash is how an UNSELECTED empty day is drawn, and dashing the selected disc's own
   4px stroke turned it into a cog. */
.wkday.is-selected.is-empty .ring-track {
  stroke-dasharray: none;
}

.wkday.is-today .wkday-lab {
  color: var(--ink);
  font-weight: 700;
}

.wkday.is-today::after {
  content: "";
  position: absolute;
  bottom: 3px;
  left: 50%;
  width: 3px;
  height: 3px;
  margin-left: -1.5px;
  border-radius: 50%;
  background: var(--ink);
}

.wkday.is-future {
  opacity: 0.42;
}

.wkday-lab {
  font-size: 11px;
  font-weight: 600;
  line-height: 1;
}

.wkring {
  position: relative;
  width: 36px;
  height: 36px;
}

.ring-sm {
  --ring-c: var(--ring-c-sm);
}

/* 4, not 9. At 9 the seven day circles were seven heavy donuts across the top of
   the screen and the number inside each one had no room to be a number. A thin
   ring is a calendar; a thick one is a dashboard. */
.ring-sm .ring-track,
.ring-sm .ring-arc {
  stroke-width: 4;
}

/* The track has to be visible on the wash page, where --line is not: it is the
   circle that says a day exists. Overrides the --line the small rings were given
   for the white canvas. */
.ring-sm .ring-track {
  stroke: #e2e6eb;
}

.wkday-n {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 12.5px;
  font-weight: 700;
  color: var(--ink);
}

.wkday.is-future .wkday-n {
  color: var(--ink-muted);
}

/* One week back, one week forward. The strip alone could only ever show the week
   it is on, and the record goes further back than that. */
.weekstep {
  flex: 0 0 26px;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: var(--tap);
  color: var(--ink-muted);
  text-decoration: none;
}

.weekstep.is-off {
  opacity: 0.3;
}

.weekstep-glyph {
  display: block;
  width: 17px;
  height: 17px;
}

/* --- the day hero -------------------------------------------------------- */
/* What has been eaten, big, with the target beside it and the same figure drawn
   as a ring. The number is the answer; the ring is the feeling. */
/* The scale here is the whole of B3 in design v4: on the phone this card was the
   headline of the app and it read as one row of a list. The figure, the ring and
   the remainder each went up one step, and the card breathes 22px instead of 18. */
.dayhero {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 22px;
  margin-bottom: 18px;
}

.dayhero-figures {
  flex: 1 1 auto;
  min-width: 0;
}

/* One line, never two: "1,742 / 2,100 kcal" reading as "1,742 / 2,100" on one
   line and "kcal" alone on the next is the same figure told as two. The sizes
   below are what makes the widest case (a five-character eaten figure beside a
   five-character target) fit next to the ring at 390pt. */
.dayhero-figure {
  display: flex;
  flex-wrap: nowrap;
  align-items: baseline;
  gap: 5px;
  margin: 0;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}

.dayhero-num {
  font-size: 44px;
  font-weight: 800;
  line-height: 1.05;
  letter-spacing: -0.035em;
}

.dayhero-of {
  font-size: 14.5px;
  font-weight: 700;
  color: var(--ink-muted-2);
}

.dayhero-cap {
  margin: 2px 0 0;
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.02em;
  color: var(--ink-muted-2);
}

/* The sentence somebody actually reads off this card. It is the answer to "can I
   eat this", so it is the same weight as a heading and one step up in size. */
.dayhero-rest {
  margin: 8px 0 0;
  font-size: 15.5px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

.hero-ring {
  position: relative;
  flex: 0 0 104px;
  width: 104px;
  height: 104px;
  --ring-c: var(--ring-c-sm);
}

.hero-ring .ring-track,
.hero-ring .ring-arc {
  stroke-width: 10;
}

/* Ink, not muted. It is a figure, and a figure inside its own ring was the one
   number on the screen printed in the colour of a caption. */
.hero-ring-pct {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 15px;
  font-weight: 700;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}

/* --- macro cards with rings ---------------------------------------------- */
/* The same three numbers as a fraction, one card each. Eaten over target in
   figures, and the fraction again as a ring in the macro's own hue. */
.mring {
  position: relative;
  width: 52px;
  height: 52px;
  margin: 11px auto 0;
  --ring-c: var(--ring-c-sm);
}

.mring .ring-track,
.mring .ring-arc {
  stroke-width: 10;
}

.arc-p {
  stroke: var(--macro-p);
}

.arc-f {
  stroke: var(--macro-f);
}

.arc-c {
  stroke: var(--macro-c);
}

.macro-card-frac {
  display: flex;
  align-items: baseline;
  gap: 1px;
  margin: 7px 0 0;
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}

.macro-card-eaten {
  font-size: 19px;
  font-weight: 800;
  line-height: 1.15;
  letter-spacing: -0.02em;
}

.macro-card-target {
  font-size: 12px;
  font-weight: 600;
  color: var(--ink-muted);
}

/* --- the day's meal list ------------------------------------------------- */
/* Photo led, one tap to the meal's own screen. The photograph is flush to the
   card's left edge and full height: a meal is a picture first, and inset
   thumbnails made the list read as a settings menu. */
.mealrow {
  display: flex;
  align-items: stretch;
  min-height: 104px;
  margin-bottom: 10px;
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--canvas);
  color: var(--ink);
  text-decoration: none;
  overflow: hidden;
  transition: transform 120ms ease;
}

.mealrow:active {
  background: var(--wash);
  transform: scale(0.97);
}

.mealrow-thumb {
  flex: 0 0 104px;
  width: 104px;
  min-height: 104px;
  object-fit: cover;
  background: var(--wash);
}

.mealrow-body {
  flex: 1 1 auto;
  min-width: 0;
  padding: 12px 14px 13px;
}

/* The name and the calories are two COLUMNS, both starting at the top of the
   row. They used to share a baseline, which on a two-line dish name put the
   calorie figure on the second line, halfway down the card, beside the middle of
   a word. The figure belongs at the top right of the row and it stays there
   whether the name takes one line or two. */
.mealrow-top {
  display: flex;
  align-items: flex-start;
  gap: 10px;
}

/* Two lines, then an ellipsis. A four-line dish name pushed the macros out of the
   row; the whole name is on the meal's own screen, one tap away. */
.mealrow-name {
  flex: 1 1 auto;
  min-width: 0;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  font-size: 16px;
  font-weight: 700;
  line-height: 1.4;
  overflow-wrap: anywhere;
}

.mealrow-kcal {
  flex: 0 0 auto;
  align-self: flex-start;
  text-align: right;
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
  white-space: nowrap;
}

.mealrow-kcal .num {
  font-size: 21px;
  font-weight: 800;
  letter-spacing: -0.02em;
}

.mealrow-kcal .unit {
  margin-left: 2px;
  font-size: 11.5px;
  font-weight: 700;
  color: var(--ink-muted);
}

/* The meta band is deliberately one step below the macros under it: time and a
   health chip are context, and at 12.5px they were competing with the figures. */
.mealrow-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 8px;
  margin-top: 6px;
  font-size: 11.5px;
  color: var(--ink-muted);
}

/* The three macros on a list row. There is no room here for the spelled-out
   labels the cards carry, so the key letter does that work and the screen
   reader is handed the whole word: P, F and C are taught on this very screen by
   the PFCバランス heading above, and every one of them prints its own figure. */
/* All three on ONE line: this row is a glance, and a macro that wrapped onto a
   line of its own read as a fourth thing. The sizes below are what fits three of
   them beside a 96px photo at 390pt with a three-figure carb count. */
.mkeys {
  display: flex;
  flex-wrap: nowrap;
  gap: 9px;
  margin: 8px 0 0;
  font-size: 12px;
  font-variant-numeric: tabular-nums;
}

.mkey {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  white-space: nowrap;
}

.mkeys .dot {
  flex: 0 0 7px;
  width: 7px;
  height: 7px;
}

.mkey-lab {
  font-weight: 700;
  color: var(--ink-muted);
}

.mkey-val {
  font-weight: 700;
}

/* Cancels most of the row's gap, so the unit sits against its figure rather than
   floating between two of them. */
.mkey-unit {
  margin-left: -3px;
  font-size: 11px;
  font-weight: 700;
  color: var(--ink-muted);
}

/* Nothing logged yet. A line and the way in, not an illustration. */
.emptyday {
  padding: 22px 18px 24px;
  text-align: center;
}

.emptyday p {
  margin: 0;
}

.emptyday-hint {
  margin-top: 8px;
  font-size: 13.5px;
  color: var(--ink-muted);
}

/* --- the photo screen ---------------------------------------------------- */
/* One meal, one screen. The photograph fills the top of the viewport edge to
   edge and the white sheet climbs over its bottom edge, which is the whole
   composition: the picture is the subject and the figures are its caption.
   Used twice, from one partial, so a fresh estimate and a saved meal are the
   same screen: server-rendered at /meals/:id, and cloned by public/app.js into
   a fixed layer for a scan that has not been saved yet. */
.photoscreen {
  max-width: 480px;
  margin: 0 auto;
}

/* The client-rendered copy. A layer over 今日 rather than a page: the photo is
   already in the browser as a blob, and navigating would throw it away. */
.photolayer {
  position: fixed;
  inset: 0;
  z-index: 40;
  background: var(--canvas);
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior-y: contain;
}

/* A layer covers the day completely, so the day must not scroll behind it: a
   second scrollable surface under a fixed one is how a phone ends up scrolled
   somewhere nobody asked for. public/app.js adds the class while a layer is up. */
html.layer-open,
html.layer-open body {
  overflow: hidden;
}

/* 220ms on the system's own easing, and the reduced-motion rule turns it off. */
.photolayer:not([hidden]) {
  animation: sheet-up 220ms cubic-bezier(0.2, 0.8, 0.2, 1);
}

@keyframes sheet-up {
  from {
    opacity: 0;
    transform: translateY(18px);
  }
}

/* Just under half the viewport, and now shaped as well as capped: 4/5 is the
   frame a phone camera actually hands us, so the crop matches what the person
   saw through the viewfinder, and 46vh is the ceiling on a short screen. The two
   vh the sheet borrows are what leaves the first ingredient row half visible
   above the action bar, which is the only honest way a screen says "there is more
   of this below". */
/* The 4/5 shape written as arithmetic rather than as aspect-ratio, and that is not
   a style preference: `aspect-ratio: 4/5` with `max-height: 46vh` on a stretch-fit
   block makes the browser keep the RATIO and give up the WIDTH, so the photograph
   came out 315px wide on a 390pt screen with a white margin down one side. Height
   is the ratio applied to the column's own width (5/4 = 125%), capped at 46vh, and
   the width is left alone. 480px is the column's cap, so 600px is 125% of it. */
.ph-photo {
  position: relative;
  width: 100%;
  height: min(46vh, 125vw, 600px);
  min-height: 290px;
  background: var(--wash);
}

/* 42% rather than dead centre. A plate photographed from above or at 45 degrees
   sits below the middle of the frame (there is always table above it), so a
   centred crop cut the near edge of the food off and kept the tablecloth. */
.ph-img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center 42%;
}

/* A meal saved without a photo. The sheet is the whole screen then, and the
   band above it says why rather than showing a broken frame. */
.ph-photo.is-blank {
  height: 132px;
  min-height: 0;
}

.ph-blank-note {
  position: absolute;
  inset: auto 0 34px;
  margin: 0;
  text-align: center;
  font-size: 13.5px;
  color: var(--ink-muted);
}

/* A floating control over photography. Shadow allowed: see the note on .tabdock.
   The glyph is ink on near-white so it survives a bright photo underneath. */
.fbtn {
  position: absolute;
  top: calc(12px + env(safe-area-inset-top));
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--tap);
  height: var(--tap);
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.92);
  color: var(--ink);
  text-decoration: none;
  box-shadow: 0 2px 10px rgba(26, 29, 33, 0.2);
  cursor: pointer;
}

.fbtn:active {
  background: #fff;
  opacity: 0.9;
}

.fbtn-l {
  left: max(16px, env(safe-area-inset-left));
}

.fbtn-r {
  right: max(16px, env(safe-area-inset-right));
}

.fbtn-glyph {
  display: block;
  width: 21px;
  height: 21px;
}

/* The sheet. It overlaps the photo by 20px, which is what makes the photograph
   look like it is behind the screen rather than cropped to fit above it. */
.ph-sheet {
  position: relative;
  margin-top: -20px;
  /* The action bar's height, plus 20px of air, plus the home indicator. This is
     the fix for the defect on the owner's phone: the three macro tiles were cut
     in half by the bar, because the reserve was a guess (--tap + 46) that no
     longer matched what the bar had become. */
  padding: 18px 18px calc(var(--phbar-h) + 20px + env(safe-area-inset-bottom));
  background: var(--canvas);
  border-radius: var(--radius-sheet) var(--radius-sheet) 0 0;
}

/* The time chip on the photo screen is the shared .chip .chip-time now (see the
   chip block below); this rule only takes the paragraph's margin off it. */
.ph-time {
  margin: 0;
}

.ph-namerow {
  display: flex;
  align-items: flex-start;
  gap: 6px;
  margin: 10px 0 0;
}

.ph-name {
  flex: 1 1 auto;
  min-width: 0;
  margin: 0;
  font-size: 22px;
  font-weight: 700;
  line-height: 1.4;
  letter-spacing: -0.01em;
  overflow-wrap: anywhere;
}

/* The same line as an input, for an estimate that has not been saved yet. It
   looks like the heading it will become: no box until it has focus. */
.ph-name-input {
  flex: 1 1 auto;
  min-width: 0;
  width: 100%;
  min-height: var(--tap);
  margin: -6px -10px 0;
  padding: 4px 10px;
  background: var(--canvas);
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  color: var(--ink);
  font-family: inherit;
  font-size: 22px;
  font-weight: 700;
  letter-spacing: -0.01em;
  appearance: none;
  -webkit-appearance: none;
}

.ph-name-input:focus {
  background: var(--wash);
  border-color: var(--ink);
  outline: none;
}

.ph-chiprow {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin: 10px 0 0;
}

.ph-kcal {
  display: flex;
  align-items: baseline;
  gap: 6px;
  margin: 12px 0 0;
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}

.ph-kcal-num {
  font-size: 40px;
  font-weight: 800;
  line-height: 1.05;
  letter-spacing: -0.035em;
}

.ph-kcal-unit {
  font-size: 15px;
  font-weight: 700;
  color: var(--ink-muted);
}

/* The candidates' spread, directly under the median. Quiet on purpose: the median
   is the answer and this is how much three tries of it disagreed, so it is a
   caption to the big figure and never a competing one. It is one line at 390pt. */
.ph-kcal-range {
  margin: 3px 0 0;
  font-size: 13px;
  line-height: 1.5;
  color: var(--ink-muted);
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}

/* The three macros as tiles on the wash rather than as bordered cards: the
   sheet is already a surface, and a box inside a box needs a reason. */
.ph-macros {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px;
  list-style: none;
  margin: 14px 0 0;
  padding: 0;
}

.ph-macro {
  padding: 11px 11px 12px;
  background: var(--wash);
  border-radius: var(--radius);
}

/* Same label, same treatment, on the photo screen's three tiles. */
.ph-macro-lab {
  display: flex;
  align-items: center;
  gap: 5px;
  margin: 0;
  font-size: 11.5px;
  font-weight: 700;
  line-height: 1.3;
  color: var(--ink-muted-2);
  letter-spacing: 0.02em;
}

.ph-macro-val {
  display: flex;
  align-items: baseline;
  gap: 1px;
  margin: 6px 0 0;
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}

.ph-macro-num {
  font-size: 18px;
  font-weight: 800;
  line-height: 1.15;
  letter-spacing: -0.02em;
}

.ph-macro-unit {
  font-size: 11.5px;
  font-weight: 700;
  color: var(--ink-muted);
}

.ph-section {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  margin: 20px 0 0;
}

.ph-section h2 {
  margin: 0;
  font-size: 16px;
}

/* The fixed action bar. Two buttons: the quiet one is the way back out, the ink
   one is what the screen is for. */
.ph-bar {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 41;
  display: flex;
  gap: 10px;
  max-width: 480px;
  margin: 0 auto;
  padding: 10px max(16px, env(safe-area-inset-left)) calc(10px + env(safe-area-inset-bottom))
    max(16px, env(safe-area-inset-right));
  /* Frosted, not opaque. Content scrolls UNDER this bar by design (that is how
     the screen says there is more below), and a solid white slab made the cut
     look like a layout accident. Through the blur it reads as a pane over the
     sheet, which is what it is. */
  background: rgba(255, 255, 255, 0.86);
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
  border-top: 1px solid var(--line);
}

/* No blur available: opaque, because 86% white over text is worse than white. */
@supports not ((backdrop-filter: blur(12px)) or (-webkit-backdrop-filter: blur(12px))) {
  .ph-bar {
    background: var(--canvas);
  }
}

.ph-bar .btn {
  flex: 1 1 auto;
  min-height: 52px;
}

.ph-bar .btn-quiet {
  flex: 0 0 auto;
  padding: 12px 16px;
  font-size: 15px;
}

/* --- the scan moment ----------------------------------------------------- */
/* The photograph, whole, the instant it is chosen. The fields that make the
   estimate better sit in a sheet over the bottom of it, and analysing keeps the
   picture exactly where it is. */
.scanlayer {
  position: fixed;
  inset: 0;
  z-index: 40;
  background: var(--ink);
  overflow: hidden;
}

.scanlayer-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.scansheet {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  max-width: 480px;
  margin: 0 auto;
  max-height: 82vh;
  overflow-y: auto;
  padding: 18px 18px calc(18px + env(safe-area-inset-bottom));
  background: var(--canvas);
  border-radius: var(--radius-sheet) var(--radius-sheet) 0 0;
}

.scansheet:not([hidden]) {
  animation: sheet-up 220ms cubic-bezier(0.2, 0.8, 0.2, 1);
}

/* Everything in this sheet is one step tighter than the same thing elsewhere in
   the app, and deliberately so: every pixel it does not use is a pixel of the
   photograph, which is the thing the screen is about. */
.scansheet h2 {
  margin-bottom: 3px;
  font-size: 17px;
}

.scansheet > .muted {
  margin-bottom: 2px;
  font-size: 13px;
}

.scansheet .field {
  margin-top: 12px;
}

.scansheet .field input {
  min-height: 48px;
}

.scansheet textarea {
  min-height: 46px;
}

.scansheet .btn-block {
  margin-top: 18px;
}

/* The one button on the scan sheet, and it is the first thing under the
   photograph rather than the last thing under two fields nobody fills in. */
.scan-go {
  margin-top: 4px;
}

/* The two optional fields, folded away. Native <details>: no script, and the
   browser's own disclosure behaviour is better than any of ours. */
.scan-more {
  margin-top: 6px;
}

.scan-more summary {
  display: flex;
  align-items: center;
  gap: 4px;
  min-height: var(--tap);
  color: var(--ink-muted);
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
  list-style: none;
}

/* Safari draws its own triangle unless both of these are said. */
.scan-more summary::-webkit-details-marker {
  display: none;
}

.scan-more[open] summary {
  color: var(--ink);
}

.disc-chev {
  display: block;
  width: 17px;
  height: 17px;
  transition: transform 150ms ease;
}

.scan-more[open] .disc-chev {
  transform: rotate(180deg);
}

.scan-more > .muted {
  margin: 0;
}

/* --- the analysing checklist ---------------------------------------------- */
/* Three lines, ticking over while the model works. The delay is on a custom
   property so the row, its circle and its tick all move together on one number. */
.ckl {
  list-style: none;
  margin: 16px 0 12px;
  padding: 0;
}

.ck {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 9px;
  font-size: 14.5px;
  font-weight: 600;
  color: var(--ink-muted);
  animation: ck-ink 400ms ease-out forwards;
  animation-delay: var(--ck-delay, 0ms);
}

.ck:last-child {
  margin-bottom: 0;
}

.ck-1 {
  --ck-delay: 300ms;
}

.ck-2 {
  --ck-delay: 2600ms;
}

.ck-3 {
  --ck-delay: 5200ms;
}

.ck-dot {
  position: relative;
  flex: 0 0 22px;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  box-shadow: inset 0 0 0 2px var(--line);
  animation: ck-fill 400ms ease-out forwards;
  animation-delay: var(--ck-delay, 0ms);
}

.ck-tick {
  position: absolute;
  inset: 4px;
  display: block;
  width: 14px;
  height: 14px;
  color: var(--action-ink);
  opacity: 0;
  animation: ck-show 300ms ease-out forwards;
  animation-delay: var(--ck-delay, 0ms);
}

@keyframes ck-fill {
  to {
    background: var(--ink);
    box-shadow: inset 0 0 0 2px var(--ink);
  }
}

@keyframes ck-show {
  to {
    opacity: 1;
  }
}

@keyframes ck-ink {
  to {
    color: var(--ink);
  }
}

/* Analysing. The same sheet, shorter, with an indeterminate bar: there is no
   progress to report, and a fake percentage would be a lie about a model call. */
.scanbusy {
  text-align: left;
}

.prog {
  height: 4px;
  margin: 14px 0 2px;
  border-radius: 999px;
  background: var(--line);
  overflow: hidden;
}

.prog-fill {
  display: block;
  width: 40%;
  height: 100%;
  border-radius: 999px;
  background: var(--ink);
  animation: prog-slide 1.4s ease-in-out infinite;
}

@keyframes prog-slide {
  from {
    transform: translateX(-100%);
  }
  to {
    transform: translateX(250%);
  }
}

/* No animation to watch, so the bar states plainly that something is happening
   instead of sitting frozen at 40%. */
@media (prefers-reduced-motion: reduce) {
  .prog-fill {
    width: 100%;
  }

  /* And the checklist does not tick over: the three lines are simply the three
     things being done, printed. The global reduced-motion rule at the top of this
     file zeroes durations but not delays, so without this the ticks would still
     appear on a timer, one at a time, with no transition -- which is the worst of
     both. Nothing here claims a step is finished. */
  .ck,
  .ck-dot,
  .ck-tick {
    animation: none;
  }
}

/* --- the upgrade sheet --------------------------------------------------- */
/* A 402 arrives during the scan moment, so the answer to it is a sheet over
   that moment rather than a card further down a page nobody is looking at. */
.sheet {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 50;
  max-width: 480px;
  margin: 0 auto;
  padding: 20px 18px calc(18px + env(safe-area-inset-bottom));
  background: var(--canvas);
  border: 1px solid var(--line);
  border-bottom: 0;
  border-radius: var(--radius-sheet) var(--radius-sheet) 0 0;
  box-shadow: 0 -8px 28px rgba(26, 29, 33, 0.14);
}

.sheet:not([hidden]) {
  animation: sheet-up 220ms cubic-bezier(0.2, 0.8, 0.2, 1);
}

/* The message is the headline here, so it carries the weight a heading would. The
   right-hand gutter is the ✕'s room: without it the 44px escape button sat on top
   of the sentence and covered two characters of it. */
.sheet-lead {
  margin-bottom: 8px;
  padding-right: 52px;
  font-weight: 700;
}

/* --- accessibility -------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

@media (prefers-contrast: more) {
  :root {
    --line: #ced4db;
    --ink-muted: #4b5563;
  }
}

/* --- the day screen ------------------------------------------------------- */

/* One rule for the rhythm of every section heading on an app screen: 28px of air
   above it, 10px below. A heading belongs to what follows it, and the pair of
   near-equal gaps this used to have made every heading look like it belonged to
   the card above. */
.section-title {
  margin: 28px 0 10px;
  font-size: 15px;
  letter-spacing: 0.01em;
}

/* Progress bars. The track is the border tone rather than the wash, so a slim
   bar still has an edge on a white card. The bar always repeats a figure that
   is printed beside it, so it is aria-hidden. */
.bar {
  height: 8px;
  margin: 12px 0 10px;
  border-radius: 999px;
  background: var(--line);
  overflow: hidden;
}

.bar-thin {
  height: 5px;
  margin: 6px 0 0;
}

.bar-fill {
  display: block;
  height: 100%;
  border-radius: 999px;
  background: var(--ink);
}

.fill-p {
  background: var(--macro-p);
}

.fill-f {
  background: var(--macro-f);
}

.fill-c {
  background: var(--macro-c);
}

/* --- date navigation (履歴) ----------------------------------------------- */

.daynav {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 14px;
}

.daynav-day {
  flex: 1 1 auto;
  margin: 0;
  text-align: center;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

.daynav-btn {
  flex: 0 0 auto;
  min-height: var(--tap);
  padding: 10px 12px;
  font-size: 14px;
  font-weight: 600;
}

.daynav-btn.is-off {
  color: var(--ink-muted);
  opacity: 0.55;
}

/* --- meal cards ----------------------------------------------------------- */

.meal-head {
  display: flex;
  align-items: flex-start;
  gap: 12px;
}

.meal-thumb {
  flex: 0 0 60px;
  width: 60px;
  height: 60px;
  border-radius: var(--radius-sm);
  object-fit: cover;
  background: var(--wash);
}

.meal-title {
  flex: 1 1 auto;
  min-width: 0;
}

.meal-name {
  margin: 0;
  font-size: 16.5px;
  font-weight: 700;
  overflow-wrap: anywhere;
}

.meal-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 8px;
  margin: 5px 0 0;
  font-size: 13px;
  color: var(--ink-muted);
}

.meal-edited {
  font-size: 12px;
}

.meal-kcal {
  flex: 0 0 auto;
  margin: 0;
  text-align: right;
  font-variant-numeric: tabular-nums;
}

.meal-kcal .num {
  font-size: 25px;
  font-weight: 800;
  letter-spacing: -0.02em;
}

.meal-kcal .unit {
  margin-left: 2px;
  font-size: 12px;
  font-weight: 700;
  color: var(--ink-muted);
}

/* The health score chip. A five step green to amber ramp rather than green to
   red: the score is a nudge about balance, not a warning about danger.

   Re-derived for the white canvas. Every step is a dark ink of its own hue on a
   light tint of the same hue, with a 1px border a few steps darker than the
   tint so the chip has an edge on white (a tint alone measures 1.1:1 against
   the canvas and would read as nothing). Each ink was checked as TEXT, which is
   the right check for a chip: 4.6 to 4.9:1 on its own tint and 5.2 to 5.4:1 on
   the canvas, both above the 4.5:1 AA floor. The chip always prints the number,
   so nobody has to read the colour to get the meaning.

   This is an ordinal ramp, not a categorical palette: the correct measure is
   per step text contrast plus the printed figure, which is what was run. The
   categorical CVD check does not apply to a ramp whose neighbours are meant to
   be near each other. */
/* The label carries its own trailing space, because .chip is an inline-flex box
   and a flex container drops the whitespace BETWEEN its items: the literal space
   in "健康度 <span>8</span>/10" is discarded, while the same string set as one
   text node by public/app.js keeps it. Two paths, one chip, and they have to
   read identically, so the gap is a margin rather than a space either of them
   could lose. */
.chip-lab {
  margin-right: 4px;
}

/* ONE chip, everywhere. Design v4 unified the two chips this app had drifted
   into: a 24px pill on a 999 radius, printing its own figure, in one of exactly
   two flavours.

     .chip-time  a wash pill. The clock, and anything else that is context.
     .chip-hN    a tinted pill on the health ramp below.

   The same markup now appears on a meal row in the day's list and on the meal's
   own screen, so the same fact does not change shape when you tap it. */
.chip {
  display: inline-flex;
  align-items: center;
  min-height: 24px;
  padding: 2px 10px;
  border: 1px solid transparent;
  border-radius: 999px;
  font-size: 12px;
  font-weight: 700;
  white-space: nowrap;
}

.chip-time {
  background: var(--wash);
  color: var(--ink-muted-2);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}

.chip-h1 {
  background: #faf1e0;
  border-color: #f1d6a7;
  color: #936310;
}

.chip-h2 {
  background: #f9f6e2;
  border-color: #ede6ab;
  color: #766d13;
}

.chip-h3 {
  background: #f1f8e3;
  border-color: #d6eaae;
  color: #577718;
}

.chip-h4 {
  background: #e7f6e4;
  border-color: #bbe6b3;
  color: #2f7920;
}

.chip-h5 {
  background: #e5f6ed;
  border-color: #b4e4cc;
  color: #237b4f;
}

.meal-detail {
  margin-top: 14px;
  border-top: 1px solid var(--line);
}

.meal-detail summary {
  min-height: var(--tap);
  display: flex;
  align-items: center;
  font-size: 14px;
  font-weight: 600;
  color: var(--ink-muted);
  cursor: pointer;
}

/* --- ingredient rows ------------------------------------------------------ */

.ing-title {
  margin: 18px 0 4px;
}

.ing-help,
.ing-status {
  font-size: 13px;
}

.ing-status {
  margin: 10px 0 0;
}

.ing-list {
  list-style: none;
  margin: 4px 0 0;
  padding: 0;
}

/* One ingredient. The whole left side is the edit control: the row IS the
   button, so correcting a number is one tap on the thing you want to correct
   rather than a hunt for a small word next to it. Grams sit hard right, where
   they line up down the list. */
.ing {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px 10px;
  border-top: 1px solid var(--line);
}

.ing:first-child {
  border-top: 0;
}

.ing-main {
  flex: 1 1 auto;
  min-width: 0;
  display: block;
  min-height: var(--tap);
  padding: 10px 0;
  border: 0;
  background: none;
  color: var(--ink);
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.ing-name {
  display: block;
  font-size: 15.5px;
  line-height: 1.45;
  overflow-wrap: anywhere;
}

/* Grams are a figure somebody corrects, so they are printed like one: ink, 700,
   15px, and they line up down the list. They used to be a muted 14.5/600, which
   made the editable number the quietest thing in its own row. */
.ing-grams {
  flex: 0 0 auto;
  min-width: 54px;
  text-align: right;
  font-size: 15px;
  font-weight: 700;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.ing-unit {
  margin-left: 1px;
  font-size: 12px;
  font-weight: 700;
  color: var(--ink-muted);
}

/* The per-ingredient calories, as their own right-aligned column rather than a
   line of small print under the name: five ingredients each carrying a kcal
   figure only line up if they are a column. */
.ing-kcal-col {
  flex: 0 0 auto;
  min-width: 58px;
  text-align: right;
  font-size: 13.5px;
  font-weight: 600;
  color: var(--ink-muted-2);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* 削除 was an underlined word beside every row: five of them down the list, each
   one a 44px target competing with the row it belonged to, and the underline read
   as the loudest thing in the list. It is now a 32px ghost icon button. The
   accessible name still says 削除. */
.ing-del {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  margin-right: -6px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: none;
  color: var(--ink-muted);
  cursor: pointer;
  transition: transform 120ms ease;
}

.ing-del:active {
  background: var(--wash);
  transform: scale(0.97);
}

.ing-del-glyph {
  display: block;
  width: 17px;
  height: 17px;
}

.ing-add {
  margin: 4px 0 0;
  padding-left: 0;
}

.ing-hidden {
  display: none;
}

.link-btn {
  min-height: var(--tap);
  padding: 0 8px;
  border: 0;
  background: none;
  color: var(--ink);
  font: inherit;
  font-size: 14px;
  font-weight: 600;
  text-decoration: underline;
  text-decoration-color: #c9cfd6;
  text-underline-offset: 3px;
  cursor: pointer;
}

.ing.editing .ing-grams,
.ing.editing .ing-kcal-col,
.ing.editing .ing-del {
  display: none;
}

.ing.editing .ing-main {
  min-height: 0;
  padding-bottom: 0;
  cursor: default;
}

.ing-edit {
  flex: 1 1 100%;
  margin-top: 6px;
  padding: 12px;
  background: var(--wash);
  border-radius: var(--radius);
}

.mini {
  display: block;
}

.mini-wide {
  margin-bottom: 8px;
}

.mini-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(88px, 1fr));
  gap: 8px;
}

.mini-label {
  display: block;
  margin-bottom: 2px;
  font-size: 12px;
  color: var(--ink-muted);
}

/* Minimal until it matters: the number is the thing, so the box around it does
   not appear until the field has focus. */
.mini input {
  width: 100%;
  min-height: var(--tap);
  padding: 8px 10px;
  background: var(--canvas);
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  color: var(--ink);
  font-family: inherit;
  /* 17px, never below 16px: iOS Safari zooms in on a focused field with
     smaller text and then stays zoomed. */
  font-size: 17px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  appearance: none;
  -webkit-appearance: none;
}

.mini input:focus {
  border-color: var(--ink);
  outline: none;
}

/* --- scan flow ------------------------------------------------------------ */

.field textarea {
  width: 100%;
  min-height: 64px;
  padding: 12px 14px;
  background: var(--canvas);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  color: var(--ink);
  font-family: inherit;
  font-size: 17px;
  line-height: 1.6;
  resize: vertical;
  appearance: none;
  -webkit-appearance: none;
}

.field textarea:focus {
  border-color: var(--ink);
  outline: none;
}

.row-actions {
  display: flex;
  gap: 10px;
  margin-top: 16px;
}

.row-actions .btn {
  flex: 1 1 auto;
}

.row-actions-wrap {
  flex-wrap: wrap;
}

.row-actions-wrap .btn {
  flex: 1 1 44%;
}

.btn-sm {
  min-height: var(--tap);
  padding: 8px 12px;
  font-size: 14px;
}

.inline-form {
  margin-top: 14px;
  padding: 12px;
  background: var(--wash);
  border-radius: var(--radius);
}

.inline-form .field input {
  background: var(--canvas);
}

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}


/* --- the week's calories, as bars (履歴) ----------------------------------- */
/* Seven days of kcal against the target, with the week's average printed above
   them. The seven rings at the top of the same screen are the NAVIGATOR and they
   draw a clamped ratio; this is the MAGNITUDE, which a ring cannot give: two days
   at 130% and 260% draw the same closed ring and very different bars.
   Form chosen for the job (magnitude over an ordered 7-slot axis = bars), and it
   is one series, so there is no legend: the heading names it.

   Colour: ink for every bar, and the P hue only for a day over the target, which
   is the same borrowing the rings already do. No new palette was introduced, so
   there is no categorical set here to validate; both marks clear the 3:1 floor on
   the white card (16.9:1 and 3.96:1) and every bar prints its own figure to a
   screen reader, so nothing is carried by colour alone.

   The bar heights and the target line's position ride on the .ring-p-N classes at
   the bottom of this file (one class, one whole percent, no style attribute: the
   CSP has no 'unsafe-inline' in style-src). They are named for the rings that
   introduced them; they are just percentages. */
.weekbars {
  padding: 18px 18px 16px;
}

.weekbars-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  margin: 0 0 14px;
}

.weekbars-title {
  margin: 0;
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.02em;
  color: var(--ink-muted-2);
}

.weekbars-avg {
  margin: 0;
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}

.weekbars-avg .num {
  font-size: 22px;
  font-weight: 800;
  letter-spacing: -0.02em;
}

.weekbars-avg .unit {
  margin-left: 2px;
  font-size: 11.5px;
  font-weight: 700;
  color: var(--ink-muted);
}

.wb-plot {
  position: relative;
  display: flex;
  align-items: flex-end;
  gap: 2px;
  height: 78px;
}

/* Thin marks. The slot is a seventh of the column and the bar is the middle 26px
   of it: a bar as wide as its slot is a block, and seven blocks in a row stop being
   a chart. The padding is also what puts real surface between neighbours. */
.wb-bar {
  flex: 1 1 0;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  height: 100%;
  min-width: 0;
  padding: 0 11px;
}

/* 4px rounded data-end, anchored to the baseline. */
.wb-fill {
  width: 100%;
  height: calc(var(--ring-p, 0) * 1%);
  min-height: 3px;
  border-radius: 4px 4px 0 0;
  background: var(--ink);
}

.wb-fill.is-over {
  background: var(--macro-p);
}

/* A day with nothing on it is a 3px stub on the axis, not a zero-height nothing:
   the slot has to stay visible or the week reads as six days. */
.wb-fill.is-empty {
  height: 3px;
  background: #d7dce2;
}

/* The target, as a recessive 2px dashed reference line across the plot. */
.wb-target {
  position: absolute;
  left: 0;
  right: 0;
  bottom: calc(var(--ring-p, 0) * 1%);
  border-top: 2px dashed #cdd3da;
}

.wb-labs {
  display: flex;
  gap: 2px;
  margin: 6px 0 0;
  padding: 0;
  list-style: none;
}

.wb-lab {
  flex: 1 1 0;
  text-align: center;
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.02em;
  color: var(--ink-muted);
}

.wb-lab.is-selected {
  color: var(--ink);
}

.weekbars-note {
  margin: 10px 0 0;
  font-size: 12px;
  color: var(--ink-muted);
}

/* --- settings rows -------------------------------------------------------- */
/* A place you go, one line each, 44px, chevron on the right. 設定 was a stack of
   cards each containing one sentence and one full-width button, which is a form,
   not a settings screen. */
.rows {
  list-style: none;
  margin: 0;
  padding: 0;
}

.rowlink {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  min-height: var(--tap);
  padding: 11px 0;
  border: 0;
  border-top: 1px solid var(--line);
  background: none;
  color: var(--ink);
  font: inherit;
  font-size: 15.5px;
  font-weight: 600;
  text-align: left;
  text-decoration: none;
  cursor: pointer;
  transition: transform 120ms ease;
}

/* The first row in a group has no rule above it: the card's own top edge is the
   line. Written to reach a .rowlink at any depth, because half of them are wrapped
   in the <form> that posts them. */
.rows li:first-child .rowlink,
.rowlink:first-child {
  border-top: 0;
}

/* Who is signed in. */
.settings-name {
  margin: 0;
  font-size: 16.5px;
  font-weight: 700;
}

.settings-mail {
  margin: 2px 0 0;
  font-size: 13.5px;
}

.rowlink:active {
  transform: scale(0.99);
  opacity: 0.7;
}

.rowlink-lab {
  flex: 1 1 auto;
  min-width: 0;
}

.rowlink-val {
  flex: 0 0 auto;
  color: var(--ink-muted);
  font-size: 14px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

.rowlink-chev {
  flex: 0 0 auto;
  display: block;
  width: 17px;
  height: 17px;
  color: #b7bec7;
}

/* The group label above a card, in the grammar of a grouped settings screen. */
.sec-lab {
  margin: 26px 0 8px;
  padding: 0 4px;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.02em;
  color: var(--ink-muted-2);
}

/* --- landing: the hero photograph ----------------------------------------- */
/* The owner's first ask: a strong food photograph before anything else. It is a
   GENERATED photograph of a 和定食 (scripts/generate-hero.js, Gemini image API,
   chosen from four candidates) and never a real user's meal: those live only in
   the server's data directory.

   Full bleed out of the column, 4/5 capped at 46vh, with the headline sitting on
   a scrim at the bottom of it. Explicit width and height on the <img> so the
   page does not jump when it decodes. */
.hero-photo {
  position: relative;
  margin: -24px calc(-1 * max(16px, env(safe-area-inset-right))) 22px
    calc(-1 * max(16px, env(safe-area-inset-left)));
  background: var(--wash);
  overflow: hidden;
}

.hero-photo-img {
  display: block;
  width: 100%;
  aspect-ratio: 4 / 5;
  max-height: 46vh;
  object-fit: cover;
  object-position: center 45%;
}

/* Transparent for the top 42% and then down to 74% black. The spec asked for a
   58% floor and this photograph is why it is 74: the bottom third of the chosen
   dish is a WHITE plate and a white pickle dish, and 58% black over white leaves
   30px of white type sitting on light grey. Measured on the asset, not guessed:
   the darkest the type ever sits on is now around 4.5:1. The top 42% is untouched,
   so the rice, the miso and the simmered vegetables stay bright, which is the half
   of the frame the photograph is actually selling. */
.hero-scrim {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  padding: 0 max(16px, env(safe-area-inset-left)) 20px max(16px, env(safe-area-inset-right));
  background: linear-gradient(
    to bottom,
    rgba(0, 0, 0, 0) 42%,
    rgba(0, 0, 0, 0.34) 66%,
    rgba(0, 0, 0, 0.74) 100%
  );
}

.hero-title {
  margin: 0;
  color: #fff;
  font-size: 30px;
  font-weight: 800;
  line-height: 1.35;
  letter-spacing: -0.01em;
}

/* The product name over the photograph. 88% white rather than the muted ink the
   eyebrow uses on the canvas: on a photograph there is no canvas to be muted
   against. */
.eyebrow-on-photo {
  margin: 0 0 6px;
  color: rgba(255, 255, 255, 0.88);
  font-size: 12.5px;
  font-weight: 700;
  letter-spacing: 0.06em;
}

.hero-body {
  margin: 0 0 18px;
}

/* --- landing: 写真1枚 → NNN kcal ------------------------------------------ */
/* Two panes: the photograph on the left, the screen it becomes on the right. The
   right pane is real markup in the app's own component styles, with the numbers
   the app actually produces for this dish, so it is a picture of the product and
   not an illustration of it. */
.proof {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
  align-items: stretch;
  margin: 0 0 10px;
}

.proof-pane {
  border-radius: var(--radius-lg);
  background: var(--wash);
  overflow: hidden;
}

.proof-img {
  display: block;
  width: 100%;
  height: 100%;
  min-height: 168px;
  object-fit: cover;
  object-position: center 45%;
}

/* The card is the shorter of the two panes, so its contents sit in the middle of
   the row rather than at the top of a white box with a gap under it. */
.proof-card {
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 14px 12px;
  background: var(--canvas);
  box-shadow: var(--card-shadow);
}

.proof-name {
  margin: 8px 0 0;
  font-size: 14px;
  font-weight: 700;
  line-height: 1.4;
}

.proof-kcal {
  display: flex;
  align-items: baseline;
  gap: 4px;
  margin: 6px 0 0;
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}

.proof-kcal .num {
  font-size: 26px;
  font-weight: 800;
  letter-spacing: -0.03em;
  line-height: 1.05;
}

.proof-kcal .unit {
  font-size: 12px;
  font-weight: 700;
  color: var(--ink-muted);
}

.proof-chip {
  margin: 7px 0 0;
}

.proof-keys {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 8px;
  margin: 8px 0 0;
  padding: 0;
  list-style: none;
  font-size: 11.5px;
  font-variant-numeric: tabular-nums;
}

.proof-cap {
  margin: 0 0 22px;
  font-size: 13px;
  color: var(--ink-muted);
  text-align: center;
  font-variant-numeric: tabular-nums;
}

/* --- landing: the numbered steps, unboxed --------------------------------- */
/* 使い方 is a list, not a card. Four white boxes down a white page said nothing
   about which of them mattered; varying the container is what gives the page a
   shape you can remember. */
.steps-plain {
  margin: 0 0 26px;
}

.band-plain {
  margin: 0 0 26px;
  padding: 18px 16px;
  background: var(--wash);
  border-radius: var(--radius-lg);
}

.band-plain h2 {
  margin-top: 0;
}

.band-plain > :last-child {
  margin-bottom: 0;
}

/* --- the paywall ---------------------------------------------------------- */
/* ONE screen, ONE decision. Top to bottom: what you get, what the trial does,
   two prices, one button, one sentence of fine print, the three legal links.
   Nothing else is allowed on it, and in particular none of the things the
   references were full of: no testimonials, no star cards, no laurels, no
   "loved by thousands", no countdown, no invented former price with a line
   through it, no promise to remind you (this app has no push notifications), no
   mascot. Everything on this screen is either a fact about the product or a
   number that comes from our own two prices.

   The plan choice is a pair of radios and the fine print follows it with a
   sibling selector, so the sentence about what you will be charged is correct
   before any JavaScript runs and correct with JavaScript disabled forever. */
.paywall {
  max-width: 480px;
  margin: 0 auto;
}

.pw-title {
  margin: 4px 0 6px;
  font-size: 28px;
  font-weight: 800;
  line-height: 1.35;
  letter-spacing: -0.015em;
}

.pw-sub {
  margin: 0 0 20px;
  font-size: 14.5px;
  color: var(--ink-muted);
}

/* Three lines, each a thing the product does. A tick and a sentence: at three
   items a bulleted list is a list and a boxed grid is furniture. */
.pw-benefits {
  list-style: none;
  margin: 0 0 24px;
  padding: 0;
}

.pw-benefit {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  margin-bottom: 10px;
  font-size: 15px;
  font-weight: 600;
  line-height: 1.55;
}

.pw-tick {
  flex: 0 0 18px;
  width: 18px;
  height: 18px;
  margin-top: 3px;
  color: var(--ink);
}

/* The trial, as three dated steps down a rail. Honest by construction: today
   unlocks everything, the second day shows that billing is close (the app SHOWS
   it, it does not promise to send anything), the third day charges, and the date
   printed on that step is the real one. */
.pw-timeline {
  position: relative;
  margin: 0 0 24px;
  padding: 0 0 0 34px;
  list-style: none;
}

/* The rail. It stops short at both ends so it runs between the markers rather
   than out past them. */
.pw-timeline::before {
  content: "";
  position: absolute;
  top: 13px;
  bottom: 24px;
  left: 11px;
  width: 3px;
  border-radius: 999px;
  background: var(--line);
}

.pw-step {
  position: relative;
  margin-bottom: 16px;
}

.pw-step:last-child {
  margin-bottom: 0;
}

.pw-step::before {
  content: "";
  position: absolute;
  top: 5px;
  left: -34px;
  width: 25px;
  height: 25px;
  border-radius: 50%;
  background: var(--canvas);
  box-shadow: inset 0 0 0 2px var(--line);
}

/* Today is the step that has happened. */
.pw-step.is-now::before {
  background: var(--ink);
  box-shadow: none;
}

.pw-step-t {
  margin: 0;
  font-size: 15px;
  font-weight: 700;
}

.pw-step-d {
  margin: 1px 0 0;
  font-size: 13.5px;
  line-height: 1.6;
  color: var(--ink-muted);
}

/* The two plans. */
.pw-plans {
  margin: 0;
}

.pw-radio {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
}

/* Chosen is a 2px ink border and a filled radio, and nothing else changes: a
   selected card that also changed colour and grew a shadow was three answers to
   one question. The border is 2px in BOTH states so choosing does not move the
   text on the card by a pixel. */
.pw-card {
  position: relative;
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 10px;
  padding: 16px 16px 15px;
  background: var(--canvas);
  border: 2px solid #e4e8ed;
  border-radius: var(--radius-lg);
  cursor: pointer;
}

.pw-radio:checked + .pw-card {
  border-color: var(--ink);
}

/* The landing page's copy of the same cards. Nothing is being chosen there, so
   there is no radio and no cursor: they are a price list drawn in the shape the
   plan screen will show. The annual one keeps its ink border, because it is still
   the one being recommended. */
.pw-card-shown {
  cursor: default;
}

.pw-card-shown:first-of-type {
  border-color: var(--ink);
}

/* Inside the landing page's bordered 料金 card, the plan cards sit on the white
   canvas with a little air above them. */
.plan-cards {
  margin-top: 18px;
}

.pw-radio:focus-visible + .pw-card {
  outline: 2px solid var(--ink);
  outline-offset: 2px;
}

.pw-dot {
  position: relative;
  flex: 0 0 22px;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: var(--canvas);
  box-shadow: inset 0 0 0 2px #c3cad2;
}

.pw-radio:checked + .pw-card .pw-dot {
  box-shadow: inset 0 0 0 2px var(--ink);
}

.pw-radio:checked + .pw-card .pw-dot::before {
  content: "";
  position: absolute;
  inset: 5px;
  border-radius: 50%;
  background: var(--ink);
}

.pw-card-body {
  flex: 1 1 auto;
  min-width: 0;
}

.pw-plan-name {
  margin: 0;
  font-size: 14px;
  font-weight: 700;
  letter-spacing: 0.02em;
  color: var(--ink-muted-2);
}

.pw-price {
  display: flex;
  align-items: baseline;
  gap: 5px;
  margin: 2px 0 0;
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}

.pw-price-num {
  font-size: 34px;
  font-weight: 800;
  line-height: 1.05;
  letter-spacing: -0.035em;
}

.pw-price-per {
  font-size: 13px;
  font-weight: 700;
  color: var(--ink-muted-2);
}

.pw-price-total {
  margin: 3px 0 0;
  font-size: 12.5px;
  color: var(--ink-muted);
  font-variant-numeric: tabular-nums;
}

/* 58%, and it is arithmetic on the two prices printed on this very screen
   (¥990 x 12 = ¥11,880 against ¥4,900). There is no third price anywhere in this
   app's history, so there is nothing here to strike through. */
.pw-save {
  display: inline-flex;
  align-items: center;
  min-height: 24px;
  margin: 0 0 0 8px;
  padding: 2px 10px;
  background: var(--wash);
  border-radius: 999px;
  font-size: 12px;
  font-weight: 700;
  color: var(--ink-muted-2);
  font-variant-numeric: tabular-nums;
}

/* おすすめ, straddling the card's top border. Never 一番人気: we have no idea what
   is popular and would be making it up. */
.pw-badge {
  position: absolute;
  top: -12px;
  left: 16px;
  display: inline-flex;
  align-items: center;
  min-height: 22px;
  padding: 1px 10px;
  background: var(--ink);
  border-radius: 999px;
  color: var(--action-ink);
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: 0.02em;
}

/* The button, the reassurance and the fine print are PINNED to the bottom of the
   paywall, and that is the shape of the screen rather than a flourish: the reasons
   to buy are 1,200px tall on a 390pt phone and the decision cannot be 400px below
   the fold. So the reasons scroll and the decision does not move. The three lines
   stay together and in the spec's order, because the fine print belongs with the
   button it explains and nowhere else.

   This screen has no tab dock (see the layout's note): two fixed bars at the bottom
   of a phone is one too many, and the 44px ✕ in the app bar is the way out. */
.pw-bar {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 30;
  max-width: 480px;
  margin: 0 auto;
  padding: 12px max(16px, env(safe-area-inset-left)) calc(10px + env(safe-area-inset-bottom))
    max(16px, env(safe-area-inset-right));
  background: rgba(246, 247, 249, 0.92);
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
  border-top: 1px solid var(--line);
}

@supports not ((backdrop-filter: blur(12px)) or (-webkit-backdrop-filter: blur(12px))) {
  .pw-bar {
    background: var(--wash);
  }
}

/* The scrolling half reserves the bar's height. 65 of button, 12 + 10 of padding,
   and the two lines under it. */
body.paywall {
  padding-bottom: calc(196px + env(safe-area-inset-bottom));
}

/* The one button. 65px, full width, and it says what it does. */
.pw-cta {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  width: 100%;
  min-height: 65px;
  margin: 0;
  padding: 12px 20px;
  border: 0;
  border-radius: var(--radius-lg);
  background: var(--action);
  color: var(--action-ink);
  font: inherit;
  font-size: 17.5px;
  font-weight: 700;
  text-decoration: none;
  cursor: pointer;
  transition: opacity 0.15s ease, transform 120ms ease;
}

.pw-cta:active {
  opacity: 0.9;
  transform: scale(0.97);
}

.pw-cta-chev {
  display: block;
  width: 18px;
  height: 18px;
}

/* Directly under the button, because that is where the question is asked. */
.pw-reassure {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  margin: 12px 0 0;
  font-size: 13.5px;
  font-weight: 600;
  color: var(--ink);
  text-align: center;
}

.pw-reassure .pw-tick {
  flex: 0 0 16px;
  width: 16px;
  height: 16px;
  margin: 0;
}

/* ONE sentence, and it is the whole truth about what happens next. */
.pw-fine {
  margin: 8px 0 0;
  font-size: 12.5px;
  line-height: 1.6;
  color: var(--ink-muted);
  text-align: center;
}

/* Which of the two sentences is on screen follows the radio, with no script.

   The yearly sentence is the default and the monthly one is hidden, so the plan
   that is preselected is the plan the fine print describes before anything at all
   has run. :has() then swaps them when the other card is chosen. A browser without
   :has() keeps showing the yearly sentence, which is the preselected plan: the
   degradation is "the fine print does not follow you", never "the fine print is
   wrong about what you are about to buy" -- and the form still posts whichever plan
   the radio holds, because the radio is the input and this is only its caption. */
.pw-fine-monthly {
  display: none;
}

.pw-form:has(.pw-radio-monthly:checked) .pw-fine-yearly {
  display: none;
}

.pw-form:has(.pw-radio-monthly:checked) .pw-fine-monthly {
  display: block;
}

/* --- 最終確認 -------------------------------------------------------------- */
/* The block that states what is about to be bought: plan, total payable with tax,
   the date and amount of the first charge, that it renews, and how to stop it.
   Japan requires those five on the purchase screen itself, so this is not a
   flourish and it does not get to be a link to somewhere else.

   Drawn as a white card on the wash, which is this app's grammar for a surface,
   with a two-column definition list: the labels are the quiet half and the
   answers are the half being read. It sits in the scrolling part of the paywall,
   directly under the two plan cards, because it describes the choice made there. */
.pw-conf {
  margin: 20px 0 0;
  padding: 16px 16px 14px;
  background: var(--canvas);
  border-radius: var(--radius-lg);
  box-shadow: 0 1px 3px rgba(26, 29, 33, 0.05);
}

.pw-conf-t {
  margin: 0 0 10px;
  font-size: 14px;
  font-weight: 700;
  letter-spacing: 0.01em;
}

/* Label above answer rather than beside it: on a 390pt screen the answers are
   sentences (the cancellation row is three), and a 90px label column would set
   them in a 200px gutter. */
.pw-conf-list {
  margin: 0;
}

.pw-conf-list dt {
  margin: 10px 0 0;
  font-size: 11.5px;
  font-weight: 700;
  color: var(--ink-muted-2);
  letter-spacing: 0.02em;
}

.pw-conf-list dt:first-child {
  margin-top: 0;
}

.pw-conf-list dd {
  margin: 2px 0 0;
  font-size: 13.5px;
  line-height: 1.65;
}

.pw-conf-note {
  margin: 12px 0 0;
  font-size: 12.5px;
  line-height: 1.6;
  color: var(--ink-muted);
}

/* Which plan's block is on screen follows the radio, with no script, exactly like
   the fine print in the pinned bar (.pw-fine, above): the annual plan is
   preselected and its block is the default, so what the reader sees before
   anything runs is the plan the form will post. A browser without :has() keeps the
   annual block, which is that plan. On the unarmed page there is no form at all,
   and the annual block is likewise what stays. */
.pw-conf-monthly {
  display: none;
}

.pw-form:has(.pw-radio-monthly:checked) .pw-conf-yearly {
  display: none;
}

.pw-form:has(.pw-radio-monthly:checked) .pw-conf-monthly {
  display: block;
}

/* Class-keyed copies of the four :has() rules above, for browsers without
   :has() but with JavaScript: public/app.js mirrors the chosen radio onto the
   form as .plan-monthly. Same rules, second key, so the fine print and the
   confirmation block can never disagree about which plan is chosen. */
.pw-form.plan-monthly .pw-fine-yearly,
.pw-form.plan-monthly .pw-conf-yearly {
  display: none;
}

.pw-form.plan-monthly .pw-fine-monthly,
.pw-form.plan-monthly .pw-conf-monthly {
  display: block;
}

/* The free tier, said next to the prices. */
.pw-free {
  margin: 18px 0 0;
  font-size: 13px;
  color: var(--ink-muted);
  text-align: center;
}

/* 利用規約 / プライバシー / 特定商取引法に基づく表記. 特商法 on the purchase screen is
   a Japanese legal requirement, not a nicety. */
.pw-legal {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 0 14px;
  margin: 14px 0 0;
  font-size: 12.5px;
}

.pw-legal a {
  padding: 13px 0;
  color: var(--ink-muted);
}

/* --- the upgrade sheet's escape and its price line ------------------------- */
/* A 44px wash circle in the top right corner. The way out of a sheet has to be
   the size of a thumb and in the place a thumb looks for it. */
.sheet-x {
  position: absolute;
  top: 12px;
  right: max(14px, env(safe-area-inset-right));
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--tap);
  height: var(--tap);
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: var(--wash);
  color: var(--ink);
  cursor: pointer;
}

.sheet-x:active {
  background: #eceef2;
}

.sheet-x-glyph {
  display: block;
  width: 18px;
  height: 18px;
}

.sheet-price {
  margin: 4px 0 0;
  font-size: 14px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

/* The sheet's own actions: the ink button is full width and the way out is a text
   link under it, because they are not two equal choices. */
.sheet-actions {
  margin-top: 16px;
}

.sheet-out {
  display: block;
  width: 100%;
  min-height: var(--tap);
  margin: 6px 0 0;
  border: 0;
  background: none;
  color: var(--ink-muted);
  font: inherit;
  font-size: 14.5px;
  font-weight: 600;
  text-align: center;
  cursor: pointer;
}

/* ===========================================================================
   The onboarding flow (/start, S1 to S15).

   Its own surface, and deliberately not the app's: the flow is a WHITE canvas
   with one question on it, no tab dock, no footer and no column furniture. An
   app screen is the #F6F7F9 wash with white cards floating on it, which is the
   grammar of "here is your data"; this is the grammar of "answer this".

   Three things carry the whole flow, and they are the three the reference decks
   agree on: a thin progress rule under a back arrow, one big question in 700
   weight with small muted subcopy, and one full-width ink pill pinned to the
   bottom. Everything else on any given screen is the answer control.

   Selection is the system's existing .choice card, unchanged, because "this one"
   already has an answer in this design system: an ink border and a wash fill. No
   colour is spent on saying yes anywhere in this app, and that does not change
   because a screen is new.
   =========================================================================== */

/* Edge to edge, and it holds the viewport: the CTA is pinned to the bottom of
   the screen rather than to the bottom of the content, so a short question does
   not leave its button floating in the middle of the page. */
body.onb-page {
  background: var(--canvas);
}

/* The flow's own column. The wrap around it already caps the width; this adds the
   full height and the safe-area room the pinned CTA needs. */
.onb {
  display: flex;
  flex-direction: column;
  min-height: calc(100vh - 32px);
  min-height: calc(100dvh - 32px);
}

/* The no-JavaScript plan landing is an ordinary page: nothing is pinned and
   nothing is a step. */
.onb-static {
  min-height: 0;
}

/* --- the persistent bar --------------------------------------------------- */
/* One way back, one measure of how far in you are, and the count in figures so
   the bar is never the only thing saying it. Hidden on S1: a welcome screen is
   not a question and has nothing to be 1/14 of. */
.onb-bar {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 0 0 22px;
  padding-top: 2px;
}

.onb-back {
  flex: 0 0 var(--tap);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--tap);
  height: var(--tap);
  margin-left: -12px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: var(--ink);
  cursor: pointer;
}

.onb-back-glyph {
  width: 22px;
  height: 22px;
}

.onb-prog {
  flex: 1 1 auto;
  height: 4px;
  border-radius: 999px;
  background: var(--line);
  overflow: hidden;
}

/* The fill. Its width is a .w-N class, because the CSP this app sends has no
   'unsafe-inline' in style-src and a percentage cannot ride in on a style
   attribute. public/app.js swaps the class as the step changes. */
.onb-prog-fill {
  display: block;
  height: 100%;
  border-radius: 999px;
  background: var(--ink);
  transition: width 260ms cubic-bezier(0.2, 0.8, 0.2, 1);
}

.onb-count {
  flex: 0 0 auto;
  margin: 0;
  font-size: 12.5px;
  font-weight: 700;
  color: var(--ink-muted);
}

/* --- a step --------------------------------------------------------------- */
/* Every step fills the column and pushes its CTA to the bottom. With no script
   every step is visible at once and this simply stacks them, which is exactly
   what the single submit button at the end needs.

   No entry animation between steps: 15 screens with a slide each is 15 waits, and
   the thing that has to feel instant here is the answer registering. The press
   feedback on the button (.btn:active, 3% for 120ms) is the motion that answers a
   finger, and it is already in the system. */
.onb-step {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
}

/* The question. One per screen, and the largest thing on it. */
.onb-q {
  margin: 0 0 6px;
  font-size: 25px;
  font-weight: 700;
  line-height: 1.42;
  letter-spacing: -0.015em;
}

/* The reassurance under it: why this is being asked, in one line. Every data
   question in this flow carries one, and every one of them is true. */
.onb-sub {
  margin: 0 0 22px;
  font-size: 14px;
  line-height: 1.65;
  color: var(--ink-muted);
}

.onb-q + .onb-choices,
.onb-q + .onb-native,
.onb-q + .paces {
  margin-top: 22px;
}

.onb-h2 {
  margin: 26px 0 12px;
  font-size: 15px;
  font-weight: 700;
}

.onb-eyebrow {
  margin: 0 0 4px;
  font-size: 12.5px;
  font-weight: 700;
  letter-spacing: 0.04em;
  color: var(--ink-muted);
}

.onb-note {
  margin: 12px 0 0;
  font-size: 13px;
  line-height: 1.6;
  color: var(--ink-muted);
}

.onb-fieldlab {
  margin: 22px 0 8px;
  font-size: 13.5px;
  font-weight: 700;
  color: var(--ink-muted-2);
}

.onb-status {
  margin: 16px 0 0;
  font-size: 13.5px;
  color: var(--ink-muted);
}

.onb-choices {
  gap: 10px;
}

/* --- the pinned action --------------------------------------------------- */
/* One thing to do, at the bottom, always in the same place. mt:auto is what pins
   it: the step is a flex column that fills the page, so the CTA is pushed to the
   end of it whether the question above is two cards or six. */
.onb-cta {
  margin-top: auto;
  padding-top: 24px;
}

/* The button. 56px and full width, in ink, and it says what the next screen is
   rather than 「次へ」 wherever there is something better to say. */
.onb-go {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  min-height: 56px;
  padding: 14px 20px;
  border: 0;
  border-radius: var(--radius-sm);
  background: var(--action);
  color: var(--action-ink);
  font: inherit;
  font-size: 16.5px;
  font-weight: 700;
  text-align: center;
  text-decoration: none;
  cursor: pointer;
  transition: opacity 0.15s ease, transform 120ms ease;
}

.onb-go:active {
  opacity: 0.82;
  transform: scale(0.985);
}

/* Disabled is a real state here: on the steps that have no default answer the
   button waits for one. A grey pill with grey ink rather than a faded black one, so
   it reads as "not yet" instead of as broken.
   #E4E8ED against #FFFFFF is the same tone the unselected plan cards carry, and the
   label on it measures 4.6:1, so the word on a waiting button is still readable. */
.onb-go[disabled] {
  background: #e4e8ed;
  color: #6b7280;
  cursor: default;
}

.onb-go[disabled]:active {
  opacity: 1;
  transform: none;
}

/* The sign-in button is an anchor, so it needs the line-height an inline element
   does not get from min-height alone. */
.onb-go-link {
  line-height: 1.4;
}

.onb-signin-line {
  margin: 14px 0 0;
  font-size: 14px;
  text-align: center;
  color: var(--ink-muted);
}

/* --- S1 welcome ---------------------------------------------------------- */
/* The transformation, then the product doing it. The phone is not a decoration:
   the card inside it carries the figures this app's own model returned for the
   photograph behind it, which is this product's honest substitute for the
   testimonials a welcome screen usually leans on. */
.onb-step-hero {
  padding-top: 8px;
}

.onb-hero {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
}

.onb-hero-t {
  margin: 0 0 10px;
  font-size: 29px;
  font-weight: 800;
  line-height: 1.35;
  letter-spacing: -0.02em;
}

.onb-hero-s {
  margin: 0 0 26px;
  font-size: 14.5px;
  line-height: 1.7;
  color: var(--ink-muted);
}

/* A phone, drawn rather than photographed: a bezel, a screen, and the app's own
   result card over the food. No device chrome beyond the rounded rectangle, no
   notch, no status bar. */
.onb-phone {
  width: 214px;
  max-width: 66vw;
  padding: 7px;
  border-radius: 30px;
  background: var(--ink);
  box-shadow: 0 12px 30px rgba(26, 29, 33, 0.22);
}

.onb-phone-screen {
  position: relative;
  border-radius: 24px;
  overflow: hidden;
  background: var(--wash);
}

.onb-phone-img {
  display: block;
  width: 100%;
  height: 246px;
  object-fit: cover;
  object-position: center 45%;
}

/* The card sits over the bottom of the photograph, which is where this app puts
   it on the real screen too. */
.onb-phone-card {
  position: absolute;
  left: 8px;
  right: 8px;
  bottom: 8px;
  padding: 10px 11px 11px;
  border-radius: var(--radius-sm);
  background: var(--canvas);
  box-shadow: 0 2px 10px rgba(26, 29, 33, 0.14);
  text-align: left;
}

.onb-phone-name {
  margin: 0;
  font-size: 12.5px;
  font-weight: 700;
}

.onb-phone-kcal {
  display: flex;
  align-items: baseline;
  gap: 3px;
  margin: 3px 0 0;
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}

.onb-phone-kcal .num {
  font-size: 23px;
  font-weight: 800;
  letter-spacing: -0.03em;
  line-height: 1.05;
}

.onb-phone-kcal .unit {
  font-size: 11px;
  font-weight: 700;
  color: var(--ink-muted);
}

.onb-phone-keys,
.demo-keys {
  display: flex;
  flex-wrap: wrap;
  gap: 5px 8px;
  margin: 7px 0 0;
  padding: 0;
  list-style: none;
  font-size: 11.5px;
  font-variant-numeric: tabular-nums;
}

/* The macro dots in these two lists are the small size, like the meal rows'. */
.onb-phone-keys .dot,
.demo-keys .dot {
  flex: 0 0 7px;
  width: 7px;
  height: 7px;
}

/* Says what the card above it is, so nobody has to wonder whether the numbers are
   a mock-up. They are not. */
.onb-hero-cap {
  margin: 14px 0 0;
  font-size: 12.5px;
  line-height: 1.6;
  color: var(--ink-muted);
}

/* --- the credibility block, on S10 --------------------------------------- */
/* It was the bottom half of the bridge screen (the old S5) until that screen was
   cut, and it moved onto the plan screen with the citation it carries. The rules
   that drew the bridge's own struck-through rows went with the screen; only these
   are still rendered. */
/* The credibility block: how it works, and one category finding with its source
   printed. This is the slot a reference flow fills with 「80% of users maintain
   their weight loss」. This app has no users, so it names a paper instead and says
   in as many words that the paper is not about this app. */
.bridge-cred {
  margin: 26px 0 0;
  padding: 16px 15px;
  background: var(--wash);
  border-radius: var(--radius-lg);
}

.bridge-cred-t {
  margin: 0 0 3px;
  font-size: 13px;
  font-weight: 700;
}

.bridge-cred-t ~ .bridge-cred-t {
  margin-top: 14px;
}

.bridge-cred-d {
  margin: 0;
  font-size: 13.5px;
  line-height: 1.7;
  color: var(--ink-muted-2);
}

/* The citation, small and complete. Latin text, so it does not get the Japanese
   line-breaking treatment. */
.bridge-cite {
  display: block;
  margin-top: 5px;
  font-size: 11.5px;
  line-height: 1.55;
  color: var(--ink-muted);
  word-break: break-word;
}

.bridge-cred-n {
  margin: 14px 0 0;
  font-size: 11.5px;
  line-height: 1.6;
  color: var(--ink-muted);
}

/* --- the wheel picker ---------------------------------------------------- */
/* Vertical scroll-snap, and nothing else. No library and no inline style: the
   rows are 44px, CSS snaps them to the middle of the window, and public/app.js
   reads which row is centred and writes it into the native field the wheel
   replaced. So the wheel is a way of typing, and the field is still the answer.

   Three things make it read as a wheel rather than as a scrolling list: the
   selected row sits inside a wash capsule, its neighbours are faded and one step
   smaller, and the top and bottom of the window are masked. */
.wheels {
  display: flex;
  gap: 8px;
  justify-content: center;
  margin: 10px 0 0;
}

.wheels-one {
  max-width: 190px;
  margin-inline: auto;
}

.wheel {
  position: relative;
  flex: 1 1 0;
  height: 220px;
  overflow: hidden;
}

/* The capsule the selected row sits in. One rule, centred, behind the list, so
   the row that lands in it is the answer. */
.wheel::before {
  content: "";
  position: absolute;
  top: 88px;
  left: 0;
  right: 0;
  height: 44px;
  border-radius: 999px;
  background: var(--wash);
  pointer-events: none;
}

/* The list itself. 88px of padding top and bottom is exactly two rows, which is
   what puts the first and last entries reachable in the middle of the window. */
.wheel-list {
  position: relative;
  height: 100%;
  margin: 0;
  padding: 88px 0;
  list-style: none;
  overflow-y: auto;
  scroll-snap-type: y mandatory;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
  /* Fades the rows at the two ends rather than clipping them dead. */
  -webkit-mask-image: linear-gradient(to bottom, transparent, #000 26%, #000 74%, transparent);
  mask-image: linear-gradient(to bottom, transparent, #000 26%, #000 74%, transparent);
}

.wheel-list::-webkit-scrollbar {
  display: none;
}

.wheel-row {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 44px;
  scroll-snap-align: center;
  font-size: 17px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--ink-muted);
  transition: color 140ms ease, transform 140ms ease;
  cursor: pointer;
}

/* The centred row. public/app.js puts this class on exactly one row per column. */
.wheel-row.is-on {
  font-size: 19px;
  font-weight: 700;
  color: var(--ink);
}

/* The two either side of it, one step down. Anything further out is already faded
   by the mask. */
.wheel-row.is-near {
  transform: scale(0.94);
}

/* The tick on snap: a 3% pulse on the row that just became the answer. It is the
   one piece of motion on these screens, it lasts 140ms, and the reduced-motion
   block at the end of this section removes it. */
.wheel-row.is-on.is-tick {
  animation: wheel-tick 140ms ease-out;
}

@keyframes wheel-tick {
  0% {
    transform: scale(1);
  }
  45% {
    transform: scale(1.03);
  }
  100% {
    transform: scale(1);
  }
}

/* The unit under each column (年 / 月 / 日, cm). Outside the scroller, so it does
   not move. */
.wheel-unit {
  position: absolute;
  right: 4px;
  top: 99px;
  font-size: 12px;
  font-weight: 700;
  color: var(--ink-muted);
  pointer-events: none;
}

/* --- the ruler slider --------------------------------------------------- */
/* The same idea turned on its side, for a weight: a big readout, a horizontal
   scroll-snap strip of 0.5 kg ticks with the centre one magnified, and a chevron
   at each end for the last half kilo. The readout is the figure; the strip is how
   you move it. */
.ruler {
  margin: 10px 0 0;
}

.ruler-readout {
  display: flex;
  align-items: baseline;
  justify-content: center;
  gap: 4px;
  margin: 0 0 6px;
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}

.ruler-num {
  font-size: 44px;
  font-weight: 800;
  letter-spacing: -0.035em;
  line-height: 1.05;
}

.ruler-unit {
  font-size: 15px;
  font-weight: 700;
  color: var(--ink-muted);
}

.ruler-row {
  display: flex;
  align-items: center;
  gap: 2px;
}

/* The fine-adjust chevrons. 44px each, and public/app.js repeats them on a long
   press. They exist because a 0.5 kg step on a scroll strip is a fingertip wide,
   and somebody who wants 62.5 rather than 63.0 should not have to fight for it. */
.ruler-step {
  flex: 0 0 var(--tap);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--tap);
  height: var(--tap);
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: var(--ink-muted-2);
  cursor: pointer;
}

.ruler-step:active {
  color: var(--ink);
}

.ruler-step-glyph {
  width: 20px;
  height: 20px;
}

.ruler-window {
  position: relative;
  flex: 1 1 auto;
  height: 62px;
  overflow: hidden;
}

/* The centre marker: a single ink rule the ticks slide under, with the same mask
   at both ends the wheel uses. */
.ruler-window::before {
  content: "";
  position: absolute;
  left: 50%;
  top: 6px;
  width: 2px;
  height: 34px;
  margin-left: -1px;
  border-radius: 999px;
  background: var(--ink);
  pointer-events: none;
  z-index: 1;
}

.ruler-strip {
  display: flex;
  align-items: flex-start;
  height: 100%;
  margin: 0;
  padding: 0;
  list-style: none;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
  -webkit-mask-image: linear-gradient(to right, transparent, #000 16%, #000 84%, transparent);
  mask-image: linear-gradient(to right, transparent, #000 16%, #000 84%, transparent);
}

.ruler-strip::-webkit-scrollbar {
  display: none;
}

/* One tick per half kilo. 12px apart, which is close enough to read as a scale
   and far enough that the snap has somewhere to land. */
.ruler-tick {
  flex: 0 0 12px;
  position: relative;
  height: 100%;
  scroll-snap-align: center;
}

.ruler-tick::before {
  content: "";
  position: absolute;
  left: 50%;
  top: 12px;
  width: 1px;
  height: 12px;
  margin-left: -0.5px;
  background: var(--line);
}

/* Every whole kilo is taller, and every fifth carries its number, so the strip is
   a scale rather than a comb. */
.ruler-tick.is-whole::before {
  top: 8px;
  height: 20px;
  background: #d7dbe1;
}

.ruler-tick.is-label::before {
  top: 6px;
  height: 24px;
  background: var(--ink-muted);
}

.ruler-tick-lab {
  position: absolute;
  top: 34px;
  left: 50%;
  transform: translateX(-50%);
  font-size: 11px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--ink-muted);
}

/* The two spacers at the ends of the strip, so the first and last real values can
   reach the centre marker.

   Half the scroller's width, less half a tick, in pure CSS: the strip IS the
   scroll container, so a percentage here is a percentage of the visible window.
   Doing it this way rather than measuring in script is what keeps a width out of
   an inline style attribute, which this app's CSP has no 'unsafe-inline' for. */
.ruler-pad {
  flex: 0 0 calc(50% - 6px);
  height: 1px;
}

/* 数値で入力する: the native field the wheel replaced, one press away. A wheel is a
   touch control and is not keyboard-operable, so the field it replaces has to stay
   reachable rather than being hidden for good.
   Quiet on purpose: it is a way in for the few who need it, not a second answer to
   the question. A full-width bordered button here competed with the wheel it sits
   under, which the screenshots showed. 44px of touch target, no box. */
.onb-numeric {
  display: block;
  min-height: var(--tap);
  margin: 10px auto 0;
  padding: 0 14px;
  border: 0;
  background: transparent;
  color: var(--ink-muted-2);
  font: inherit;
  font-size: 13.5px;
  font-weight: 700;
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}

/* --- S9 the pace chips -------------------------------------------------- */
/* Three rungs, and the middle one carries a badge. The rate on each chip is
   printed from the same kg-per-week number the calorie adjustment is computed
   from (src/targets.js PACES), so a chip cannot advertise a pace the budget does
   not use. */
.paces {
  display: grid;
  gap: 10px;
}

.pace {
  display: block;
  position: relative;
}

.pace input {
  position: absolute;
  top: 0;
  left: 0;
  width: 1px;
  height: 1px;
  opacity: 0;
}

.pace-body {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: var(--tap);
  padding: 12px 15px;
  background: var(--canvas);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  transition: border-color 0.15s ease, background-color 0.15s ease;
}

.pace input:checked + .pace-body {
  border-color: var(--ink);
  background: var(--wash);
  box-shadow: inset 0 0 0 1px var(--ink);
}

.pace input:focus-visible + .pace-body {
  outline: 2px solid var(--ink);
  outline-offset: 2px;
}

.pace-name {
  font-size: 16px;
  font-weight: 700;
}

.pace-rate {
  flex: 1 1 auto;
  font-size: 12.5px;
  color: var(--ink-muted);
}

/* おすすめ, and that is the only word on it: 一番人気 would be a claim about what
   other people chose, and this app does not know that. */
.pace-badge {
  flex: 0 0 auto;
  padding: 3px 9px;
  border-radius: 999px;
  background: var(--ink);
  color: var(--action-ink);
  font-size: 11px;
  font-weight: 700;
}

/* --- S11 the calculating step ------------------------------------------- */
/* The scan flow's checklist, with a fourth line. The delays are the same ladder,
   compressed: this step lasts about two and a half seconds, not ten. */
.onb-step-calc {
  justify-content: center;
}

.ckl-4 .ck-1 {
  --ck-delay: 200ms;
}

.ckl-4 .ck-2 {
  --ck-delay: 800ms;
}

.ckl-4 .ck-3 {
  --ck-delay: 1400ms;
}

.ckl-4 .ck-4 {
  --ck-delay: 2000ms;
}

/* --- S12 the plan ------------------------------------------------------- */
.plan-kcal {
  position: relative;
  margin: 20px 0 0;
  padding: 20px 18px 18px;
  background: var(--wash);
  border-radius: var(--radius-lg);
  text-align: center;
}

.plan-kcal-fig {
  display: flex;
  align-items: baseline;
  justify-content: center;
  gap: 5px;
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}

.plan-kcal-num {
  font-size: 52px;
  font-weight: 800;
  letter-spacing: -0.04em;
  line-height: 1;
}

.plan-kcal-unit {
  font-size: 16px;
  font-weight: 700;
  color: var(--ink-muted);
}

.plan-kcal-lab {
  margin: 8px 0 0;
  font-size: 13.5px;
  font-weight: 600;
  color: var(--ink-muted-2);
}

/* The pencil. A REAL control: it returns to the answer that decides the number
   beside it, because this app never stores a hand-edited target and "edit this
   number" can therefore only honestly mean "change what it is computed from". */
.plan-edit {
  position: absolute;
  top: 10px;
  right: 8px;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  min-height: var(--tap);
  padding: 0 10px;
  border: 0;
  border-radius: 999px;
  background: transparent;
  color: var(--ink-muted-2);
  font: inherit;
  font-size: 12.5px;
  font-weight: 700;
  cursor: pointer;
}

.plan-edit:active {
  color: var(--ink);
}

.plan-edit-glyph {
  width: 17px;
  height: 17px;
}

.plan-edit-sm {
  top: 2px;
  right: 0;
  padding: 0;
  width: var(--tap);
  justify-content: center;
}

/* The PFC heading and its one 修正, on the same line. */
.plan-macros-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}

.plan-macros-head .onb-h2 {
  margin-bottom: 12px;
}

/* In the flow of a heading rather than pinned into a corner, which is what the
   three per-tile pencils used to be. */
.plan-edit-inline {
  position: static;
  margin: 0 -10px 0 0;
}

.plan-macros {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 10px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.plan-macro {
  position: relative;
  padding: 14px 10px 12px;
  background: var(--canvas);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  text-align: center;
}

.plan-macro-lab {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 5px;
  margin: 0;
  font-size: 12px;
  font-weight: 700;
  color: var(--ink-muted-2);
}

.plan-macro-lab .dot {
  flex: 0 0 7px;
  width: 7px;
  height: 7px;
}

.plan-macro-g {
  margin: 9px 0 0;
  font-size: 20px;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.02em;
}

/* The share of the day's calories this macro is, which is what the ring above it
   draws. Data, not decoration: the three add up to the budget. */
.plan-macro-share {
  margin: 1px 0 0;
  font-size: 11.5px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--ink-muted);
}

/* --- S12 the projection -------------------------------------------------- */
/* Two bars and a label. This is the block that replaces the reference flow's
   「Lose 10 kg by October 31」 and its rising-curve chart, and what is left is the
   only comparison this app can make honestly: what the body is estimated to
   spend, against what the plan budgets. */
.plan-proj {
  margin: 26px 0 0;
  padding: 16px 15px 15px;
  background: var(--wash);
  border-radius: var(--radius-lg);
}

.plan-proj .onb-h2 {
  margin-top: 0;
}

/* The label and its figure on one row, the bar full width underneath. A three
   column grid broke 「推定消費カロリー」 across two lines at 390px and left a lone ー
   hanging under it. */
.projbar {
  margin-bottom: 14px;
}

.projbar-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  margin: 0 0 5px;
}

.projbar-lab {
  font-size: 13px;
  font-weight: 600;
  color: var(--ink-muted-2);
}

.projbar-track {
  height: 10px;
  border-radius: 999px;
  background: #e4e8ed;
  overflow: hidden;
}

.projbar-fill {
  display: block;
  height: 100%;
  border-radius: 999px;
  transition: width 300ms cubic-bezier(0.2, 0.8, 0.2, 1);
}

/* The estimated spend is the reference, so it is the recessive one; the budget is
   the decision, so it is ink. Two tones of one hue rather than two hues: neither
   of these is a macro, and the macro palette is not available to borrow. */
.projbar-maint {
  background: #9aa3ad;
}

.projbar-budget {
  background: var(--ink);
}

.projbar-val {
  font-size: 13.5px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.plan-gap {
  margin: 14px 0 0;
  font-size: 14px;
  font-weight: 600;
  line-height: 1.6;
}

.plan-pace {
  margin: 4px 0 0;
  font-size: 13.5px;
  color: var(--ink-muted-2);
}

/* The sentence that keeps this whole block inside 景表法. Small, but never absent:
   nothing above it is a promise, and this says so. */
.plan-legal {
  margin: 12px 0 0;
  font-size: 11.5px;
  line-height: 1.6;
  color: var(--ink-muted);
}

.plan-how {
  margin: 24px 0 0;
}

.plan-how .muted {
  font-size: 13px;
}

/* --- S13 the demo scan -------------------------------------------------- */
.demo-choose {
  margin: 22px 0 0;
}

/* The sample. A real photograph with the label over it, because "tap this photo"
   is the instruction and a button that says so next to a picture is two things to
   read. */
.demo-sample {
  position: relative;
  display: block;
  width: 100%;
  margin: 12px 0 0;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--wash);
  overflow: hidden;
  cursor: pointer;
}

.demo-sample-img {
  display: block;
  width: 100%;
  height: 150px;
  object-fit: cover;
  object-position: center 45%;
}

.demo-sample-lab {
  position: absolute;
  left: 10px;
  bottom: 10px;
  padding: 8px 14px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.94);
  color: var(--ink);
  font-size: 14px;
  font-weight: 700;
  box-shadow: 0 2px 8px rgba(26, 29, 33, 0.16);
}

.demo-busy {
  margin: 26px 0 0;
}

.demo-result {
  margin: 20px 0 0;
}

.demo-card {
  display: grid;
  grid-template-columns: 116px 1fr;
  gap: 12px;
  padding: 12px;
  background: var(--canvas);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
}

.demo-card-photo {
  border-radius: var(--radius-sm);
  background: var(--wash);
  overflow: hidden;
}

.demo-card-img {
  display: block;
  width: 100%;
  height: 100%;
  min-height: 116px;
  object-fit: cover;
  object-position: center 45%;
}

.demo-card-body {
  min-width: 0;
}

.demo-name {
  margin: 2px 0 0;
  font-size: 15px;
  font-weight: 700;
  line-height: 1.4;
}

.demo-kcal {
  display: flex;
  align-items: baseline;
  gap: 4px;
  margin: 5px 0 0;
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}

.demo-kcal .num {
  font-size: 30px;
  font-weight: 800;
  letter-spacing: -0.03em;
  line-height: 1.05;
}

.demo-kcal .unit {
  font-size: 12px;
  font-weight: 700;
  color: var(--ink-muted);
}

.demo-range {
  margin: 3px 0 0;
  font-size: 11.5px;
  font-variant-numeric: tabular-nums;
}

/* The tie back to the plan: what this meal leaves of today. It is the sentence
   that makes the demo about THEIR plan rather than about a photograph. */
.demo-tie {
  margin: 14px 0 0;
  padding: 12px 14px;
  background: var(--wash);
  border-radius: var(--radius-sm);
  font-size: 14.5px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

/* --- S14 sign in -------------------------------------------------------- */
/* What is being kept, on the screen that asks to keep it. */
.save-preview {
  display: grid;
  grid-template-columns: 88px 1fr;
  gap: 12px;
  align-items: center;
  margin: 22px 0 0;
  padding: 12px;
  background: var(--wash);
  border-radius: var(--radius-lg);
}

.save-thumb {
  border-radius: var(--radius-sm);
  overflow: hidden;
  background: #e4e8ed;
}

.save-thumb-img {
  display: block;
  width: 100%;
  height: 88px;
  object-fit: cover;
  object-position: center 45%;
}

.save-name {
  margin: 0;
  font-size: 15px;
  font-weight: 700;
}

.save-plan {
  margin: 4px 0 0;
  font-size: 13px;
  font-variant-numeric: tabular-nums;
  color: var(--ink-muted-2);
}

.save-points {
  list-style: none;
  margin: 22px 0 0;
  padding: 0;
}

/* --- S15 the proof block ------------------------------------------------ */
/* Their own saved scan, shown again as the thing the subscription is for. This is
   the slot a paywall usually fills with a testimonial; this app has none, so it
   shows the work instead. */
.pw-proof {
  display: grid;
  grid-template-columns: 104px 1fr;
  gap: 12px;
  margin: 0 0 22px;
  padding: 11px;
  background: var(--wash);
  border-radius: var(--radius-lg);
}

.pw-proof-photo {
  border-radius: var(--radius-sm);
  overflow: hidden;
  background: #e4e8ed;
}

.pw-proof-img {
  display: block;
  width: 100%;
  height: 104px;
  object-fit: cover;
  object-position: center 45%;
}

.pw-proof-cap {
  margin: 2px 0 0;
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: 0.03em;
  color: var(--ink-muted);
}

.pw-proof-name {
  margin: 3px 0 0;
  font-size: 14.5px;
  font-weight: 700;
}

.pw-proof-kcal {
  display: flex;
  align-items: baseline;
  gap: 4px;
  margin: 3px 0 0;
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}

.pw-proof-kcal .num {
  font-size: 26px;
  font-weight: 800;
  letter-spacing: -0.03em;
  line-height: 1.05;
}

.pw-proof-kcal .unit {
  font-size: 11.5px;
  font-weight: 700;
  color: var(--ink-muted);
}

/* The way out of the last step. A paywall with no visible escape is a trap: the
   free tier is real and 今日 is one tap away. */
.pw-skip {
  margin: 18px 0 0;
  font-size: 14px;
  text-align: center;
}

.pw-skip a {
  color: var(--ink-muted);
}

/* --- the flow's motion, under prefers-reduced-motion -------------------- */
/* The global rule at the top of this file zeroes DURATIONS but not DELAYS, and that
   gap is what this block exists for. An animation with a 2000ms delay and a 0.01ms
   duration still fires: it waits two seconds and then snaps to its end state, which
   is the worst of both worlds. So the two staggered things in this flow are turned
   off by name: the four-line checklist, and the wheel's snap pulse.
   The three `transition: none` rules below cannot win against the global block's
   `transition-duration: 0.01ms !important` and are not trying to; they state the
   intent locally, so a future narrowing of that global rule does not silently give
   these three their motion back.
   The progress bar keeps its class-driven WIDTH either way: that width is the answer
   to "how far in am I" and not an animation. */
@media (prefers-reduced-motion: reduce) {
  .ckl-4 .ck,
  .ckl-4 .ck-dot,
  .ckl-4 .ck-tick {
    animation: none;
  }

  .wheel-row.is-on.is-tick {
    animation: none;
  }

  .wheel-row,
  .onb-prog-fill,
  .projbar-fill {
    transition: none;
  }
}

/* --- bar widths ----------------------------------------------------------- */
/* One class per whole percent, because the Content-Security-Policy this app
   sends has no 'unsafe-inline' in style-src and the day screen's progress bars
   used to carry style="width: N%" attributes. Generated by hand once and
   left alone: the callers (views/today.ejs) already clamp to 0-100 whole
   numbers in src/routes/today.js, so a class is always present. Written compactly
   on purpose; there is nothing to read here. */
.w-0{width:0%}.w-1{width:1%}.w-2{width:2%}.w-3{width:3%}.w-4{width:4%}.w-5{width:5%}.w-6{width:6%}.w-7{width:7%}.w-8{width:8%}.w-9{width:9%}
.w-10{width:10%}.w-11{width:11%}.w-12{width:12%}.w-13{width:13%}.w-14{width:14%}.w-15{width:15%}.w-16{width:16%}.w-17{width:17%}.w-18{width:18%}.w-19{width:19%}
.w-20{width:20%}.w-21{width:21%}.w-22{width:22%}.w-23{width:23%}.w-24{width:24%}.w-25{width:25%}.w-26{width:26%}.w-27{width:27%}.w-28{width:28%}.w-29{width:29%}
.w-30{width:30%}.w-31{width:31%}.w-32{width:32%}.w-33{width:33%}.w-34{width:34%}.w-35{width:35%}.w-36{width:36%}.w-37{width:37%}.w-38{width:38%}.w-39{width:39%}
.w-40{width:40%}.w-41{width:41%}.w-42{width:42%}.w-43{width:43%}.w-44{width:44%}.w-45{width:45%}.w-46{width:46%}.w-47{width:47%}.w-48{width:48%}.w-49{width:49%}
.w-50{width:50%}.w-51{width:51%}.w-52{width:52%}.w-53{width:53%}.w-54{width:54%}.w-55{width:55%}.w-56{width:56%}.w-57{width:57%}.w-58{width:58%}.w-59{width:59%}
.w-60{width:60%}.w-61{width:61%}.w-62{width:62%}.w-63{width:63%}.w-64{width:64%}.w-65{width:65%}.w-66{width:66%}.w-67{width:67%}.w-68{width:68%}.w-69{width:69%}
.w-70{width:70%}.w-71{width:71%}.w-72{width:72%}.w-73{width:73%}.w-74{width:74%}.w-75{width:75%}.w-76{width:76%}.w-77{width:77%}.w-78{width:78%}.w-79{width:79%}
.w-80{width:80%}.w-81{width:81%}.w-82{width:82%}.w-83{width:83%}.w-84{width:84%}.w-85{width:85%}.w-86{width:86%}.w-87{width:87%}.w-88{width:88%}.w-89{width:89%}
.w-90{width:90%}.w-91{width:91%}.w-92{width:92%}.w-93{width:93%}.w-94{width:94%}.w-95{width:95%}.w-96{width:96%}.w-97{width:97%}.w-98{width:98%}.w-99{width:99%}
.w-100{width:100%}

/* --- ring progress -------------------------------------------------------- */
/* Same reason as the bar widths above, same shape: one class per whole percent,
   each setting only the --ring-p custom property that .ring-arc's
   stroke-dashoffset is computed from. views/today.ejs prints the percent that
   src/routes/today.js has already clamped to 0-100. */
.ring-p-0{--ring-p:0}.ring-p-1{--ring-p:1}.ring-p-2{--ring-p:2}.ring-p-3{--ring-p:3}.ring-p-4{--ring-p:4}.ring-p-5{--ring-p:5}.ring-p-6{--ring-p:6}.ring-p-7{--ring-p:7}.ring-p-8{--ring-p:8}.ring-p-9{--ring-p:9}
.ring-p-10{--ring-p:10}.ring-p-11{--ring-p:11}.ring-p-12{--ring-p:12}.ring-p-13{--ring-p:13}.ring-p-14{--ring-p:14}.ring-p-15{--ring-p:15}.ring-p-16{--ring-p:16}.ring-p-17{--ring-p:17}.ring-p-18{--ring-p:18}.ring-p-19{--ring-p:19}
.ring-p-20{--ring-p:20}.ring-p-21{--ring-p:21}.ring-p-22{--ring-p:22}.ring-p-23{--ring-p:23}.ring-p-24{--ring-p:24}.ring-p-25{--ring-p:25}.ring-p-26{--ring-p:26}.ring-p-27{--ring-p:27}.ring-p-28{--ring-p:28}.ring-p-29{--ring-p:29}
.ring-p-30{--ring-p:30}.ring-p-31{--ring-p:31}.ring-p-32{--ring-p:32}.ring-p-33{--ring-p:33}.ring-p-34{--ring-p:34}.ring-p-35{--ring-p:35}.ring-p-36{--ring-p:36}.ring-p-37{--ring-p:37}.ring-p-38{--ring-p:38}.ring-p-39{--ring-p:39}
.ring-p-40{--ring-p:40}.ring-p-41{--ring-p:41}.ring-p-42{--ring-p:42}.ring-p-43{--ring-p:43}.ring-p-44{--ring-p:44}.ring-p-45{--ring-p:45}.ring-p-46{--ring-p:46}.ring-p-47{--ring-p:47}.ring-p-48{--ring-p:48}.ring-p-49{--ring-p:49}
.ring-p-50{--ring-p:50}.ring-p-51{--ring-p:51}.ring-p-52{--ring-p:52}.ring-p-53{--ring-p:53}.ring-p-54{--ring-p:54}.ring-p-55{--ring-p:55}.ring-p-56{--ring-p:56}.ring-p-57{--ring-p:57}.ring-p-58{--ring-p:58}.ring-p-59{--ring-p:59}
.ring-p-60{--ring-p:60}.ring-p-61{--ring-p:61}.ring-p-62{--ring-p:62}.ring-p-63{--ring-p:63}.ring-p-64{--ring-p:64}.ring-p-65{--ring-p:65}.ring-p-66{--ring-p:66}.ring-p-67{--ring-p:67}.ring-p-68{--ring-p:68}.ring-p-69{--ring-p:69}
.ring-p-70{--ring-p:70}.ring-p-71{--ring-p:71}.ring-p-72{--ring-p:72}.ring-p-73{--ring-p:73}.ring-p-74{--ring-p:74}.ring-p-75{--ring-p:75}.ring-p-76{--ring-p:76}.ring-p-77{--ring-p:77}.ring-p-78{--ring-p:78}.ring-p-79{--ring-p:79}
.ring-p-80{--ring-p:80}.ring-p-81{--ring-p:81}.ring-p-82{--ring-p:82}.ring-p-83{--ring-p:83}.ring-p-84{--ring-p:84}.ring-p-85{--ring-p:85}.ring-p-86{--ring-p:86}.ring-p-87{--ring-p:87}.ring-p-88{--ring-p:88}.ring-p-89{--ring-p:89}
.ring-p-90{--ring-p:90}.ring-p-91{--ring-p:91}.ring-p-92{--ring-p:92}.ring-p-93{--ring-p:93}.ring-p-94{--ring-p:94}.ring-p-95{--ring-p:95}.ring-p-96{--ring-p:96}.ring-p-97{--ring-p:97}.ring-p-98{--ring-p:98}.ring-p-99{--ring-p:99}
.ring-p-100{--ring-p:100}

/* --- iOS install hint ----------------------------------------------------- */
/* Shown by public/app.js only to an iPhone visitor in Safari who has not
   installed the app and has not dismissed this before (localStorage). Quiet by
   design: it is a note about where the share sheet is, not a prompt. */
.install-hint {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  margin-bottom: 14px;
  padding: 12px 14px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--wash);
  color: var(--ink-muted);
  font-size: 14px;
}

.install-hint-body {
  margin: 0;
  flex: 1;
  line-height: 1.6;
}

.install-hint-close {
  flex: none;
  margin: 0;
}

/* --- legal pages ---------------------------------------------------------- */
/* Long-form reading, so the type is a little larger and the sections are spaced
   far enough apart to scan on a phone. */
.legal {
  font-size: 15px;
}

.legal h1 {
  margin-bottom: 4px;
}

.legal h2 {
  margin: 28px 0 8px;
  font-size: 17px;
}

.legal h3 {
  margin: 18px 0 6px;
  font-size: 15px;
  color: var(--ink-muted);
}

.legal p {
  margin: 0 0 12px;
}

.legal-date {
  color: var(--ink-muted);
  font-size: 13px;
}

.legal .plain-list {
  margin: 0 0 12px;
}

.legal .answer-list {
  margin: 0 0 12px;
}

/* Links in prose are ink and underlined. There is no accent left to spend on
   them, and an underline is what says "link" anyway. */
.legal a {
  color: var(--ink);
  font-weight: 600;
}

/* The 特商法 table. A description list rather than a <table>: on a 390pt screen
   a two-column table either wraps badly or scrolls, and every row here is one
   label and one value. */
.legal-table {
  margin: 18px 0 0;
  border-top: 1px solid var(--line);
}

.legal-table dt {
  padding: 12px 0 0;
  font-weight: 700;
  font-size: 14px;
  color: var(--ink-muted);
}

.legal-table dd {
  margin: 2px 0 0;
  padding: 0 0 12px;
  border-bottom: 1px solid var(--line);
  color: var(--ink);
}
