@font-face {
  font-family: 'DM Sans';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/static/fonts/dm-sans-400.woff2') format('woff2');
}
@font-face {
  font-family: 'DM Sans';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('/static/fonts/dm-sans-500.woff2') format('woff2');
}
@font-face {
  font-family: 'DM Sans';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('/static/fonts/dm-sans-700.woff2') format('woff2');
}
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/static/fonts/jetbrains-mono-400.woff2') format('woff2');
}
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('/static/fonts/jetbrains-mono-600.woff2') format('woff2');
}

:root {
  --cream: #FAF7F2;
  --charcoal: #1C1C1C;
  --ink: #2D2D2D;
  --stone: #8B8680;
  --accent: #18181B;
  --accent-hover: #3F3F46;
  --rose: #EF4444;
  --white: #FFFFFF;
  --track: #EFECE5;
  --blue: #2563EB;

  /* Status colours. --rose is the alert colour everywhere, so there is no
     separate "bad" token: two names for one red is how they drift apart.
     --ok is the dot green, --ok-ink the darker step that stays readable as
     text on cream. */
  --ok: #15803D;
  --ok-ink: #166534;
  /* The middle step of the green ramp. Shared by the training heatmap and the
     public profile's bar charts, which is why it is a token and not a literal
     in two places. */
  --ok-mid: #4E9E68;
  --warn: #A16207;
  --flag-low: #D4A853;
  --flag-none: #D4D4D8;

  /* Sleep stage ramp, deep to light. Also the single bar colour on the
     training chart, which reuses --chart-deep on purpose: one bar needs no
     ramp, and a second blue would read as a second meaning. */
  --chart-deep: #1E40AF;
  --chart-rem: #3B82F6;
  --chart-light: #93C5FD;

  /* Borders. Three tiers of the same stone, weakest first: --line-soft
     separates rows inside a card, --line draws a control, --line-strong is
     for a chip or a box that has to read as a boundary on its own. */
  --line-soft: rgba(139, 134, 128, .1);
  --line: rgba(139, 134, 128, .2);
  --line-strong: rgba(139, 134, 128, .25);

  --radius-s: .75rem;
  --radius-m: 1rem;
  /* The card lift: a wide, soft, almost entirely negative-spread shadow, so a
     white card sits on cream without a visible edge. --shadow-modal is the
     same idea one storey up, for a dialog over a dimmed page. */
  --shadow-card: 0 14px 35px -30px rgba(28, 28, 28, .5);
  --shadow-modal: 0 30px 60px -30px rgba(28, 28, 28, .55);

  /* Spacing scale. Six steps, used for the layout rhythm: the column, the gaps
     between cards, the padding inside them. Older rules still carry their own
     odd values (.375, .625, .875, 1.25rem) inside components; those are left
     alone deliberately, because snapping them to the scale would move type and
     borders all over the app for no structural gain. New work uses the scale. */
  --s1: .25rem;
  --s2: .5rem;
  --s3: .75rem;
  --s4: 1rem;
  --s5: 1.5rem;
  --s6: 2rem;

  --font-sans: 'DM Sans', system-ui, -apple-system, 'Segoe UI', sans-serif;
  --font-mono: 'JetBrains Mono', ui-monospace, 'SF Mono', Menlo, monospace;
  /* One column width for the whole app. The top bar and the tab bar keep
     full-bleed backgrounds but their contents sit on this column, so the
     wordmark, the tabs and the cards share a left edge. Change it here only. */
  --col-width: 1140px;
  --col-gutter: 1rem;

  /* The three widths the layout is written for. They are stated here for the
     reader only: a media query cannot use a custom property. Small phone 480,
     phone 639/640, tablet 900. Nothing else in this file may invent a fourth. */
}

* { box-sizing: border-box; }
html { background: var(--cream); color: var(--ink); }
body { min-width: 0; margin: 0; font: 400 16px/1.5 var(--font-sans); }
button, input, select { font: inherit; }
button, a, input, select, summary { outline-offset: 3px; }
:focus-visible { outline: 3px solid var(--rose); }
a { color: var(--ink); }
p { margin: 0 0 .625rem; }
h1, h2, h3 { color: var(--charcoal); font-family: var(--font-sans); }
h1, h2 { margin: 0 0 1rem; font-size: 1.25rem; font-weight: 600; }
h3 { margin: 1.25rem 0 .625rem; font-size: .75rem; letter-spacing: .025em; text-transform: uppercase; }
.mono, .stat-trio strong, .metric-copy span:last-child, .feed-values, .tap, table { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
.muted { color: var(--stone); }
.small { font-size: .8125rem; }
.row { display: flex; align-items: center; }
.between { justify-content: space-between; }
.gap { gap: .625rem; flex-wrap: wrap; }
.inline { display: inline; margin: 0; }
.stack { display: flex; flex-direction: column; gap: var(--s3); }
.stack.tight { margin-top: var(--s3); }

.topbar { position: sticky; top: 0; z-index: 40; background: var(--charcoal); }
.topbar-inner { display: flex; padding-block: .75rem; align-items: center; justify-content: space-between; gap: .75rem; }
.brand { flex: none; display: inline-flex; align-items: center; gap: .45em; color: var(--white); font-size: 1.25rem; font-weight: 700; letter-spacing: -.02em; text-decoration: none; }
.brand-mark { display: block; width: 1.15em; height: 1.15em; }
.brand-word { display: inline-block; }
/* One red for the whole site. --rose (#EF4444) is the single value that stays legible
   on the charcoal bar and on cream, so the bar no longer needs a lighter step
   of its own and every surface carries the same accent. */
.brand-age { color: var(--rose); }
.topnav { display: flex; min-width: 0; align-items: center; justify-content: flex-end; gap: .75rem; }
.sync-control { display: flex; min-width: 0; align-items: center; gap: var(--s2); }
.white-pill { flex: none; padding: .5rem .75rem; border: 0; border-radius: var(--radius-m); background: var(--white); color: var(--ink); font-size: .75rem; font-weight: 500; cursor: pointer; }
.top-sync-status { display: flex; min-width: 0; max-width: 23rem; align-items: center; gap: .375rem; color: var(--stone); font-size: .7rem; line-height: 1.2; }
.sync-text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.signout { padding: 0; border: 0; background: none; color: var(--stone); cursor: pointer; font-size: .875rem; }
.signout:hover { color: var(--cream); }
.sync-dot { display: inline-block; width: .5rem; height: .5rem; flex: none; border-radius: 50%; background: var(--stone); }
.sync-dot.ok { background: var(--ok); }
.sync-dot.bad { background: var(--rose); }
.sync-dot.busy, .sync-dot.warn { background: var(--warn); }

.tabbar { border-bottom: 1px solid var(--line-soft); background: var(--cream); }
.tabbar-inner { display: flex; align-items: center; justify-content: space-between; gap: 1rem; }
/* The tab strip scrolls sideways when it does not fit, and an overflow with no
   edge treatment reads as a strip that simply ends. These four background
   layers are the standard self-hiding scroll shadow: the two soft shadows are
   attached to the box, the two cream covers scroll with the content and are
   painted over them. Scrolled to the start, the left cover sits exactly on the
   left shadow and hides it; scroll away and the shadow appears. Nothing has to
   measure anything, and a strip that fits shows neither.
   The transparent stop is written out in full rather than as `transparent`,
   which some engines interpolate through transparent black and turn into a
   grey smear. */
.tabs {
  display: flex;
  min-width: 0;
  gap: 1.5rem;
  overflow-x: auto;
  background-image:
    linear-gradient(to right, var(--cream) 55%, rgba(250, 247, 242, 0)),
    linear-gradient(to left, var(--cream) 55%, rgba(250, 247, 242, 0)),
    linear-gradient(to right, rgba(28, 28, 28, .14), rgba(28, 28, 28, 0)),
    linear-gradient(to left, rgba(28, 28, 28, .14), rgba(28, 28, 28, 0));
  background-position: left center, right center, left center, right center;
  background-size: 2rem 100%, 2rem 100%, 1.25rem 100%, 1.25rem 100%;
  background-repeat: no-repeat;
  background-attachment: local, local, scroll, scroll;
}
.tabs a { padding: .75rem .25rem; border-bottom: 2px solid transparent; color: var(--ink); font-weight: 500; text-decoration: none; white-space: nowrap; }
.tabs a:hover { color: var(--charcoal); }
.tabs a.active { border-bottom-color: var(--rose); color: var(--rose); }
.daybar { display: flex; flex: none; align-items: center; gap: .375rem; font-size: .75rem; }
.daynav { display: grid; width: 1.75rem; height: 1.75rem; place-items: center; border: 1px solid var(--line); border-radius: 50%; text-decoration: none; }
.daynav.disabled { color: var(--stone); }
.daylabel { display: flex; flex-direction: column; text-align: center; white-space: nowrap; }
.daylabel small { color: var(--stone); font-size: .625rem; }

/* The single shared column. Everything that has to line up uses this rule. */
.topbar-inner, .tabbar-inner, .wrap, .foot {
  width: min(calc(100% - var(--col-gutter) * 2), var(--col-width));
  margin-inline: auto;
}
.wrap { display: flex; flex-direction: column; gap: var(--s5); padding-block: var(--s5) 3rem; }
.foot { padding-bottom: var(--s6); color: var(--stone); font-size: .75rem; }
.card { min-width: 0; padding: 1.25rem; border: 0; border-radius: var(--radius-m); background: var(--white); box-shadow: var(--shadow-card); }
.card.narrow { width: min(100%, 25rem); margin: 3.5rem auto; }
.card-heading { display: flex; align-items: baseline; justify-content: space-between; gap: var(--s4); }
.body-age-card { position: relative; }

.stat-trio { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); padding: .75rem 0; border-radius: .75rem; background: var(--cream); }
.stat-trio.two-stats { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.stat-trio > div { min-width: 0; padding-inline: .75rem; text-align: center; }
.stat-trio > div + div { border-left: 1px solid var(--line-soft); }
.stat-trio strong { display: block; overflow-wrap: anywhere; color: var(--charcoal); font-size: 2rem; font-weight: 600; line-height: 1.2; }
.stat-trio span { display: block; margin-top: .25rem; color: var(--stone); font-size: .75rem; font-weight: 500; letter-spacing: .025em; text-transform: uppercase; }
.body-age-copy { margin: 1rem 0 0; }
.how { margin-top: 1rem; border-top: 1px solid var(--line-soft); padding-top: .75rem; }
.how-summary, summary { color: var(--ink); cursor: pointer; font-weight: 500; }
.how-body { padding-top: .875rem; color: var(--stone); font-size: .875rem; }

.metric-list { display: flex; flex-direction: column; gap: 1rem; }
.metric-copy { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem; }
.metric-copy span:last-child { text-align: right; font-size: .8125rem; }
.progress { width: 100%; height: 4px; margin-top: .375rem; overflow: hidden; border-radius: 9999px; background: var(--track); }
.progress span { display: block; height: 100%; border-radius: inherit; background: var(--blue); }
.checkin { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1.5rem; margin-top: 1.5rem; padding-top: 1.25rem; border-top: 1px solid var(--line-soft); }
.tap-row { display: flex; align-items: center; gap: .625rem; }
.tap-label { width: 3rem; color: var(--stone); font-size: .8125rem; }
.segments { display: grid; flex: 1; grid-template-columns: repeat(5, minmax(0, 1fr)); }
.tap { width: 100%; min-height: 2.25rem; padding: .375rem; border: 1px solid var(--line); border-radius: 0; background: var(--white); color: var(--ink); cursor: pointer; }
.segments form:not(:first-child) .tap { border-left: 0; }
.segments form:first-child .tap { border-radius: .5rem 0 0 .5rem; }
.segments form:last-child .tap { border-radius: 0 .5rem .5rem 0; }
.tap.on { border-color: var(--charcoal); background: var(--charcoal); color: var(--white); }

.add-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1rem; align-items: start; }
.add-details { min-width: 0; }
.add-details > summary { padding: 1.25rem; border: 1px dashed var(--line); border-radius: 1rem; background: var(--white); text-align: center; list-style: none; }
.add-details > summary::-webkit-details-marker { display: none; }
.add-details > summary:hover { border-color: var(--charcoal); }
.add-details[open] { grid-column: 1 / -1; }
.add-panel { margin-top: 1rem; }
.dropzone { padding: 1.5rem .875rem; border: 1px dashed var(--line); border-radius: .75rem; background: var(--cream); text-align: center; }
.dropzone.dragging { border-color: var(--rose); }
.dz-text { margin: 0; color: var(--stone); font-size: .875rem; }
.preview { max-width: 100%; max-height: 13.75rem; margin-top: .75rem; border-radius: .75rem; }

label, .form-label { display: block; color: var(--stone); font-size: .8125rem; font-weight: 500; }
input[type='text'], input[type='email'], input[type='password'], input[type='number'], input[type='date'], input[type='time'], select { width: 100%; min-width: 0; margin-top: .3125rem; padding: .5625rem .625rem; border: 1px solid var(--line); border-radius: .75rem; background: var(--white); color: var(--ink); }
label.check { display: flex; align-items: center; gap: .5rem; color: var(--ink); font-weight: 400; }
label.check input { width: auto; }
button { cursor: pointer; }
button:disabled { cursor: not-allowed; opacity: .5; }
button.primary { width: fit-content; padding: .625rem 1rem; border: 0; border-radius: .75rem; background: var(--accent); color: var(--white); font-size: .875rem; font-weight: 500; }
button.primary:hover { background: var(--accent-hover); }
button.secondary, .chip-button { padding: .5rem .875rem; border: 1px solid var(--line); border-radius: .75rem; background: var(--white); color: var(--stone); font-weight: 500; }
button.secondary:hover, .chip-button:hover { border-color: var(--charcoal); color: var(--charcoal); }
button.danger { padding: .5rem .875rem; border: 1px solid var(--rose); border-radius: .75rem; background: var(--white); color: var(--rose); }
button.linkish { padding: 0; border: 0; background: none; color: inherit; text-decoration: underline; }
.grid-form { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: .75rem; }
.form-actions { grid-column: 1 / -1; }
.repeat-list, .sync-list { margin: 0; padding: 0; list-style: none; }
.repeat-list { display: flex; flex-wrap: wrap; gap: .4375rem; }
.weight-form .row { margin-top: .3125rem; flex-wrap: nowrap; }
.weight-form input { margin: 0; }

.feed { display: flex; flex-direction: column; }
.feed-row { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: .75rem 1rem; padding: 1rem 0; border-top: 1px solid var(--line-soft); }
.feed-row:first-child { border-top: 0; }
.feed-copy, .feed-values { display: flex; flex-direction: column; min-width: 0; }
.feed-values { align-items: flex-end; font-size: .8125rem; text-align: right; }
.feed-edit { grid-column: 1 / -1; }
.meal-photo { width: 4rem; height: 4rem; margin-top: .5rem; border-radius: .75rem; object-fit: cover; }

.trend-card { padding: 1.25rem; }
.chart-wrap { position: relative; }
.trend-chart { display: block; width: 100%; height: auto; }
.chart-axis { stroke: var(--line); stroke-width: 1; }
.chart-label { fill: var(--stone); font: 10px var(--font-mono); }
/* The goal line's own label. It sits in the bar chart's left gutter, which is
   62 viewBox units wide, the same gutter the line chart uses for its value
   labels: four mono characters need about 24 units at the authored size and
   about 41 at the phone size, so it clears the axis at both. Dimmed so it
   reads as an annotation on the goal line rather than as data.
   Longhands, not the `font` shorthand, and stated here beside .chart-label
   rather than lower down with the bar chart colours: the shorthand resets
   font-size, and a base rule placed after the 639px block would win on source
   order alone and defeat the phone bump. Both labels are now declared before
   that block, so the bump applies to both. */
.chart-goal-label { fill: var(--stone); font-family: var(--font-mono); font-size: 10px; opacity: .75; }
.trend-area { fill: var(--blue); opacity: .08; }
.trend-line { stroke: var(--blue); stroke-width: 3; stroke-linecap: round; stroke-linejoin: round; vector-effect: non-scaling-stroke; }
.trend-point { fill: transparent; }
.chart-crosshair { stroke: var(--stone); stroke-width: 1; stroke-dasharray: 3 3; }
.chart-tooltip { position: absolute; top: 2px; padding: .375rem .5rem; border: 1px solid var(--line); border-radius: .375rem; background: var(--white); box-shadow: var(--shadow-card); font: .75rem var(--font-mono); pointer-events: none; white-space: nowrap; }
.data-table { margin-top: .5rem; }
.table-scroll { overflow-x: auto; }
table { width: 100%; margin-top: .625rem; border-collapse: collapse; font-size: .75rem; }
th, td { padding: .4375rem .625rem; border-bottom: 1px solid var(--line-soft); text-align: left; }
.empty-state { color: var(--stone); }
.sync-summary { display: flex; align-items: center; gap: .5rem; }
.sync-list { display: flex; flex-direction: column; gap: .625rem; margin-block: .875rem; }
.sync-item { display: flex; align-items: flex-start; gap: .5625rem; }
.sync-item .sync-dot { margin-top: .375rem; }
.sync-item p { margin: .125rem 0; }
.notice { padding: .5625rem .6875rem; border-radius: .75rem; background: var(--track); font-size: .875rem; }
.notice.bad { color: var(--rose); }
.notice.good { color: var(--ok-ink); }

@media (min-width: 640px) {
  /* No padding step here any more: the bars ride the shared column, and an
     inset would push the wordmark and the tabs off the cards' left edge. */
  .white-pill { font-size: .875rem; }
  .card, .add-details > summary { padding: var(--s5); }
}

@media (max-width: 639px) {
  /* Two rows: the wordmark alone, then everything that acts. One row could not
     hold them. At 390px the pill, the status line, Admin and Sign out came to
     more than the column is wide, and a nowrap flex row answers that by
     pushing the wordmark off the left edge. Both children are flex: 1 0 100%,
     which is what forces the break, so nothing depends on wrapping order. */
  .topbar-inner { flex-wrap: wrap; gap: .375rem .75rem; padding-block: .625rem; }
  .brand { flex: 1 0 100%; }
  .topnav { flex: 1 0 100%; justify-content: flex-start; gap: .875rem; }
  /* The status line is back on a phone, where it now has a row to sit in. It
     takes the leftover width and truncates rather than pushing Sign out out of
     reach; the same words are on the Settings tab in full. */
  .top-sync-status { flex: 1 1 auto; max-width: none; }
  .stat-trio strong { font-size: 1.35rem; }
  .stat-trio span { font-size: .625rem; }
  .checkin { grid-template-columns: 1fr; gap: .75rem; }
  .add-grid, .grid-form { grid-template-columns: 1fr; }
  .form-actions, .add-details[open] { grid-column: auto; }

  /* The day arrows get their own row instead of competing with the tabs for
     the same line. Squeezed beside them they were the first thing to overflow,
     and they are the control most likely to be tapped by mistake. */
  .tabbar-inner { align-items: stretch; flex-direction: column; gap: 0; }
  .daybar { align-self: flex-end; padding-bottom: var(--s2); }

  /* Charts are drawn in a 680 unit viewBox. Across a phone that box renders at
     roughly 350px, so every label in it is drawn at about half the size it was
     authored at. 17 units lands back near 9px on the glass. Desktop keeps the
     10 unit size: there the same box renders wider than it is authored, and a
     bumped label would come out oversized. */
  .chart-label, .chart-goal-label { font-size: 17px; }

  /* The feedback pill sits over the content on every page. On a phone it
     overlaps a card whatever it does, so it shrinks, and it stays slightly
     transparent until it is reached for. min-height keeps the tap target at
     44px, which is where the padding alone would no longer put it. */
  .fb-widget { right: var(--s3); bottom: var(--s3); }
  .fb-launcher {
    display: flex;
    min-height: 2.75rem;
    align-items: center;
    padding: .5rem .875rem;
    font-size: .8125rem;
    opacity: .82;
    transition: opacity .12s ease;
  }
  .fb-launcher:hover, .fb-launcher:focus-visible, .fb-launcher[aria-expanded='true'] { opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  .fb-launcher { transition: none; }
}

/* Small phone. Everything here used to sit at 420px, which no current handset
   is: a 390 iPhone got it and a 430 Pro Max did not, from the same hand. 480
   covers the whole large-phone range instead. */
@media (max-width: 480px) {
  .card-heading { align-items: flex-start; flex-direction: column; gap: 0; }
  /* The Body Age card used to clear an absolutely positioned element up here
     with 3.5rem of top padding. Nothing sits there now, and the card has to
     stay short, so the reserved strip is gone. */
  .stat-trio > div { padding-inline: .35rem; }
  .metric-copy { align-items: flex-start; flex-direction: column; gap: 0; }
  .feed-row { grid-template-columns: 1fr; }
  .feed-values { align-items: flex-start; text-align: left; }
  .feed-edit { grid-column: auto; }
}

.guide-tag { color: var(--stone); font-size: .75rem; }
.notice.warn { color: var(--warn); }
.kpi-empty { color: var(--stone); font-size: .875rem; }
.integrations { display: flex; flex-direction: column; gap: .875rem; margin-top: .5rem; }
.integration-row { display: flex; align-items: flex-start; gap: .625rem; }
.integration-row .sync-dot { margin-top: .4rem; }
.integration-row p { margin: .125rem 0 0; }
.foot a { color: var(--stone); }
.integration-body { display: flex; flex-direction: column; gap: .375rem; align-items: flex-start; }
.small-btn { padding: .375rem .75rem; font-size: .8125rem; }
.btn-link { display: inline-block; text-decoration: none; border: 1px solid var(--line); border-radius: .75rem; padding: .375rem .75rem; color: var(--ink); font-size: .8125rem; }
.btn-link:hover { border-color: var(--charcoal); }
.invite-box { background: var(--cream); border-radius: .75rem; padding: 1rem; margin-top: 1rem; overflow-wrap: anywhere; }
a.signout { text-decoration: none; }
.chart-goal { stroke: var(--stone); stroke-width: 1; stroke-dasharray: 4 4; opacity: .5; }
.seg-deep { fill: var(--chart-deep); }
.seg-rem { fill: var(--chart-rem); }
.seg-light { fill: var(--chart-light); }
.chart-legend { display: flex; gap: 1.25rem; margin-top: .5rem; color: var(--stone); font-size: .8125rem; }
.legend-dot { display: inline-block; width: .5rem; height: .5rem; border-radius: 2px; margin-right: .375rem; }
.legend-dot.seg-deep { background: var(--chart-deep); } .legend-dot.seg-rem { background: var(--chart-rem); } .legend-dot.seg-light { background: var(--chart-light); }
.stage-strip { display: flex; height: 6px; border-radius: 3px; overflow: hidden; gap: 2px; margin-top: .5rem; max-width: 320px; }
.stage-strip span { display: block; height: 100%; }
.stage-strip .seg-deep { background: var(--chart-deep); } .stage-strip .seg-rem { background: var(--chart-rem); } .stage-strip .seg-light { background: var(--chart-light); }

/* Bar charts */
.bars-wrap { position: relative; }
.bar-hit { fill: transparent; }
.bar-group { cursor: default; outline: none; }
.bar-group rect:not(.bar-hit) { transition: opacity .12s ease; }
.bars-wrap:hover .bar-group:not(.is-on) rect:not(.bar-hit) { opacity: .38; }
.bar-group.is-on rect:not(.bar-hit) { opacity: 1; }
.bar-group:focus-visible .bar-hit { fill: rgba(37,99,235,.08); }
.bar-tooltip { position: absolute; top: 0; z-index: 2; background: var(--charcoal); color: var(--white); border-radius: .5rem; padding: .5rem .625rem; font-size: .75rem; line-height: 1.5; pointer-events: none; white-space: nowrap; box-shadow: 0 8px 20px -12px rgba(0,0,0,.6); }
.bar-tooltip strong { display: block; font-family: var(--font-mono); font-size: .6875rem; color: var(--rose); margin-bottom: .125rem; }
.bar-tooltip span { display: block; }

/* Today two-column */
.today-grid { display: grid; grid-template-columns: minmax(0, 5fr) minmax(0, 7fr); gap: 1.25rem; align-items: start; }
.today-grid > * { min-width: 0; }
/* Each column is a stack with the same gap as the grid, so the cards inside a
   column are spaced like the cards beside them. Nothing set this before and no
   card carries a margin, so two cards in the same column sat flush against
   each other and read as one tall card with a rule through it. */
.col-left, .col-right { display: flex; flex-direction: column; gap: 1.25rem; }

/* The Day. A full width card under the two columns, holding one list. Its
   heading takes the small uppercase treatment so it introduces the list
   without competing with the card headings above it, and the card is shorter
   in the vertical than a card that carries its own sections. */
.day-card { padding-block: var(--s4); }
.day-card > h2 { margin: 0 0 var(--s2); color: var(--stone); font-size: .75rem; font-weight: 500; letter-spacing: .025em; text-transform: uppercase; }
/* With nothing recorded the card was a full height white block saying so.
   One line on the page says the same thing and does not claim the room. */
.day-empty { margin: 0; padding: .625rem .875rem; border: 1px solid var(--line-soft); border-radius: var(--radius-s); background: var(--white); color: var(--stone); font-size: .875rem; }
.mini-chart-card h3 { font-size: .9375rem; margin: 0 0 .25rem; color: var(--charcoal); }
.mini-chart-card .muted.small { margin: 0 0 .5rem; }
@media (max-width: 900px) { .today-grid { grid-template-columns: 1fr; } }

/* Flip card. Both faces sit in the same grid cell, so the card is as tall as
   the taller of the two and the flip never changes its height. The hidden face
   is not just turned away, it is visibility: hidden, so it is out of the tab
   order and out of a screen reader. That switch is delayed past the halfway
   point of the turn, where the outgoing face is edge on and invisible anyway,
   because flipping it at once would blank the animation. The faces deliberately
   carry no backface-visibility: the visibility swap already hides the outgoing
   face, and if a browser ever flattens preserve-3d here, hiding backfaces would
   turn the flipped card blank instead of leaving a readable mirrored one. */
.flip-card { perspective: 1200px; }
.flip-faces { display: grid; transform-style: preserve-3d; transition: transform .45s ease; }
.flip-face { grid-area: 1 / 1; min-width: 0; visibility: hidden; transition: visibility 0s linear .22s; }
.flip-face > p:first-child { margin-top: 0; }
.flip-back { transform: rotateY(180deg); }
.flip-faces.is-flipped { transform: rotateY(180deg); }
.flip-faces:not(.is-flipped) > .flip-front { visibility: visible; }
.flip-faces.is-flipped > .flip-back { visibility: visible; }
.flip-toggle { flex: none; }

@media (prefers-reduced-motion: reduce) {
  /* No turn at all: the faces swap on the spot, and neither the transform nor
     the delayed visibility switch runs. */
  .flip-faces, .flip-face { transition: none; }
  .flip-faces.is-flipped, .flip-back { transform: none; }
}

/* ---------------------------------------------------------------------------
   Settings. Six cards in an order the owner set, which nothing here changes.
   What it does change is the density: slim labels group the cards into four
   named sections, and the forms stop stretching to the full 1140px column.
   --------------------------------------------------------------------------- */
/* .wrap puts 1.5rem between its children, which around a one line label is
   3rem of nothing. The negative bottom margin pulls the card that the label
   introduces up against it, so the label belongs to what follows rather than
   floating between two cards. */
.section-label { margin: var(--s2) 0 calc(var(--s3) - var(--s5)); color: var(--stone); font-size: .6875rem; font-weight: 500; letter-spacing: .08em; text-transform: uppercase; }
.section-label:first-of-type { margin-top: 0; }
.settings-card { padding-block: var(--s4); }
/* The card titles are h3 so the four .section-label h2s above them own the
   outline instead of sitting flat beside six more h2s. They must not look
   demoted, so the h3 here is restated as the h2 it renders as: the global h3
   rule is a small uppercase eyebrow, which is not what a card title is. */
.settings-card > h3 { margin: 0 0 var(--s3); font-size: 1.25rem; font-weight: 600; letter-spacing: normal; text-transform: none; }
/* A form is a column of controls, not a banner. Left to itself in this column
   a single text input was 1090px wide for a 30 character username. 46rem holds
   the two column grid at a sane field width; 34rem is the single column case.
   Neither binds on a phone, where the column is narrower than both. */
.settings-card form { max-width: 34rem; }
.settings-card .grid-form { max-width: 46rem; }
/* Help text under a field reads at the same measure as the field it explains
   rather than running the width of the page. */
.settings-card p { max-width: 46rem; }

/* Public profile card in settings. */
.profile-link { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem .75rem; margin: .875rem 0; }
.gate-status { margin: .5rem 0 0; color: var(--stone); font-size: .8125rem; }
.gate-status.good { color: var(--ok-ink); }

/* Food analysis card */
.food-strip { display: flex; gap: .5rem; margin: .75rem 0 0; }
.food-macro { flex: 1; background: var(--cream); border-radius: .75rem; padding: .625rem .75rem; }
.food-macro span { display: block; font-size: .6875rem; color: var(--stone); text-transform: uppercase; letter-spacing: .04em; }
.food-macro strong { font-family: var(--font-mono); font-size: 1.125rem; color: var(--charcoal); }
.meal-thumbs { display: flex; gap: .5rem; margin-top: .75rem; flex-wrap: wrap; }
.meal-thumbs img { width: 56px; height: 56px; object-fit: cover; border-radius: .5rem; }
.meal-thumbs .thumb-none { width: 56px; height: 56px; border-radius: .5rem; background: var(--cream); display: grid; place-items: center; font-size: .625rem; color: var(--stone); text-align: center; }

/* Health markers */
.marker-list { display: flex; flex-direction: column; }
.marker-row { display: grid; grid-template-columns: 1fr 80px auto; gap: 1rem; align-items: center; padding: .625rem 0; border-bottom: 1px solid var(--line-soft); }
.marker-row:last-child { border-bottom: 0; }
.marker-name { display: flex; align-items: center; gap: .5rem; font-size: .9375rem; }
.marker-dot { width: .5rem; height: .5rem; border-radius: 50%; background: var(--ok); flex: none; }
.flag-high .marker-dot, .flag-item.flag-high .marker-dot { background: var(--rose); }
.flag-low .marker-dot, .flag-item.flag-low .marker-dot { background: var(--flag-low); }
.flag-none .marker-dot { background: var(--flag-none); }
.marker-spark svg { width: 72px; height: 24px; display: block; }
.marker-spark polyline { stroke: var(--blue); stroke-width: 1.5; stroke-linejoin: round; stroke-linecap: round; }
.marker-value { text-align: right; white-space: nowrap; font-size: .875rem; }
.marker-value strong { font-size: 1rem; color: var(--charcoal); }
.marker-unit { color: var(--stone); margin-left: .25rem; font-size: .75rem; }
.marker-ref { display: block; color: var(--stone); font-size: .6875rem; }
.flag-list { margin-top: 1.25rem; border-top: 1px solid var(--line-soft); padding-top: 1rem; }
.flag-list h3 { font-size: .8125rem; text-transform: uppercase; letter-spacing: .06em; color: var(--stone); margin: 0 0 .625rem; }
.flag-item { display: flex; align-items: center; gap: .625rem; padding: .375rem 0; font-size: .9375rem; }
.flag-name { flex: 1; }
@media (max-width: 639px) { .marker-row { grid-template-columns: 1fr auto; } .marker-spark { display: none; } }

/* Health marker rows with inline history charts */
.marker-row { border-bottom: 1px solid var(--line-soft); }
.marker-row:last-child { border-bottom: 0; }
.marker-row > summary { display: grid; grid-template-columns: minmax(0, 1fr) 200px 150px; gap: 1.25rem; align-items: center; padding: .625rem 0; cursor: pointer; list-style: none; }
.marker-row > summary::-webkit-details-marker { display: none; }
.marker-row > summary:hover { background: rgba(250,247,242,.7); }
.marker-chart svg { width: 200px; height: 46px; display: block; }
.marker-band { fill: rgba(37,99,235,.07); }
/* One dot per reading. The latest one is a size step larger and carries a
   surface ring so it stays readable where dots crowd together. */
.marker-pt { fill: var(--blue); }
.marker-last { fill: var(--blue); stroke: var(--cream); stroke-width: 1.25; }
.marker-card:hover .marker-last { stroke: var(--white); }
.flag-high .marker-pt, .flag-high .marker-last { fill: var(--rose); }
.flag-low .marker-pt, .flag-low .marker-last { fill: var(--flag-low); }
.trend { display: block; font-family: var(--font-sans); font-size: .6875rem; }
.trend-better { color: var(--ok); }
.trend-worse { color: var(--rose); }
.marker-detail { padding: .25rem 0 1rem; }
.marker-facts { display: flex; flex-wrap: wrap; gap: 1rem; color: var(--stone); font-size: .8125rem; margin-bottom: .625rem; }
.single-list { display: flex; flex-wrap: wrap; gap: .5rem 1.5rem; }
.single-item { display: flex; gap: .5rem; font-size: .875rem; min-width: 15rem; }
.single-item .mono { color: var(--charcoal); }
.upload-panel { border: 1px dashed rgba(139,134,128,.35); border-radius: .75rem; padding: 1.25rem; text-align: center; }
.upload-panel:hover { border-color: var(--charcoal); }
@media (max-width: 639px) { .marker-row > summary { grid-template-columns: 1fr auto; } .marker-chart { display: none; } }

/* Marker grid, filters and modal */
.filter-row { display: flex; flex-wrap: wrap; gap: .5rem; align-items: center; margin: 0 0 1.25rem; }
.chip { background: var(--white); border: 1px solid var(--line-strong); border-radius: 999px; padding: .375rem .875rem; font: inherit; font-size: .8125rem; color: var(--ink); cursor: pointer; }
.chip span { color: var(--stone); margin-left: .25rem; }
.chip:hover { border-color: var(--charcoal); }
.chip.is-on { background: var(--accent); border-color: var(--accent); color: var(--white); }
.chip.is-on span { color: rgba(255,255,255,.65); }
.marker-search { flex: 1; min-width: 12rem; border: 1px solid var(--line-strong); border-radius: 999px; padding: .375rem .875rem; font: inherit; font-size: .8125rem; }
.marker-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); gap: .875rem; margin-top: .75rem; }
.marker-card { display: flex; flex-direction: column; gap: .375rem; text-align: left; background: var(--cream); border: 1px solid transparent; border-left: 3px solid var(--ok); border-radius: .75rem; padding: .875rem; font: inherit; cursor: pointer; }
.marker-card:hover { border-color: rgba(139,134,128,.35); border-left-color: var(--ok); background: var(--white); }
.marker-card.flag-high { border-left-color: var(--rose); } .marker-card.flag-high:hover { border-left-color: var(--rose); }
.marker-card.flag-low { border-left-color: var(--flag-low); } .marker-card.flag-low:hover { border-left-color: var(--flag-low); }
.marker-card.flag-none { border-left-color: var(--flag-none); } .marker-card.flag-none:hover { border-left-color: var(--flag-none); }
.marker-card-head { display: flex; justify-content: space-between; align-items: baseline; gap: .5rem; }
.marker-card .marker-name { font-size: .875rem; color: var(--ink); gap: 0; }
.marker-card .marker-dot { display: none; }
.marker-card-value strong { font-size: 1.5rem; color: var(--charcoal); }
/* height follows the 240x60 viewBox. The chart draws circles now, so the
   aspect ratio has to stay uniform or the dots come out as ellipses. */
.marker-card-chart svg { width: 100%; height: auto; display: block; }
.marker-card-foot { display: flex; justify-content: space-between; color: var(--stone); font-size: .6875rem; }
.headline-list { margin-top: 1.25rem; border-top: 1px solid var(--line-soft); padding-top: 1rem; display: flex; flex-direction: column; gap: .5rem; }
.headline-list p { margin: 0; font-size: .9375rem; display: flex; align-items: center; gap: .5rem; }
.headline-list .marker-dot { display: inline-block; }
.marker-dot.flag-high { background: var(--rose); } .marker-dot.flag-low { background: var(--flag-low); }
.marker-modal { border: 0; border-radius: 1rem; padding: 1.75rem; max-width: min(94vw, 820px); width: 100%; background: var(--white); color: var(--ink); box-shadow: var(--shadow-modal); }
.marker-modal::backdrop { background: rgba(28,28,28,.45); }
.modal-close-form { display: flex; justify-content: flex-end; }
.modal-close { background: none; border: 1px solid var(--line-strong); border-radius: .5rem; padding: .25rem .625rem; font: inherit; font-size: .8125rem; cursor: pointer; color: var(--stone); }
.modal-close:hover { border-color: var(--charcoal); color: var(--ink); }
.modal-title { margin: .25rem 0 .25rem; color: var(--charcoal); font-size: 1.375rem; }
.modal-sub { margin: 0 0 1rem; }
.modal-chart svg { width: 100%; height: auto; }
.modal-band { fill: rgba(37,99,235,.07); }
.modal-hit { fill: transparent; }
.modal-pt { fill: var(--blue); }
.modal-pt.is-last { stroke: var(--white); stroke-width: 2; }
.modal-dot:hover .modal-pt { fill: var(--charcoal); }
/* The chart scales with the dialog, so on a phone everything in it shrinks by
   more than half. A line survives that; a dot turns into a speck. Scale the
   marks and the labels back up in viewBox units. r is a CSS geometry property
   in current browsers, and where it is not the r attribute still applies. */
@media (max-width: 639px) {
  .modal-pt { r: 7; }
  .modal-pt.is-last { r: 9; stroke-width: 3; }
  .modal-chart .modal-point-label { font-size: 17px; }
  .modal-chart .chart-label { font-size: 16px; }
}
.modal-point-label { font-family: var(--font-mono); font-size: 11px; fill: var(--stone); }
.modal-table { margin-top: 1rem; max-height: 220px; overflow-y: auto; }
.chart-note { margin: 0 0 var(--s2); }
/* No max-height here any more. An SVG with width:100% and height:auto takes
   its height from the viewBox; capping that height did not crop the chart, it
   made the box wider than the drawing and preserveAspectRatio centred a 680
   unit chart inside an 1140 unit card, with a quarter of the card empty on
   each side. The chart fills the card and is as tall as its own ratio. */

/* The stat band above a Trends chart. Two or three figures the chart cannot
   state on its own: where the series ended up, and how far it moved. Same
   cream box and mono-over-uppercase pairing as the summary band on Health,
   one size step down, because here it introduces a chart rather than being
   the content of the card. */
.chart-stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(5.5rem, 1fr)); margin: 0 0 var(--s4); padding: .625rem 0; border-radius: var(--radius-s); background: var(--cream); }
.chart-stats > div { min-width: 0; padding-inline: var(--s3); text-align: center; }
.chart-stats > div + div { border-left: 1px solid var(--line-soft); }
.chart-stats strong { display: block; overflow-wrap: anywhere; color: var(--charcoal); font-family: var(--font-mono); font-variant-numeric: tabular-nums; font-size: 1.25rem; font-weight: 600; line-height: 1.2; }
.chart-stats span { display: block; margin-top: .125rem; color: var(--stone); font-size: .625rem; font-weight: 500; letter-spacing: .04em; text-transform: uppercase; }

/* Body Age card: headline numbers and their progression -------------------
   Two panels side by side, Body Age left and Aging Speed right. Separate
   charts, not two series in one, because a number around 33 and a number
   around 1.08 share no scale. Side by side also keeps the card shorter than
   the version it replaces, which is the point: the Today column has to be
   readable in one go. */
.ba-panels { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1rem; }
.ba-panel { display: flex; min-width: 0; flex-direction: column; padding: .75rem 1rem; border-radius: .75rem; background: var(--cream); }
.ba-figures { display: flex; flex-wrap: wrap; gap: .5rem 1.5rem; }
.ba-figure { min-width: 0; }
/* These numbers are the whole promise of the product, so they are the largest
   type in the app. JetBrains Mono at 600, because 400 and 600 are the two
   weights that ship as real faces here and a synthesised 700 would smear. */
.ba-num {
  display: block;
  overflow-wrap: anywhere;
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: clamp(2rem, 4.2vw, 3.25rem);
  font-weight: 600;
  line-height: 1;
  letter-spacing: -.03em;
}
.ba-num-age { color: var(--rose); }
.ba-num-speed, .ba-num-delta { color: var(--blue); }
.ba-figure span { display: block; margin-top: .375rem; color: var(--stone); font-size: .6875rem; font-weight: 500; letter-spacing: .025em; text-transform: uppercase; }
/* margin-top:auto pins both charts to the bottom of their panel, so they line
   up across the two even when one set of labels wraps to a second line. */
.ba-chart { margin-top: auto; padding-top: .625rem; }
.ba-explain { text-decoration: none; white-space: nowrap; }
.ba-explain:hover { color: var(--ink); text-decoration: underline; }
.ba-note { margin: .75rem 0 0; }

/* A dot strip: fixed 34px tall at every width rather than an SVG whose height
   grows with the column. Dots are absolutely positioned, percent across for
   the date and pixels down for the value, at a fixed pixel size so they stay
   round no matter how wide the panel gets. */
.prog-plot { position: relative; display: block; min-width: 0; height: 34px; }
/* Inset by the radius of the largest dot, so the first and last sit fully
   inside the strip instead of hanging over the edge. */
.prog-track { position: absolute; inset: 0 4px; }
.prog-dot { position: absolute; width: 4.5px; height: 4.5px; margin: -2.25px 0 0 -2.25px; border-radius: 50%; background: var(--blue); }
.prog-dot.is-last { width: 8px; height: 8px; margin: -4px 0 0 -4px; box-shadow: 0 0 0 1.5px var(--cream); }
.prog-anchor { position: absolute; left: 0; right: 0; height: 0; border-top: 1px dashed rgba(139, 134, 128, .55); }
.prog-foot { display: flex; justify-content: space-between; gap: .5rem; margin: .25rem 0 0; color: var(--stone); font-family: var(--font-mono); font-size: .625rem; }
.prog-note { font-family: var(--font-sans); }

/* On a phone the panels stack, because two 150px wide strips of 60 daily dots
   are an unreadable smudge. Stacked, the two cream boxes would cost a second
   set of padding for nothing, so they merge into one box and the panels inside
   it go transparent. The strip also gets shorter, which the percent based dot
   offsets allow without touching the markup. */
@media (max-width: 639px) {
  .ba-panels { grid-template-columns: 1fr; gap: .875rem; padding: .875rem 1rem; border-radius: var(--radius-s); background: var(--cream); }
  .ba-panel { padding: 0; background: none; }
  .ba-chart { padding-top: .5rem; }
  .ba-num { font-size: 1.875rem; }
  .ba-figures { gap: .375rem 1.25rem; }
  .prog-plot { height: 26px; }
  /* Stacked in one box the two strips share an axis, so the window dates are
     stated once, under the lower of the two. */
  .ba-panel:first-child .prog-foot { display: none; }
}

@media (max-width: 480px) {
  /* This card keeps its heading on one line. The link is short, and a second
     line here costs more height than it is worth on a phone. */
  .body-age-card .card-heading { align-items: baseline; flex-direction: row; gap: .75rem; }
  .prog-note { display: none; }
  .prog-dot { width: 3.5px; height: 3.5px; margin: -1.75px 0 0 -1.75px; }
  .prog-dot.is-last { width: 7px; height: 7px; margin: -3.5px 0 0 -3.5px; }
}

/* ---------------------------------------------------------------------------
   Feedback widget. Fixed bottom right on the signed-in app pages only (the
   layout gates it); never on sign-in, the paywall or an error page.
   --------------------------------------------------------------------------- */
.sr-only { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; border: 0; }

/* Above the sticky top bar (z-index 40) and the tab bar, below a native
   <dialog>, which the browser paints on its own layer. */
.fb-widget { position: fixed; right: 1rem; bottom: 1rem; z-index: 60; display: flex; flex-direction: column; align-items: flex-end; gap: .625rem; }
.fb-widget[hidden] { display: none; }
.fb-launcher { padding: .625rem 1rem; border: 0; border-radius: .75rem; background: var(--charcoal); color: var(--white); font-size: .875rem; font-weight: 500; box-shadow: 0 14px 30px -16px rgba(28, 28, 28, .75); }
.fb-launcher:hover { background: var(--accent-hover); }

/* 340px on a desk, and never wider than the viewport minus the two gutters,
   so a 390px phone keeps its 1rem margin on both sides. */
/* The panel floats over the white cards, so the card shadow alone leaves its
   edge invisible. A hairline ring in the app's own border colour draws the
   boundary and the drop shadow does the lifting. */
.fb-panel { width: min(calc(100vw - 2rem), 340px); padding: 1.125rem; border-radius: 1rem; background: var(--white); box-shadow: 0 0 0 1px var(--line), 0 24px 48px -24px rgba(28, 28, 28, .5); animation: fb-rise .12s ease-out; }
@keyframes fb-rise { from { opacity: 0; transform: translateY(.375rem); } to { opacity: 1; transform: none; } }
.fb-head { display: flex; align-items: center; justify-content: space-between; gap: .75rem; }
.fb-head h2 { margin: 0; font-size: 1rem; }
.fb-close { display: grid; flex: none; width: 1.75rem; height: 1.75rem; place-items: center; padding: 0; border: 0; border-radius: .5rem; background: none; color: var(--stone); }
.fb-close:hover { background: var(--track); color: var(--charcoal); }
.fb-intro { margin: .5rem 0 .75rem; color: var(--stone); font-size: .8125rem; }
.fb-panel textarea { display: block; width: 100%; min-height: 5.5rem; padding: .5625rem .625rem; border: 1px solid var(--line); border-radius: .75rem; background: var(--white); color: var(--ink); font: inherit; font-size: .875rem; resize: vertical; }
.fb-panel textarea::placeholder { color: var(--stone); }
/* Overrides button.primary's fit-content width, which needs the extra
   specificity to win. In a 340px panel the send button is the one action, so
   it spans the panel rather than sitting as a small tab in the corner. */
.fb-panel .fb-send { width: 100%; margin-top: .75rem; }
.fb-status { margin: .75rem 0 0; font-size: .8125rem; }
.fb-status.good { color: var(--ok-ink); }
.fb-status.bad { color: var(--rose); }

@media (prefers-reduced-motion: reduce) {
  .fb-panel { animation: none; }
}

/* The admin feedback log. Free text, so it wraps and preserves the line
   breaks the sender typed rather than living in a table cell. */
.fb-log { margin: .75rem 0 0; padding: 0; list-style: none; display: flex; flex-direction: column; gap: .875rem; }
.fb-log li { padding-top: .875rem; border-top: 1px solid var(--line-soft); }
.fb-log li:first-child { padding-top: 0; border-top: 0; }
.fb-log-meta { margin: 0 0 .25rem; }
.fb-log-body { margin: 0; overflow-wrap: anywhere; white-space: pre-wrap; }

/* ---------------------------------------------------------------------------
   Agent access (MCP). Shared by the Settings card and the public /docs/mcp
   page, so a setup snippet looks the same wherever it is read.
   --------------------------------------------------------------------------- */
.mcp-facts { display: flex; flex-wrap: wrap; gap: .75rem 2.5rem; margin: 1rem 0; padding: .875rem 1rem; border-radius: .75rem; background: var(--cream); }
.mcp-facts > div { min-width: 0; }
.mcp-facts dt { color: var(--stone); font-size: .6875rem; font-weight: 500; letter-spacing: .025em; text-transform: uppercase; }
.mcp-facts dd { margin: .25rem 0 0; overflow-wrap: anywhere; color: var(--ink); font-size: .875rem; }
.mcp-status { margin: .75rem 0 0; font-size: .8125rem; }
.mcp-status.good { color: var(--ok-ink); }
.mcp-status.bad { color: var(--rose); }
.mcp-confirm { margin-top: .875rem; padding: .875rem 1rem; border: 1px solid var(--line-strong); border-radius: .75rem; }
.mcp-confirm p { margin: 0 0 .625rem; font-size: .875rem; }

/* The one place in the app that shows a secret. A ring in the accent colour,
   because this box is the only chance the reader gets to copy the key. */
.mcp-fresh { margin-top: .875rem; padding: .875rem 1rem; border: 1px solid rgba(239, 68, 68, .35); border-radius: .75rem; }
.mcp-fresh-title { margin: 0 0 .5rem; color: var(--charcoal); font-size: .875rem; font-weight: 500; }
.mcp-key-row { display: flex; flex-wrap: wrap; align-items: center; gap: .625rem; }
.mcp-key-row code { min-width: 0; flex: 1 1 18rem; padding: .5rem .625rem; overflow-wrap: anywhere; border-radius: .5rem; background: var(--cream); font-family: var(--font-mono); font-size: .8125rem; }

/* The endpoint is the one fact a reader needs before they have a key at all,
   so it sits above both states of the card rather than inside either. */
.mcp-endpoint-row { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem .625rem; margin: .875rem 0; }
.mcp-endpoint-label { color: var(--stone); font-size: .6875rem; font-weight: 500; letter-spacing: .025em; text-transform: uppercase; }
.mcp-endpoint-row code { min-width: 0; padding: .375rem .5rem; overflow-wrap: anywhere; border-radius: .5rem; background: var(--cream); font-family: var(--font-mono); font-size: .8125rem; }
.mcp-guide { margin: 1rem 0 0; }
.mcp-fresh .snippet { margin-bottom: 0; }

.snippet { margin: .75rem 0 1.25rem; }
.snippet-head { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: .5rem; margin-bottom: .375rem; }
.snippet-title { margin: 0; color: var(--charcoal); font-size: .8125rem; font-weight: 700; }
/* white-space: pre plus a scroll box: a one line install command stays one
   line and scrolls, rather than wrapping into something that no longer looks
   like the command it is. */
.snippet-body { margin: 0; padding: .75rem .875rem; overflow-x: auto; border: 1px solid var(--line); border-radius: .75rem; background: var(--cream); }
.snippet-body code { color: var(--ink); font-family: var(--font-mono); font-size: .75rem; line-height: 1.6; white-space: pre; }

/* The global table rule sets the mono face for numbers. This table is prose,
   so it takes the sans face back and leaves .mono to the tool names. */
.tool-table { font-family: var(--font-sans); }
.tool-table td { vertical-align: top; font-size: .8125rem; }
.tool-table td:first-child { white-space: nowrap; }
.tool-inputs { margin: 0; padding-left: 1.1rem; }
.tool-inputs li { margin-bottom: .25rem; }

/* Public free tool pages, currently /tools/food-card. A stranger's first sight
   of this app, so the intro gets a size step the authenticated cards do not,
   and the photo box is given the room to read as the point of the page.
   The .tool-* names above belong to the MCP tool table on /docs/mcp; these are
   prefixed .foodtool- so the two cannot be confused for one family. */
/* The whole tool page sits in .foodtool so the rules below cannot reach any
   other page. It repeats .wrap's own column so wrapping the sections in it
   changes nothing about how they stack. */
.foodtool { display: flex; flex-direction: column; gap: 1.5rem; min-width: 0; }
.foodtool-hero h1 { font-size: 1.75rem; line-height: 1.2; }
.foodtool-lede { margin: .75rem 0 .5rem; color: var(--ink); font-size: 1.0625rem; line-height: 1.5; }
.foodtool-dropzone { padding: 2.25rem 1rem; }
/* An anchor styled as a chip is inline by default, which drops its vertical
   padding out of the line box. The chips inside a .row are flex items and are
   blockified anyway, so this only changes the standalone ones. Scoped to this
   page: unscoped it also took the underline off the admin tool's download link,
   which is a chip in a page full of underlined links and looked out of place. */
.foodtool a.chip-button { display: inline-block; text-decoration: none; }
.foodtool-cta { background: var(--charcoal); color: var(--cream); }
.foodtool-cta h2 { color: var(--white); }
.foodtool-cta .chip-button { border-color: rgba(250, 247, 242, .25); background: transparent; color: var(--cream); }
.foodtool-cta .chip-button:hover { border-color: var(--cream); background: var(--cream); color: var(--charcoal); }

@media (min-width: 640px) {
  .foodtool-hero h1 { font-size: 2.25rem; }
  .foodtool-lede { font-size: 1.125rem; }
}

/* ---------------------------------------------------------------------------
   Training heatmap, views/partials/heatmap.ejs. Shared by the public profile
   and the back face of the Training card on Today. These rules lived in a
   <style> block inside the partial, which meant they were re-sent with every
   render and never cache-busted; they are here now for that reason alone.
   Nothing about them changed in the move.
   --------------------------------------------------------------------------- */
.hm { display: grid; grid-template-columns: auto 1fr; gap: var(--s1) .375rem; }
.hm-scroll { overflow-x: auto; padding-bottom: var(--s1); }
.hm-months { display: grid; grid-auto-flow: column; grid-auto-columns: 11px; gap: 3px; height: .875rem; }
.hm-month { overflow: visible; color: var(--stone); font-size: .625rem; line-height: 1; white-space: nowrap; }
.hm-weekdays { display: grid; grid-template-rows: repeat(7, 11px); gap: 3px; }
.hm-weekday { display: flex; align-items: center; color: var(--stone); font-size: .625rem; line-height: 1; }
.hm-grid { display: grid; grid-auto-flow: column; grid-template-rows: repeat(7, 11px); grid-auto-columns: 11px; gap: 3px; }
/* inline-block so the legend swatches size the same as the grid cells, which
   are blockified by the grid anyway. */
.hm-cell { display: inline-block; width: 11px; height: 11px; border-radius: 2px; background: var(--track); }
/* Five steps toward the same green the rest of the app uses for a good
   reading, so the graph belongs to this palette and not to GitHub's. */
.hm-cell.l1 { background: #D3E9D8; }
.hm-cell.l2 { background: #96CDA6; }
.hm-cell.l3 { background: var(--ok-mid); }
.hm-cell.l4 { background: var(--ok); }
.hm-cell.pad { background: none; }
.hm-legend { display: flex; align-items: center; justify-content: flex-end; gap: .3rem; margin-top: var(--s2); color: var(--stone); font-size: .6875rem; }
.hm-legend .hm-cell { border-radius: 2px; }

/* ---------------------------------------------------------------------------
   Public profile at /u/:username, views/public-profile.ejs. Moved out of that
   file's <style> block for the same reason as the heatmap above: one stylesheet,
   one cache-busted URL. Selectors and values are unchanged, except that the
   40rem query is now the app's own 639px phone width and the two stone borders
   read from --line-soft.
   --------------------------------------------------------------------------- */
.pp-head { display: flex; align-items: baseline; justify-content: space-between; gap: var(--s4); flex-wrap: wrap; }
.pp-handle { color: var(--stone); font-family: var(--font-mono); font-size: .8125rem; }
.pp-bars { display: flex; align-items: flex-end; gap: 2px; height: 60px; margin-top: var(--s3); }
.pp-bar { flex: 1 1 0; min-width: 3px; border-radius: 2px 2px 0 0; background: var(--ok-mid); }
.pp-bar.none { background: var(--track); }
.pp-scale { display: flex; justify-content: space-between; margin-top: var(--s1); color: var(--stone); font-size: .625rem; }
.pp-marker { display: flex; align-items: baseline; justify-content: space-between; gap: var(--s3); padding: .375rem 0; border-bottom: 1px solid var(--line-soft); }
.pp-marker:last-child { border-bottom: 0; }
.pp-marker-name { min-width: 0; overflow-wrap: anywhere; }
.pp-marker-value { flex: none; font-family: var(--font-mono); font-variant-numeric: tabular-nums; text-align: right; }
.pp-ref { display: block; color: var(--stone); font-size: .6875rem; }
.pp-flag { display: inline-block; padding: .0625rem .375rem; border-radius: .625rem; font-size: .625rem; letter-spacing: .02em; text-transform: uppercase; }
.pp-flag.ok { background: #E3F0E7; color: var(--ok-ink); }
.pp-flag.out { background: #FDE7E7; color: #991B1B; }
.pp-panels { display: grid; grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr)); gap: 1.25rem; }
.pp-cta { text-align: center; }
@media (max-width: 639px) { .pp-panels { grid-template-columns: 1fr; } }

/* ---------------------------------------------------------------------------
   Login. The generous outer rhythm makes the single card feel centred while
   leaving room for notices to grow without pinning the page to the viewport.
   --------------------------------------------------------------------------- */
/* Qualified with .card.narrow, which is what these cards also carry: that rule
   sets `margin: 3.5rem auto` at 0,2,0 and would otherwise beat a bare
   .login-card at 0,1,0 whatever the source order. The auto inline margins from
   the shorthand still centre the card; only the block margins are replaced. */
.card.narrow.login-card { margin-block: max(var(--s6), 8vh); padding: var(--s6); }
/* No wordmark in the card: the top bar already carries the brand lockup on
   this page, and one heading with visible text is its own accessible name. */
.login-title { margin-bottom: var(--s3); }
.login-benefit { margin-bottom: var(--s5); color: var(--stone); }
.login-google-form { margin-bottom: var(--s4); }
/* Google is the intended path, so it alone occupies the full card width. */
.login-card .login-google-button { width: 100%; }
.login-divider { display: flex; align-items: center; gap: var(--s3); margin-bottom: var(--s4); white-space: nowrap; }
.login-divider::before, .login-divider::after { height: 1px; flex: 1; background: var(--line-soft); content: ""; }
/* The email input takes the app's global control rule (it is in that selector
   list now), so it matches the owner-password input below it on this card. */
.login-card .login-email-button { width: 100%; }
.login-early-access { margin-top: var(--s4); }
.login-owner { color: var(--stone); }
.login-owner .stack { padding-top: var(--s3); }

@media (max-width: 480px) {
  .card.narrow.login-card { margin-block: var(--s4); padding: var(--s5); }
}

/* ---------------------------------------------------------------------------
   Subscribe. A narrow vertical price stack keeps the choice easy to scan and
   leaves checkout as the only visually dominant action.
   --------------------------------------------------------------------------- */
/* Qualified for the same reason as .login-card above: .card.narrow is 0,2,0. */
.card.narrow.sub-card { max-width: 25rem; padding: var(--s6); }
.sub-title { margin-bottom: var(--s3); }
.sub-intro { color: var(--stone); }
.sub-batch { display: inline-block; margin: var(--s3) 0 var(--s4); padding: var(--s1) var(--s3); border: 1px solid var(--line-strong); border-radius: var(--radius-s); color: var(--stone); font-size: .6875rem; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; }
.sub-price { display: flex; align-items: baseline; gap: var(--s2); margin-bottom: var(--s5); }
.sub-amount { color: var(--charcoal); font-family: var(--font-mono); font-size: 2.75rem; font-weight: 600; letter-spacing: -.04em; line-height: 1; }
.sub-period { color: var(--stone); font-size: .8125rem; }
.sub-features { display: flex; flex-direction: column; gap: var(--s3); margin: 0 0 var(--s5); padding: 0; list-style: none; }
.sub-features li { position: relative; padding-left: var(--s5); }
.sub-features li::before { position: absolute; left: 0; color: var(--ok-ink); content: "\2713"; font-weight: 700; }
.sub-checkout-form { margin-top: var(--s2); }
.sub-card .sub-checkout-button { width: 100%; }
.sub-small-print { margin: var(--s4) 0 0; padding-top: var(--s4); border-top: 1px solid var(--line-soft); }

@media (max-width: 480px) {
  .card.narrow.sub-card { margin-block: var(--s4); padding: var(--s5); }
  .sub-amount { font-size: 2.25rem; }
}

/* ---------------------------------------------------------------------------
   Admin. These small utilities keep owner-only preview spacing out of markup.
   --------------------------------------------------------------------------- */
/* Cap uploaded photos so selecting a file does not push the form controls away. */
.admin-upload-preview { max-height: 6rem; }
/* Preserve the generated card's aspect ratio while fitting narrow admin cards. */
.admin-card-preview { display: block; max-width: 100%; height: auto; border-radius: var(--radius-s); }
/* Separate the download controls from the generated card preview. */
.admin-card-actions { margin-top: var(--s4); }
