/* =====================================================================
   Nutrition Gnome - component library
   Built on the variables defined in tokens.css. Reset, layout helpers,
   and every component/page-section class the templates use.
   ===================================================================== */

/* ---------- reset ---------- */
*, *::before, *::after { box-sizing: border-box; }
* { margin: 0; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
body {
  font-family: var(--sans);
  color: var(--ink);
  background: var(--parchment);
  line-height: 1.62;
  font-size: 17px;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  /* Now that Fraunces is self-hosted as a variable font (fonts.css), its
     optical-size axis can actually respond to rendered text size instead of
     being frozen at one point the way Google's per-weight static export was.
     WONK/SOFT are non-standard axes with no CSS-level default (the font's
     own default is WONK 1, i.e. "wonky" forms on), so they're pinned
     explicitly to 0 here to keep today's letterforms unchanged. */
  font-optical-sizing: auto;
  font-variation-settings: "WONK" 0, "SOFT" 0;
}
img { max-width: 100%; display: block; height: auto; }
a { color: inherit; text-decoration: none; }
button { font: inherit; cursor: pointer; }

/* ---------- type ---------- */
h1, h2, h3 { font-family: var(--serif); font-weight: 600; line-height: 1.12; color: var(--ink); }
em { font-style: italic; }

.eyebrow {
  font-family: var(--serif);
  font-weight: 600;
  font-size: 0.78rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--terracotta-text);
  margin-bottom: 0.9rem;
}
.eyebrow--center { text-align: center; }
.eyebrow--light { color: var(--gold); }

.section-title {
  font-size: clamp(1.9rem, 1.2rem + 2.4vw, 2.9rem);
  letter-spacing: -0.01em;
}
.section-title--center { text-align: center; }
.section-title--light { color: var(--parchment); }

/* ---------- layout ---------- */
.container { width: 100%; max-width: var(--maxw); margin: 0 auto; padding: 0 24px; }
.container--narrow { max-width: 760px; }
.section { padding: clamp(3.5rem, 2rem + 6vw, 6.5rem) 0; }
.site-main { display: block; }

/* Readable by a screen reader, invisible on screen. Used for table captions
   and header cells whose meaning is obvious visually but would otherwise be
   an unlabelled blank to assistive tech. */
.visually-hidden {
  position: absolute; width: 1px; height: 1px; margin: -1px;
  padding: 0; overflow: hidden; white-space: nowrap;
  clip-path: inset(50%); border: 0;
}

/* ---------- skip link ---------- */
.skip-link {
  position: absolute; left: -999px; top: 0; z-index: 200;
  background: var(--ink); color: var(--parchment);
  padding: 10px 16px; border-radius: 0 0 8px 0;
}
.skip-link:focus { left: 0; }

/* ---------- seals (signature) ---------- */
.seal {
  display: inline-flex; align-items: center; justify-content: center;
  font-family: var(--serif); font-weight: 600;
  text-transform: uppercase; letter-spacing: 0.14em;
  font-size: 0.7rem; color: var(--terracotta-text);
  border: 1.5px solid var(--terracotta);
  border-radius: 999px; padding: 0.42em 0.95em;
  box-shadow: inset 0 0 0 2px var(--parchment), inset 0 0 0 3px rgba(var(--terracotta-rgb), 0.25);
  background: transparent;
}
.seal--sm { font-size: 0.62rem; padding: 0.36em 0.8em; }
.seal--num {
  width: 58px; height: 58px; padding: 0; font-size: 1.15rem; letter-spacing: 0.04em;
  color: var(--forest); border-color: var(--forest);
  box-shadow: inset 0 0 0 2px var(--cream-card), inset 0 0 0 3px rgba(var(--forest-rgb), 0.28);
}

/* ---------- sprig divider ---------- */
.sprig { display: flex; justify-content: center; color: var(--ink-soft); opacity: 0.55; padding: 0.5rem 0; }
.sprig svg { width: 220px; height: auto; }

/* ---------- buttons ---------- */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 0.5em;
  font-family: var(--sans); font-weight: 800; font-size: 0.96rem;
  letter-spacing: 0.01em; padding: 0.85em 1.5em; border-radius: 10px;
  border: 1.6px solid transparent; transition: transform 0.15s ease, background 0.2s ease, color 0.2s ease, box-shadow 0.2s ease;
}
.btn:active { transform: translateY(1px); }
.btn--primary { background: var(--terracotta); color: #fff; box-shadow: var(--shadow-sm); }
.btn--primary:hover { background: var(--terracotta-dark); box-shadow: var(--shadow-md); }
.btn--gold { background: var(--gold); color: #2c2114; }
.btn--gold:hover { background: var(--gold-dark); }
.btn--ghost { border-color: var(--line-strong); color: var(--ink); background: transparent; }
.btn--ghost:hover { background: rgba(var(--ink-rgb), 0.06); border-color: var(--ink); }
.btn--ghost-light { border-color: rgba(var(--cream-fixed-rgb), 0.55); color: var(--cream-fixed); }
.btn--ghost-light:hover { background: rgba(var(--cream-fixed-rgb), 0.12); border-color: var(--cream-fixed); }
.btn--block { width: 100%; }

/* ---------- nav ---------- */
.site-nav {
  position: sticky; top: 0; z-index: 100;
  background: rgba(var(--parchment-rgb), 0.88);
  backdrop-filter: saturate(150%) blur(8px);
  -webkit-backdrop-filter: saturate(150%) blur(8px);
  border-bottom: 1px solid var(--line);
}
.nav-inner {
  max-width: var(--maxw); margin: 0 auto; padding: 0.7rem 24px;
  display: flex; align-items: center; justify-content: space-between; gap: 1rem;
}
.brand { display: inline-flex; align-items: center; gap: 0.6rem; }
.brand-mark { width: 44px; height: 44px; }
.brand-name { font-family: var(--serif); font-weight: 600; font-size: 1.22rem; letter-spacing: -0.01em; }
.brand-name em { color: var(--terracotta-text); font-style: italic; }

.nav-links { display: flex; align-items: center; gap: 1.6rem; }
.nav-links a { font-weight: 700; font-size: 0.96rem; color: var(--ink); transition: color 0.18s ease; }
.nav-links a:hover { color: var(--terracotta-text); }
.nav-links a.is-active { color: var(--terracotta-text); }
.nav-cta { background: var(--terracotta); color: #fff !important; padding: 0.55em 1.1em; border-radius: 9px; }
.nav-cta:hover { background: var(--terracotta-dark); }
.nav-cta--quiet { background: transparent; color: var(--ink) !important; border: 1.5px solid var(--line-strong); }
.nav-cta--quiet:hover { background: rgba(var(--ink-rgb),0.06); }

.nav-actions { display: flex; align-items: center; gap: 0.7rem; }

.theme-toggle {
  display: inline-flex; align-items: center; justify-content: center;
  width: 40px; height: 40px; flex: 0 0 auto;
  background: transparent; border: 1px solid var(--line); border-radius: 9px;
  color: var(--ink); transition: background 0.18s ease, border-color 0.18s ease, color 0.18s ease;
}
.theme-toggle:hover { background: rgba(var(--ink-rgb), 0.06); border-color: var(--line-strong); }
.theme-toggle-icon { width: 20px; height: 20px; }
.theme-toggle-icon--moon { display: none; }
:root[data-theme="dark"] .theme-toggle-icon--sun { display: none; }
:root[data-theme="dark"] .theme-toggle-icon--moon { display: block; }

.nav-toggle { display: none; flex-direction: column; gap: 5px; width: 44px; height: 40px; align-items: center; justify-content: center; background: transparent; border: 1px solid var(--line); border-radius: 9px; }
.nav-toggle span { display: block; width: 20px; height: 2px; background: var(--ink); transition: transform 0.2s ease, opacity 0.2s ease; }
.nav-toggle[aria-expanded="true"] span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
.nav-toggle[aria-expanded="true"] span:nth-child(2) { opacity: 0; }
.nav-toggle[aria-expanded="true"] span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }

/* ---------- hero ---------- */
.hero {
  background:
    radial-gradient(1100px 480px at 82% -12%, rgba(var(--gold-rgb), 0.13), transparent 60%),
    radial-gradient(820px 460px at -5% 112%, rgba(var(--forest-rgb), 0.10), transparent 55%),
    var(--parchment);
  /* Vertical padding is in vh, not vw. The job of these numbers is to decide
     where the fold lands - the results cards below are meant to break it -
     and that depends on how TALL the viewport is, not how wide. A short
     laptop screen compresses the hero; a tall monitor lets it breathe, and
     the cards keep peeking either way. */
  padding: clamp(1.75rem, 4vh, 3.25rem) 0 clamp(1.5rem, 3.5vh, 2.75rem);
  overflow: hidden;
}
.hero-inner {
  max-width: var(--maxw); margin: 0 auto; padding: 0 24px;
  display: grid; grid-template-columns: 1.15fr 0.85fr; gap: clamp(2rem, 5vw, 4rem); align-items: center;
}
.hero-title { font-size: clamp(2.1rem, 1.2rem + 3.4vw, 3.4rem); font-weight: 600; letter-spacing: -0.02em; margin-bottom: 0.9rem; }
.hero-title em { color: var(--terracotta-text); }
.hero-lede { font-size: clamp(1.02rem, 1rem + 0.3vw, 1.14rem); color: var(--ink-soft); max-width: 38ch; margin-bottom: 1.4rem; }
.hero-actions { display: flex; flex-wrap: wrap; gap: 0.8rem; }
.hero-actions--center { justify-content: center; }
.hero-trust { margin-top: 1rem; font-size: 0.84rem; color: var(--ink-soft); font-weight: 600; }

.hero-badge { position: relative; display: flex; justify-content: center; }
.badge-ring {
  position: relative; width: min(300px, 62vw); aspect-ratio: 1; border-radius: 50%;
  background: radial-gradient(circle at 50% 40%, var(--cream-card), var(--cream-card-2));
  border: 2px solid var(--terracotta);
  box-shadow: 0 0 0 8px var(--parchment), 0 0 0 9px rgba(var(--terracotta-rgb),0.25), var(--shadow-lg);
  display: flex; align-items: center; justify-content: center;
}
.badge-ring::after {
  content: ""; position: absolute; inset: 14px; border-radius: 50%;
  border: 1.5px dashed rgba(var(--gold-rgb), 0.6);
}
.badge-ring img { width: 78%; height: auto; filter: drop-shadow(0 8px 18px rgba(0,0,0,0.18)); }
.seal--float { position: absolute; bottom: 6%; left: 2%; background: var(--parchment); }

/* ---------- steps ---------- */
.steps { background: var(--parchment-2); }
.step-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1.5rem; margin-top: 2.6rem; }
.step-card {
  background: var(--cream-card); border: 1px solid var(--line); border-radius: var(--radius-lg);
  padding: 2rem 1.7rem; box-shadow: var(--shadow-sm);
}
.step-card .seal--num { margin-bottom: 1.1rem; }
.step-card h3 { font-size: 1.3rem; margin-bottom: 0.55rem; }
.step-card p { color: var(--ink-soft); }

/* ---------- band (philosophy) ---------- */
.band {
  background:
    radial-gradient(700px 360px at 90% 0%, rgba(var(--gold-rgb),0.14), transparent 60%),
    var(--forest-deep);
  color: var(--cream-fixed); padding: clamp(3.2rem, 2rem + 5vw, 5.5rem) 0;
}
.band-inner { display: grid; grid-template-columns: 1.05fr 0.95fr; gap: clamp(2rem, 5vw, 4rem); align-items: center; }
.band-lede { color: rgba(var(--cream-fixed-rgb), 0.82); font-size: 1.08rem; margin: 1rem 0 1.8rem; max-width: 42ch; }
.phase-figure { display: flex; align-items: stretch; gap: 1rem; }
.phase {
  flex: 1; background: rgba(var(--cream-fixed-rgb), 0.07); border: 1px solid rgba(var(--cream-fixed-rgb), 0.2);
  border-radius: var(--radius); padding: 1.4rem 1.3rem;
}
.phase-label { display: block; font-family: var(--serif); font-size: 0.72rem; letter-spacing: 0.14em; text-transform: uppercase; color: var(--gold); margin-bottom: 0.5rem; }
.phase strong { font-family: var(--serif); font-weight: 600; font-size: 1.18rem; display: block; margin-bottom: 0.35rem; }
.phase p { color: rgba(var(--cream-fixed-rgb), 0.78); font-size: 0.95rem; }
.phase--active { border-left: 3px solid var(--terracotta); }
.phase--hold { border-left: 3px solid var(--gold); }
.phase-arrow { align-self: center; font-size: 1.6rem; color: var(--gold); }

/* ---------- features ---------- */
.features { background: var(--parchment-2); }
.feature-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 1.4rem; margin-top: 2.6rem; }
.feature { background: var(--cream-card); border: 1px solid var(--line); border-radius: var(--radius-lg); padding: 1.8rem 1.5rem; box-shadow: var(--shadow-sm); }
.feature-icon {
  display: inline-flex; align-items: center; justify-content: center; width: 52px; height: 52px;
  border-radius: 50%; background: rgba(var(--forest-rgb), 0.10); color: var(--forest); margin-bottom: 1.1rem;
}
.feature-icon svg { width: 26px; height: 26px; }
.feature h3 { font-size: 1.18rem; margin-bottom: 0.5rem; }
.feature p { color: var(--ink-soft); font-size: 0.97rem; }

/* ---------- coach ---------- */
.coach { background: var(--parchment); }
.coach-inner { display: grid; grid-template-columns: 0.8fr 1.2fr; gap: clamp(2rem, 5vw, 3.5rem); align-items: center; }
.coach-portrait { position: relative; justify-self: center; width: min(260px, 70vw); aspect-ratio: 1; border-radius: 50%; background: var(--cream-card); border: 2px solid var(--forest); box-shadow: 0 0 0 7px var(--parchment), var(--shadow-md); display: flex; align-items: center; justify-content: center; }
.coach-portrait img { width: 74%; }
.coach-portrait .seal--sm { position: absolute; bottom: -8px; left: 50%; transform: translateX(-50%); background: var(--parchment); }
.coach-copy p { color: var(--ink-soft); margin-bottom: 1rem; max-width: 52ch; }
.coach-copy .btn { margin-top: 0.4rem; }

/* ---------- quote ---------- */
.quote-section { background: var(--parchment-2); }
.quote { text-align: center; max-width: 780px; margin: 0 auto; position: relative; }
.quote::before { content: "\201C"; font-family: var(--serif); font-size: 5rem; line-height: 0.6; color: var(--terracotta); opacity: 0.35; display: block; margin-bottom: 0.4rem; }
.quote blockquote { font-family: var(--serif); font-style: italic; font-weight: 500; font-size: clamp(1.4rem, 1rem + 1.6vw, 2rem); color: var(--ink); line-height: 1.34; }
.quote figcaption { margin-top: 1.4rem; font-size: 0.85rem; letter-spacing: 0.06em; text-transform: uppercase; color: var(--ink-soft); }

/* ---------- proof bar (headline stats) ----------
   Distinct from the dashboard's .stat-strip: that one shows a logged-in
   client their own figures, so the two must be able to change apart.
   parchment, not parchment-2: it sits between the results band above and
   the steps band below, both parchment-2, and two identical surfaces in a
   row read as one section. The keylines make it read as its own band. */
.proof-bar { background: var(--parchment); border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); padding: clamp(1.8rem, 1.2rem + 2vw, 2.8rem) 0; }
.proof-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 1.4rem; }
.proof-item { text-align: center; }
.proof-value { font-family: var(--serif); font-weight: 600; font-size: clamp(2rem, 1.5rem + 1.8vw, 2.9rem); line-height: 1; color: var(--terracotta-text); }
.proof-suffix { font-size: 0.5em; font-weight: 700; margin-left: 0.05em; }
.proof-label { margin-top: 0.5rem; font-size: 0.88rem; color: var(--ink-soft); line-height: 1.35; max-width: 22ch; margin-left: auto; margin-right: auto; }
.proof-note { text-align: center; margin-top: 1.6rem; font-size: 0.8rem; font-style: italic; color: var(--ink-soft); }

/* ---------- results / success stories ---------- */
.results { background: var(--parchment-2); }
/* The home-page instance straddles the fold, so it uses tighter top padding
   than the standard .section rhythm - every pixel above the cards pushes
   them further off screen. The full /success-stories page keeps .section. */
.results--fold { padding-top: clamp(1.5rem, 3vh, 2.5rem); }
.results--fold .section-title { font-size: clamp(1.5rem, 1.1rem + 1.4vw, 2.1rem); }
.results--fold .results-grid { margin-top: clamp(1.2rem, 2.5vh, 1.9rem); }
.results-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1.5rem; margin-top: 2.6rem; }
.result-card {
  display: flex; flex-direction: column; gap: 1rem;
  background: var(--cream-card); border: 1px solid var(--line);
  border-radius: var(--radius-lg); padding: 1.5rem 1.4rem; box-shadow: var(--shadow-sm);
}
.result-figures { display: grid; grid-template-columns: 1fr 1fr; gap: 0.7rem; }
.result-fig { margin: 0; }
.result-fig img { display: block; width: 100%; height: auto; border-radius: var(--radius); border: 1px solid var(--line); background: var(--parchment); }
.result-fig figcaption { margin-top: 0.4rem; text-align: center; font-size: 0.7rem; font-weight: 800; letter-spacing: 0.1em; text-transform: uppercase; color: var(--ink-soft); }
.result-delta { display: flex; align-items: baseline; gap: 0.5rem; flex-wrap: wrap; border-bottom: 1px solid var(--line); padding-bottom: 0.9rem; }
.result-delta strong { font-family: var(--serif); font-weight: 600; font-size: 1.9rem; line-height: 1; color: var(--forest); }
.result-delta span { font-size: 0.85rem; color: var(--ink-soft); }
.result-quote { margin: 0; font-family: var(--serif); font-style: italic; font-weight: 500; font-size: 1.02rem; line-height: 1.5; color: var(--ink-body); }
.result-person { display: flex; flex-direction: column; line-height: 1.3; margin-top: auto; }
.result-person strong { color: var(--ink); font-size: 0.96rem; }
.result-person span { color: var(--ink-soft); font-size: 0.84rem; }
.results-more { display: flex; justify-content: center; margin-top: 2.2rem; }
.results-note { text-align: center; margin-top: 1.6rem; font-size: 0.82rem; font-style: italic; color: var(--ink-soft); max-width: 62ch; margin-left: auto; margin-right: auto; }
.results-note .seal--sm { margin-right: 0.5rem; font-style: normal; }

/* ---------- waitlist / pre-launch ----------
   The panel takes the slot under the hero that the success-story cards use
   once real ones exist, so it carries the same job: break the fold with
   something meaningful rather than leaving the hero over empty space. */
.waitlist-fold { background: var(--parchment-2); padding-top: clamp(1.5rem, 3vh, 2.5rem); }
.waitlist-panel {
  max-width: 720px; margin: 0 auto; text-align: center;
  background: var(--cream-card); border: 1px solid var(--line);
  border-radius: var(--radius-lg); box-shadow: var(--shadow-sm);
  padding: clamp(1.6rem, 1rem + 2vw, 2.6rem) clamp(1.3rem, 1rem + 1.5vw, 2.2rem);
}
.waitlist-panel .section-title { font-size: clamp(1.5rem, 1.1rem + 1.4vw, 2.1rem); }
.waitlist-body { color: var(--ink-soft); margin: 0.9rem auto 1.5rem; max-width: 52ch; }
.waitlist-note { margin-top: 1.1rem; font-size: 0.85rem; color: var(--ink-soft); }

/* The same offer, restated as a banner above the tier grid on /pricing. */
.waitlist-notice {
  background: rgba(var(--gold-rgb), 0.13); border: 1px solid rgba(var(--gold-rgb), 0.5);
  border-radius: var(--radius-lg); padding: 1.6rem 1.5rem; margin-bottom: 2.4rem;
  text-align: center;
}
.waitlist-notice h2 { font-size: clamp(1.4rem, 1.1rem + 1vw, 1.9rem); margin: 0.7rem 0 0.5rem; }
.waitlist-notice p { color: var(--ink-body); max-width: 56ch; margin: 0 auto; }
.waitlist-notice .btn { margin-top: 1.2rem; }
.waitlist-notice .waitlist-note { margin-top: 0.9rem; }

/* Stands in for .tier-price when prices are withheld before launch - same
   vertical space, so the tier cards keep their alignment. */
.tier-price--tbc {
  font-family: var(--serif); font-weight: 600; color: var(--ink-soft);
  font-size: 1.5rem; line-height: 1.1;
}

/* ---------- comparison table ---------- */
.compare { background: var(--parchment-2); }
/* The wrapper scrolls so the table can keep a usable min-width on a phone
   without widening the page body (see .compare-table min-width). */
.compare-scroll { margin-top: 2.6rem; overflow-x: auto; border: 1px solid var(--line); border-radius: var(--radius-lg); background: var(--cream-card); box-shadow: var(--shadow-sm); }
.compare-table { width: 100%; border-collapse: collapse; font-size: 0.94rem; min-width: 720px; }
.compare-table th, .compare-table td { padding: 0.95rem 1.1rem; text-align: left; vertical-align: top; border-bottom: 1px solid rgba(var(--ink-rgb), 0.08); }
.compare-table thead th { font-family: var(--sans); font-weight: 800; font-size: 0.76rem; text-transform: uppercase; letter-spacing: 0.06em; color: var(--forest); background: rgba(var(--forest-rgb), 0.10); border-bottom: 1px solid var(--line); }
.compare-table tbody th { font-weight: 700; color: var(--ink); width: 22%; }
.compare-table tbody td { color: var(--ink-soft); }
.compare-table tbody tr:last-child th, .compare-table tbody tr:last-child td { border-bottom: none; }
/* Our column, tinted the full height of the table so the eye tracks it. */
.compare-table .compare-ours { background: rgba(var(--terracotta-text-rgb), 0.07); color: var(--ink-body); }
.compare-table thead th.compare-ours { background: rgba(var(--terracotta-text-rgb), 0.16); color: var(--terracotta-text); }

/* ---------- assurance / risk reversal ---------- */
.assurance { background: var(--parchment); }
.assurance-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 1.4rem; margin-top: 2.6rem; }
.assurance-item { background: var(--cream-card); border: 1px solid var(--line); border-radius: var(--radius-lg); padding: 1.6rem 1.4rem; box-shadow: var(--shadow-sm); }
.assurance-tick {
  display: inline-flex; align-items: center; justify-content: center;
  width: 38px; height: 38px; border-radius: 50%; margin-bottom: 0.9rem;
  background: rgba(var(--forest-rgb), 0.14); color: var(--forest);
}
.assurance-tick svg { width: 20px; height: 20px; }
.assurance-item h3 { font-size: 1.1rem; margin-bottom: 0.45rem; }
.assurance-item p { color: var(--ink-soft); font-size: 0.94rem; }

/* ---------- home faq teaser ---------- */
.faq-more { text-align: center; margin-top: 1.6rem; font-weight: 700; font-size: 0.94rem; }
.faq-more a { color: var(--terracotta-text); }
.faq-more a:hover { text-decoration: underline; }

/* ---------- cta band ---------- */
.cta-band { background: var(--terracotta); color: #fff; padding: clamp(3rem, 2rem + 4vw, 4.6rem) 0; }
.cta-inner { text-align: center; }
.cta-band .section-title { color: #fff; }
.cta-band p { color: rgba(255, 255, 255, 0.9); max-width: 50ch; margin: 0.9rem auto 1.8rem; }

/* ---------- page head ---------- */
.page-head { background: var(--parchment); padding: clamp(3rem, 2rem + 4vw, 5rem) 0 0.5rem; }
.page-head-lede { text-align: center; color: var(--ink-soft); max-width: 56ch; margin: 1rem auto 0; font-size: 1.08rem; }

/* ---------- pricing ---------- */
.pricing { background: var(--parchment); }
/* flex-wrap, not a fixed 3-column grid: a tier can go dormant (see
   core/content.py's onstage()) and the count on the page varies, so this
   needs to centre cleanly at 2 as well as fill out at 3, with no code change
   either way. */
.tier-grid { display: flex; flex-wrap: wrap; justify-content: center; gap: 1.5rem; align-items: stretch; }
.tier {
  position: relative; display: flex; flex-direction: column;
  flex: 1 1 300px; max-width: 380px;
  background: var(--cream-card); border: 1px solid var(--line); border-radius: var(--radius-lg);
  padding: 2rem 1.7rem; box-shadow: var(--shadow-sm);
}
.tier--featured { border: 2px solid var(--terracotta); box-shadow: var(--shadow-lg); transform: translateY(-8px); }
.tier-ribbon {
  position: absolute; top: -13px; left: 50%; transform: translateX(-50%);
  background: var(--terracotta); color: #fff; font-weight: 800; font-size: 0.72rem;
  letter-spacing: 0.1em; text-transform: uppercase; padding: 0.4em 1em; border-radius: 999px; white-space: nowrap;
}
.tier-name { font-size: 1.7rem; }
.tier-tagline { color: var(--ink-soft); font-size: 0.95rem; margin-top: 0.15rem; }
.tier-price { font-family: var(--serif); font-weight: 600; font-size: 3rem; line-height: 1; margin: 1.1rem 0 0.2rem; }
.tier-currency { font-size: 1.5rem; vertical-align: super; margin-right: 0.05em; }
.tier-period { font-family: var(--sans); font-size: 0.95rem; font-weight: 700; color: var(--ink-soft); }
.tier-note { font-size: 0.82rem; color: var(--ink-soft); min-height: 2.4em; }
.tier-blurb { margin: 1rem 0 1.2rem; color: var(--ink); font-size: 0.98rem; }
.tier-features { list-style: none; padding: 0; display: grid; gap: 0.7rem; margin-bottom: 1.6rem; }
.tier-features li { display: flex; gap: 0.6rem; align-items: flex-start; font-size: 0.95rem; }
.tier-features .lacks { color: var(--ink-soft); }
.tick { flex: 0 0 auto; width: 20px; height: 20px; border-radius: 50%; background: rgba(var(--forest-rgb),0.14); color: var(--forest); display: inline-flex; align-items: center; justify-content: center; margin-top: 1px; }
.tick svg { width: 13px; height: 13px; }
.tick--off { background: rgba(var(--ink-rgb),0.08); color: var(--ink-soft); font-weight: 700; }
.tier-cta { margin-top: auto; }

.tier-footnote {
  text-align: center; margin-top: 1rem; color: var(--ink-soft); font-size: 0.85rem;
}

.founding-note {
  display: flex; align-items: center; gap: 1rem; flex-wrap: wrap;
  margin-top: 2.4rem; padding: 1.2rem 1.5rem; background: var(--cream-card);
  border: 1px dashed var(--line-strong); border-radius: var(--radius); color: var(--ink-soft); font-size: 0.95rem;
}

/* ---------- faq ---------- */
.faq { background: var(--parchment-2); }
.faq-inner { max-width: 800px; margin: 0 auto; }
.faq-list { margin-top: 2.2rem; display: grid; gap: 0.8rem; }
.faq-item { background: var(--cream-card); border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden; }
.faq-item summary {
  list-style: none; cursor: pointer; padding: 1.15rem 1.4rem; font-family: var(--serif);
  font-weight: 600; font-size: 1.1rem; display: flex; justify-content: space-between; align-items: center; gap: 1rem;
}
.faq-item summary::-webkit-details-marker { display: none; }
.faq-mark { position: relative; flex: 0 0 auto; width: 18px; height: 18px; }
.faq-mark::before, .faq-mark::after { content: ""; position: absolute; background: var(--terracotta); border-radius: 2px; }
.faq-mark::before { top: 8px; left: 0; width: 18px; height: 2px; }
.faq-mark::after { top: 0; left: 8px; width: 2px; height: 18px; transition: transform 0.2s ease; }
.faq-item[open] .faq-mark::after { transform: rotate(90deg); opacity: 0; }
.faq-answer { padding: 0 1.4rem 1.3rem; }
.faq-answer p { color: var(--ink-soft); }

/* ---------- blog ---------- */
.blog-list { background: var(--parchment); }
.post-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 1.5rem; }
.post-card { background: var(--cream-card); border: 1px solid var(--line); border-radius: var(--radius-lg); box-shadow: var(--shadow-sm); transition: transform 0.18s ease, box-shadow 0.18s ease; }
.post-card:hover { transform: translateY(-4px); box-shadow: var(--shadow-md); }
.post-card-link { display: block; padding: 1.7rem 1.6rem; height: 100%; }
.post-meta { display: flex; align-items: center; gap: 0.5rem; font-size: 0.78rem; letter-spacing: 0.04em; color: var(--ink-soft); margin-bottom: 0.8rem; }
.post-tag { color: var(--terracotta-text); text-transform: uppercase; font-weight: 800; letter-spacing: 0.1em; }
.post-card-title { font-size: 1.4rem; line-height: 1.18; margin-bottom: 0.6rem; }
.post-card-excerpt { color: var(--ink-soft); font-size: 0.97rem; }
.post-card-more { margin-top: 1rem; font-weight: 800; color: var(--terracotta-text); font-size: 0.92rem; }

/* ---------- post page ---------- */
.post-head { background: var(--parchment); padding: clamp(2.5rem, 1.5rem + 4vw, 4rem) 0 0; }
.post-back { display: inline-block; color: var(--ink-soft); font-weight: 700; font-size: 0.9rem; margin-bottom: 1.4rem; }
.post-back:hover { color: var(--terracotta-text); }
.post-title { font-size: clamp(2rem, 1.3rem + 3vw, 3.1rem); letter-spacing: -0.015em; margin: 0.6rem 0; }
.post-byline { color: var(--ink-soft); font-size: 0.92rem; }
.post-body { padding-top: 2rem; padding-bottom: 1rem; font-size: 1.09rem; }
.post-body h2 { font-size: 1.6rem; margin: 2rem 0 0.7rem; }
.post-body h3 { font-size: 1.25rem; margin: 1.5rem 0 0.5rem; }
.post-body p { margin-bottom: 1.1rem; color: var(--ink-body); }
.post-body ul, .post-body ol { margin: 0 0 1.2rem 1.2rem; }
.post-body li { margin-bottom: 0.5rem; }
.post-body strong { color: var(--ink); }
.post-body hr { border: none; border-top: 1px solid var(--line); margin: 2.6rem 0; }
.post-body .standfirst { font-family: var(--serif); font-size: clamp(1.2rem, 1.05rem + 0.7vw, 1.45rem); line-height: 1.4; color: var(--ink-soft); margin-bottom: 1.8rem; }
.post-body .callout {
  background: rgba(var(--forest-rgb), 0.09);
  border-left: 4px solid var(--forest);
  border-radius: var(--radius);
  padding: 1.1rem 1.35rem;
  margin: 1.8rem 0;
}
.post-body .callout strong { color: var(--forest); }
.post-body .disclaimer {
  font-size: 0.9rem;
  color: var(--ink-soft);
  background: var(--cream-card-2);
  border-radius: var(--radius);
  padding: 1.1rem 1.25rem;
  margin-top: 2rem;
}
.post-cta { margin: 2.5rem 0 1rem; padding: 1.8rem; background: var(--cream-card); border: 1px solid var(--line); border-radius: var(--radius-lg); text-align: center; }
.post-cta .seal--sm { margin-bottom: 0.8rem; }
.post-cta p { color: var(--ink-soft); margin-bottom: 1.2rem; max-width: 46ch; margin-left: auto; margin-right: auto; }

/* ---------- auth ---------- */
.auth { min-height: calc(100vh - 230px); display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 1.2rem; padding: 3rem 24px; background:
  radial-gradient(700px 360px at 50% -10%, rgba(var(--gold-rgb),0.12), transparent 60%), var(--parchment); }
.auth-card { width: 100%; max-width: 420px; background: var(--cream-card); border: 1px solid var(--line); border-radius: var(--radius-lg); box-shadow: var(--shadow-md); padding: 2.4rem 2.2rem; text-align: center; }
/* The "unrecognised Google account" notice on account/login.html sits above
   auth-card as a sibling, not inside it - matched to the same width so the
   two stack as one visual column rather than the notice looking wider. */
.auth .card--notice { width: 100%; max-width: 420px; }
.auth-mark { width: 72px; height: 72px; margin: 0 auto 1rem; }
.auth-title { font-size: 1.9rem; }
.auth-sub { color: var(--ink-soft); margin: 0.4rem 0 1.6rem; font-size: 0.98rem; }
/* account/login.html has no sub-copy under the title, so .auth-sub's bottom
   margin never runs. This reproduces the same gap, but only when the form
   directly follows the title - if sub-copy comes back the paragraph sits
   between them again and this rule simply stops matching. */
.auth-title + form { margin-top: 1.6rem; }
/* Sits under the Google button. Small print, but it answers the two questions
   a login page with no password field always raises: why is there no password,
   and what does Google hand over. */
.auth-fineprint { margin-top: 1rem; font-size: 0.82rem; line-height: 1.55; color: var(--ink-soft); }
.auth-fineprint a { color: var(--terracotta-text); text-decoration: underline; text-underline-offset: 2px; }
/* The "not a client yet" escape hatch - see login.html for why it exists. */
.auth-alt { margin-top: 1.8rem; padding-top: 1.4rem; border-top: 1px dashed var(--line-strong); }
.auth-alt-head { font-family: var(--serif); font-weight: 700; font-size: 1.05rem; color: var(--ink); }
.auth-alt-body { margin: 0.3rem 0 1rem; font-size: 0.88rem; color: var(--ink-soft); }
/* Same reasoning as .auth-title + form above: reproduces .auth-alt-body's
   bottom margin for the case where that paragraph has been removed. */
.auth-alt-head + .btn { margin-top: 1rem; }

/* The date-range inputs and feedback-box fields on the dashboard. (The auth
   pages have no form fields, login is Google-only, so these plus the
   field-notes signup near the footer rules below are the only forms on the
   site.) */
.field { display: grid; gap: 0.35rem; }
.field-label { font-weight: 700; font-size: 0.85rem; color: var(--ink); }
.field input, .field select, .field textarea { font: inherit; padding: 0.75rem 0.9rem; border: 1.5px solid var(--line-strong); border-radius: 9px; background: var(--input-bg); color: var(--ink); }
.field input:focus, .field select:focus, .field textarea:focus { outline: none; border-color: var(--terracotta); box-shadow: 0 0 0 3px rgba(var(--terracotta-text-rgb),0.18); }
.field textarea { resize: vertical; }

/* The netweight chart's weekly-average toggle (see static/js/netweight-
   controls.js), and the feedback box's radio choices - a labeled checkbox or
   radio sitting alongside .field elements. */
.check-field { display: flex; align-items: center; gap: 0.5rem; font-weight: 700; font-size: 0.85rem; color: var(--ink); }
.check-field input { width: 15px; height: 15px; margin: 0; }

/* ---------- feedback module ---------- */
/* A reusable review box (see core/feedback.py + _feedback_card.html); the
   netweight chart's is the first, more will follow the same pattern. */
.feedback-card fieldset.field { border: none; padding: 0; margin: 0; }
.feedback-questions { display: grid; gap: 1.2rem; margin-bottom: 1.2rem; }
.choice-row { display: flex; gap: 1.2rem; flex-wrap: wrap; }

/* ---------- flash messages ---------- */
/* Rendered once, from base.html - see core/templates/core/_messages.html.
   Text stays --ink-body in every level so contrast is the same AA-safe pair
   used for body copy in both themes; the level is carried by the tint and the
   accent bar instead, which theme automatically through the -rgb tuples. */
.msg-region { padding: 1.2rem 0 0; }
.msg { display: flex; align-items: center; gap: 0.6rem; padding: 0.8rem 1.1rem; border-radius: var(--radius); border: 1px solid var(--line-strong); border-left-width: 4px; background: rgba(var(--ink-rgb), 0.05); color: var(--ink-body); font-size: 0.95rem; }
.msg + .msg { margin-top: 0.6rem; }
.msg--success { background: rgba(var(--forest-rgb), 0.14); border-color: rgba(var(--forest-rgb), 0.42); }
.msg--warning { background: rgba(var(--gold-rgb), 0.16); border-color: rgba(var(--gold-rgb), 0.5); }
.msg--error { background: rgba(var(--terracotta-rgb), 0.12); border-color: rgba(var(--terracotta-rgb), 0.45); }

/* ---------- dashboard ---------- */
.dash { background: var(--parchment); padding: clamp(2rem, 1.5rem + 3vw, 3.2rem) 0 clamp(3rem, 2rem + 4vw, 5rem); min-height: calc(100vh - 230px); }
.dash-head { display: flex; align-items: flex-end; justify-content: space-between; gap: 1.2rem; flex-wrap: wrap; margin-bottom: 2rem; }
.dash-title { font-size: clamp(1.8rem, 1.3rem + 2vw, 2.6rem); }
.dash-tags { display: flex; align-items: center; gap: 0.7rem; flex-wrap: wrap; }
.dash-week { font-weight: 700; color: var(--ink-soft); font-size: 0.92rem; }
.pill { font-weight: 800; font-size: 0.74rem; letter-spacing: 0.08em; text-transform: uppercase; padding: 0.4em 0.85em; border-radius: 999px; }
.pill--good { background: rgba(var(--forest-rgb),0.15); color: var(--forest); }

/* The headline stat strip - a flush hairline rail rather than a set of
   boxed cards: reads like a masthead ribbon. Reflows to a single
   horizontal bar of compact tiles on mobile - see the 820px media query
   below. */
.stat-strip { display: flex; flex-wrap: wrap; border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); padding: 0.85rem 0; margin-bottom: 1.4rem; }
.stat-item { flex: 1 1 0; min-width: 140px; padding: 0 1.4rem; border-left: 1px solid var(--line); }
.stat-item:first-child { border-left: none; padding-left: 0; }
.stat-item-label { display: block; font-size: 0.68rem; font-weight: 800; text-transform: uppercase; letter-spacing: 0.08em; color: var(--ink-soft); margin-bottom: 0.25rem; }
.stat-item-value { font-family: var(--serif); font-weight: 600; font-size: 1.5rem; line-height: 1.05; color: var(--ink); }
.stat-item-unit { font-family: var(--sans); font-size: 0.78rem; font-weight: 700; color: var(--ink-soft); margin-left: 0.15em; }
.stat-item-note { display: block; margin-top: 0.2rem; font-size: 0.74rem; font-weight: 700; }
.stat-item-note.down { color: var(--forest); }
.stat-item-note.up { color: var(--terracotta-text); }
.stat-item-note.muted { color: var(--ink-soft); }
/* Mobile-only content (see the 820px query below) - hidden by default so
   it never shows above that width. */
.stat-item-label-short, .stat-item-note-short, .stat-item-trend-mobile { display: none; }

.dash-grid { display: grid; grid-template-columns: 1.7fr 1fr; gap: 1.4rem; margin-bottom: 1.4rem; }
/* The standalone top-level cards (projection, weekly summary, log) sit
   directly under .dash .container with no grid wrapper to space them - give
   only those direct children a bottom gap. The direct-child selector skips
   the cards nested inside .dash-grid (they already get their gap from the
   grid), so this adds no double-margin there. */
.dash .container > section.card { margin-bottom: 1.4rem; }
.card { background: var(--cream-card); border: 1px solid var(--line); border-radius: var(--radius-lg); padding: 1.6rem 1.6rem; box-shadow: var(--shadow-sm); }
/* Used on the dashboard's "no coaching profile linked" notice - same
   terracotta accent as .msg--error above, so an account-level problem reads
   the same way a flash error would. Token-only, so it themes automatically. */
.card--notice { border-left: 4px solid rgba(var(--terracotta-rgb), 0.55); background: rgba(var(--terracotta-rgb), 0.06); }
.card--notice .btn { margin-top: 0.4rem; }
.card-head { display: flex; align-items: baseline; justify-content: space-between; margin-bottom: 1rem; }
.card-title { font-size: 1.3rem; }
.card-hint { font-size: 0.78rem; color: var(--ink-soft); text-transform: uppercase; letter-spacing: 0.08em; font-weight: 700; }
/* Chart.js sizes its canvas off this wrapper's box; giving the height to the
   canvas element itself (as a bare `.chart-card canvas` rule would) creates a
   measure/resize feedback loop that lets the chart grow without bound. */
.chart-canvas-wrap { position: relative; width: 100%; height: 260px; }
.chart-canvas-wrap canvas { display: block; }

/* ---------- consistency tracker ----------
   Compact by design: a third-width card (see .dash-grid above) or a
   full-width hairline rail, switched behind the ?consistency= DEBUG toggle
   in core/views.py while the two are compared. Squares are drawn much
   smaller than a calendar so a phase of a dozen-plus weeks still fits a
   narrow column - see .dot-grid below. */
.consist-card { display: flex; flex-direction: column; }
.consist-rail {
  display: flex; align-items: center; gap: 1.2rem; flex-wrap: wrap;
  border-top: 1px solid var(--line); border-bottom: 1px solid var(--line);
  padding: 0.85rem 0; margin-bottom: 1.4rem;
}
.consist-rail-head { display: flex; align-items: baseline; gap: 0.6rem; flex: 0 0 auto; }
.consist-rail-title { font-family: var(--serif); font-weight: 600; font-size: 1.05rem; color: var(--ink); }

/* Groups the dot grid and the streak stats into one row, dot grid on the
   left, stats beside it. On the rail, display:contents drops this wrapper
   out of the box tree entirely, so .consist-rail's own flex row (head,
   grid, stats as direct children) is exactly what it already was - the
   grid and stats were already side by side there, this only changes the
   card placement, which used to stack them. */
.consist-body { display: flex; align-items: flex-start; gap: 1rem; }
.consist-rail .consist-body { display: contents; }

/* One column per week, one square per day. 9px (versus the netweight
   chart's nothing comparable) keeps a 12-26 week phase inside a third-width
   column; overflow-x lets a very long phase scroll rather than break the
   layout. Not entirely scoreless: a missed day (one a later logged day
   proves was actually skipped) reads red. The days since the last real
   entry (pending) and a future day (not yet happened) both stay a quiet
   neutral instead, since neither of those is a day that was skipped. */
.dot-grid { display: flex; gap: 3px; margin: 0.5rem 0; flex-wrap: nowrap; overflow-x: auto; padding-bottom: 3px; flex: 1 1 auto; min-width: 0; }
.dot-week { display: grid; grid-template-rows: repeat(7, 1fr); gap: 3px; }
.dot { width: 9px; height: 9px; border-radius: 2.5px; background: rgba(var(--ink-rgb), 0.08); flex: 0 0 auto; }
.dot--logged { background: var(--forest); }
.dot--missed { background: rgba(var(--terracotta-rgb), 0.55); }
.dot--pending { background: rgba(var(--ink-rgb), 0.18); }
.dot--future { background: transparent; border: 1px solid rgba(var(--ink-rgb), 0.10); }
.dot--outside { background: transparent; border: 1px dashed rgba(var(--ink-rgb), 0.14); }

.cstat-row { display: flex; gap: 1rem; }
/* Card placement: grid and stats now sit side by side (.consist-body
   above), so the stats stack in a narrow column next to the grid rather
   than stretching the card's width. Rail is unaffected (.consist-body's
   display:contents there leaves .cstat-row as a direct child of
   .consist-rail's own row, same as before). */
.consist-card .cstat-row { flex-direction: column; flex: 0 0 auto; gap: 0.9rem; }
.cstat { flex: 1; }
.cstat-value { font-family: var(--serif); font-weight: 600; font-size: 1.05rem; color: var(--ink); line-height: 1.1; }
.cstat-unit { font-family: var(--sans); font-size: 0.68rem; font-weight: 700; color: var(--ink-soft); margin-left: 0.25em; }
.cstat-label { font-size: 0.68rem; font-weight: 600; color: var(--ink-soft); margin-top: 0.1rem; }
/* Current streak's own comparison against the personal best (longest
   streak) - filled to current/longest, so a client can see at a glance how
   their current streak stacks up. Color-graded terracotta -> gold -> forest
   as the fill grows: both the width and the color-mix() background are set
   inline per-card in _consistency.html, since they depend on that
   request's own percentage (see intensity_bar_style in build_consistency,
   core/dashboard.py). Same red/green the dot grid already uses for a
   missed/logged day, so the two match up. */
.cstat-bar { height: 4px; border-radius: 2px; background: rgba(var(--ink-rgb), 0.1); margin-top: 0.35rem; overflow: hidden; }
.cstat-bar-fill { height: 100%; border-radius: 2px 0 0 2px; background: var(--gold); } /* fallback for browsers without color-mix() */
.cstat-bar-caption { font-size: 0.62rem; color: var(--ink-soft); margin-top: 0.25rem; }
/* At 100% the current run has matched the client's longest ever: a
   gold-tinted highlight sweeps across the fill on a loop, and a small
   star badge pops next to the caption text. Chosen after comparing five
   effect candidates in sandbox/consistency.html (ring ping, breathing
   dot, shimmer sweep, radial bloom, caption badge) - the sweep alone
   proved too easy to miss at this size until tuned slower and wider, so
   both pieces stayed together. The gold band is a plateau (two same-color
   stops, not one peak) so more of the bar reads bright at once; the
   10%/68% keyframe split keeps the sweep itself taking ~2.1s to cross
   while shortening the gap between repeats. */
.cstat-bar--best .cstat-bar-fill { position: relative; overflow: hidden; }
.cstat-bar--best .cstat-bar-fill::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(115deg, transparent 8%, rgba(var(--gold-rgb), 0.8) 35%, rgba(var(--gold-rgb), 0.8) 55%, transparent 82%);
  transform: translateX(-120%);
  animation: cstat-best-sweep 3.6s ease-in-out infinite;
}
@keyframes cstat-best-sweep {
  0%, 10% { transform: translateX(-120%); }
  68%, 100% { transform: translateX(120%); }
}
.cstat-best-badge {
  display: inline-flex; align-items: center; justify-content: center;
  width: 14px; height: 14px; margin-left: 0.35rem;
  border-radius: 50%; background: rgba(var(--gold-rgb), 0.2); color: var(--gold-dark);
  font-size: 9px; line-height: 1;
  animation: cstat-best-badge-pop 2.6s ease-in-out infinite;
}
@keyframes cstat-best-badge-pop {
  0%, 100% { transform: scale(1); }
  15% { transform: scale(1.35); }
  30% { transform: scale(1); }
}
/* The rail has more width to spare than the card, so its two stats get a
   wider gap than .cstat-row's default. */
.consist-rail .cstat-row { gap: 1.4rem; }

.note-card { display: flex; flex-direction: column; }
.note-body { color: var(--ink-body); font-size: 0.98rem; }
.next-checkin { margin-top: auto; padding-top: 1.2rem; }
.next-label { display: inline-block; font-weight: 800; text-transform: uppercase; letter-spacing: 0.08em; font-size: 0.7rem; color: var(--terracotta-text); margin-bottom: 0.3rem; }
.next-when { display: block; font-weight: 700; color: var(--ink); font-size: 0.95rem; }

.table-card { overflow: hidden; }
/* Capped to roughly 15 log rows (header + 15 * ~2.9rem row height) and
   scrolled internally past that - a real client's history runs to hundreds
   of rows, and letting the table grow to match made the whole card so tall
   that the scroll-reveal observer (see main.js, 12% visibility threshold)
   didn't fire until you'd scrolled deep into what looked like a dead patch
   of empty page. */
.table-scroll { overflow-x: auto; overflow-y: auto; max-height: calc(2.6rem + 2.9rem * 15); border: 1px solid var(--line); border-radius: var(--radius); }
.log-table { width: 100%; border-collapse: collapse; font-size: 0.92rem; min-width: 640px; }
.log-table th, .log-table td { padding: 0.7rem 0.9rem; text-align: right; white-space: nowrap; }
.log-table thead th { position: sticky; top: 0; z-index: 1; background-color: var(--cream-card); background-image: linear-gradient(rgba(var(--forest-rgb),0.10), rgba(var(--forest-rgb),0.10)); font-family: var(--sans); font-weight: 800; font-size: 0.74rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--forest); border-bottom: 1px solid var(--line); }
.log-table tbody tr:nth-child(even) { background: rgba(var(--ink-rgb),0.035); }
.log-table tbody td { border-bottom: 1px solid rgba(var(--ink-rgb),0.07); color: var(--ink-body); }
.log-table tbody tr:last-child td { border-bottom: none; }
.cell-empty { color: var(--line-strong); }
.table-foot { margin-top: 1rem; font-size: 0.85rem; color: var(--ink-soft); }

/* The weekly summary table (weekly-table, dashboard.html) shares .log-table's
   look: its first column is the macro label and its last is the Average, so
   those stay text-aligned left, and its second column (the first day in the
   week) keeps the original bold treatment. The "Your log" table below has its
   own semantic classes instead (log-col-text/log-col-em), since which column
   is text or emphasised no longer matches a fixed position once the column
   set is curated per sheet. */
.weekly-table th:first-child, .weekly-table td:first-child,
.weekly-table th:last-child, .weekly-table td:last-child { text-align: left; }
.weekly-table td:nth-child(2) { font-weight: 800; color: var(--ink); }

/* ---------- dashboard: "Your log" disclosure ---------- */
/* Reuses the FAQ accordion's marker/summary styling (see .faq-item above)
   rather than duplicating it, since <details class="log-card"> is the same
   native disclosure pattern. */
.log-card > summary { list-style: none; cursor: pointer; display: flex; justify-content: space-between; align-items: center; gap: 1rem; }
.log-card > summary::-webkit-details-marker { display: none; }
.log-card[open] .faq-mark::after { transform: rotate(90deg); opacity: 0; }
/* Collapsed, the card is just this two-line summary - .card's flat 1.6rem
   bottom padding plus .card-head's margin-bottom (meant for a body that
   follows) reads as dead space with nothing below it. Scoped to the closed
   state only, so the open card (a real table's worth of content) keeps its
   normal spacing. */
.log-card:not([open]) { padding-bottom: 0.9rem; }
.log-card:not([open]) > summary { margin-bottom: 0; }
/* Groups the download link with the disclosure arrow so <summary> still has
   exactly two flex children (title block, controls) and the space-between
   layout above needs no change. */
.log-card-controls { display: flex; align-items: center; gap: 0.6rem; flex: 0 0 auto; }
/* Icon-only and compact on purpose - this sits in a card header, not as a
   standalone CTA. Reachable whether the card is open or collapsed, since
   <summary> stays visible in both states. Click handling (stopping it from
   also toggling the <details>) lives in static/js/log-table.js. */
.log-card-download {
  display: inline-flex; align-items: center; justify-content: center;
  width: 26px; height: 26px; border-radius: 7px;
  color: var(--ink-soft); transition: background 0.15s ease, color 0.15s ease;
}
.log-card-download:hover, .log-card-download:focus-visible { background: rgba(var(--ink-rgb), 0.08); color: var(--ink); }
.log-card-download svg { width: 17px; height: 17px; }
.log-export-meta { margin-top: 0.6rem; }
.log-detail-field { margin: 1rem 0; }
/* th/td-qualified so these beat ".log-table th, .log-table td" and
   ".log-table tbody td" above on specificity, not just source order. */
.log-entries th.log-col-text, .log-entries td.log-col-text { text-align: left; }
.log-entries tbody td.log-col-em { font-weight: 800; color: var(--ink); }
.log-col-detail { display: none; }
.log-entries.is-detailed .log-col-detail { display: table-cell; }

/* ---------- dashboard: chart range + weekly summary ---------- */
.chart-range { display: flex; gap: 1rem; flex-wrap: wrap; align-items: end; margin-bottom: 1.2rem; }
.chart-range .field input, .chart-range .field select { padding: 0.55rem 0.7rem; }
/* The netweight chart's margin slider shares .field's label styling but
   needs the input itself to size like a slider, not a text box. */
.chart-range .field input[type="range"] { padding: 0; border: none; background: none; min-width: 140px; }

/* Weight-projection phase switcher (Total + one button per phase) - a row
   of .btn--ghost pills, with the selected one filled in like .btn--primary. */
.phase-switch { display: flex; gap: 0.5rem; flex-wrap: wrap; margin-bottom: 1.2rem; }
.phase-switch .btn { padding: 0.45rem 0.9rem; font-size: 0.85rem; }
.phase-switch .btn.is-active { background: var(--terracotta); color: #fff; border-color: var(--terracotta); }
.phase-switch .btn.is-active:hover { background: var(--terracotta-dark); }

.week-nav { display: flex; align-items: center; justify-content: space-between; gap: 1rem; flex-wrap: wrap; text-align: center; }
.week-nav .btn { flex: 0 0 auto; }
.btn--disabled { opacity: 0.4; pointer-events: none; }
/* Average is now the rightmost column (macros run as rows, days left-to-right),
   so highlight the last cell of every row instead of the old bottom row. */
.weekly-table thead th:last-child,
.weekly-table tbody td:last-child { background: rgba(var(--forest-rgb), 0.10); border-left: 2px solid var(--line-strong); text-align: right; }
.weekly-table tbody td:last-child { color: var(--ink); }
/* The generic .log-table bolds its 2nd column; in this transposed table that
   would arbitrarily bold the first day, so neutralize it here. */
.weekly-table td:nth-child(2) { font-weight: inherit; color: inherit; }

/* Coach-only 1/2/3 week-count control, sitting beside the card title so the
   card-head's height stays driven by the title's own line box rather than
   growing to fit a full-size .btn - deliberately smaller than .phase-switch's
   pills, which live on their own row with nothing to stay level with. */
.week-title-row { display: flex; align-items: center; gap: 0.6rem; flex-wrap: wrap; }
.week-count { display: flex; gap: 0.35rem; }
.week-count .btn { padding: 0.2rem 0.55rem; font-size: 0.78rem; line-height: 1; }
.week-count .btn.is-active { background: var(--terracotta); color: #fff; border-color: var(--terracotta); }
.week-count .btn.is-active:hover { background: var(--terracotta-dark); }

/* Stacked weeks in the coach's multi-week view (core/templates/core/
   _weekly_table.html) - each .week-block is one week's own table; the label
   above it only renders once there's more than one to tell apart. */
.week-block + .week-block { margin-top: 1.4rem; }
.week-block-label { font-size: 0.78rem; color: var(--ink-soft); text-transform: uppercase; letter-spacing: 0.08em; font-weight: 700; margin-bottom: 0.5rem; }

/* ---------- legal ---------- */
.legal { background: var(--parchment); padding: clamp(2.5rem, 1.5rem + 4vw, 4.5rem) 0 clamp(3rem, 2rem + 4vw, 5rem); }
.legal-title { font-size: clamp(2rem, 1.4rem + 2.5vw, 2.8rem); margin: 0.4rem 0 1.4rem; }
.legal-flag { background: rgba(var(--gold-rgb),0.14); border: 1px solid rgba(var(--gold-rgb),0.5); border-radius: var(--radius); padding: 1rem 1.2rem; font-size: 0.95rem; color: var(--ink-body); margin-bottom: 2rem; }
.legal h2 { font-size: 1.25rem; margin: 1.8rem 0 0.5rem; }
.legal p { color: var(--ink-soft); margin-bottom: 0.8rem; }

/* A long legal document (privacy policy) reads better with more room than
   the site's usual --narrow width, not less, and each rendered document
   (German, then English) numbers its own sections from 1 - the table of
   contents heading is the only h2 without an id, so h2[id] numbers real
   chapters only and skips it. */
[data-page="datenschutz"] .container--narrow,
[data-page="agb"] .container--narrow { max-width: 900px; }
.legal-doc { counter-reset: legal-chapter; }
.legal-doc h2[id] { counter-increment: legal-chapter; }
.legal-doc h2[id]::before { content: counter(legal-chapter) ". "; }

/* EN/DE toggle: same "row of pills, selected one filled in" pattern as the
   weight-projection phase switcher above (.phase-switch). JS sets
   data-active-lang on .legal-langs and these rules hide the other
   language. With no JS, the wrapper never gets the attribute, neither
   rule matches, and both languages stay visible - the policy is never
   hidden behind a script a visitor has no way to run. */
.lang-toggle { display: flex; gap: 0.5rem; margin-bottom: 1.2rem; }
.lang-toggle .btn { padding: 0.45rem 0.9rem; font-size: 0.85rem; }
.lang-toggle .btn.is-active { background: var(--terracotta); color: #fff; border-color: var(--terracotta); }
.lang-toggle .btn.is-active:hover { background: var(--terracotta-dark); }
.legal-lang-note { margin-bottom: 1.6rem; }
.legal-langs[data-active-lang="en"] .legal-doc[data-lang="de"] { display: none; }
.legal-langs[data-active-lang="de"] .legal-doc[data-lang="en"] { display: none; }

/* ---------- 404 ---------- */
.notfound { background: var(--parchment); text-align: center; padding: clamp(4rem, 3rem + 6vw, 7rem) 0; }
.notfound .seal--num { margin: 0 auto 1.5rem; width: 78px; height: 78px; font-size: 1.3rem; }
.notfound p { color: var(--ink-soft); max-width: 44ch; margin: 0.8rem auto 1.8rem; }

/* ---------- field-notes signup ---------- */
/* One partial (_subscribe.html), two grounds. The shared rules never name a
   colour: they read four locals that each variant sets once. That is what lets
   the same form sit on the footer's fixed dark forest and on the blog page's
   themed parchment without a second copy of the rules.

   Deliberately not folded into .field above. Those inputs assume the
   --input-bg/--ink pair, which the footer's ground does not survive (it is
   dark in the light theme too, hence the -fixed cream), and the layout here is
   an input/button row rather than a stacked label and control. */
.subscribe {
  display: grid; gap: 0.65rem;
  --sub-text: var(--ink);
  --sub-muted: var(--ink-soft);
  --sub-line: var(--line-strong);
  --sub-field-bg: var(--input-bg);
  --sub-accent: var(--terracotta);
  --sub-accent-rgb: var(--terracotta-text-rgb);
}
.subscribe-heading { font-family: var(--serif); font-size: 1.05rem; color: var(--sub-text); }
.subscribe-title { font-family: var(--serif); font-size: clamp(1.4rem, 1.1rem + 1.2vw, 1.9rem); color: var(--sub-text); }
.subscribe-body { color: var(--sub-muted); font-size: 0.92rem; max-width: 46ch; }

/* Honeypot (see core/forms.py). Off-screen rather than display:none or
   [hidden], both of which anything worth trapping knows to skip. Not the
   .visually-hidden utility above, which exists for the opposite purpose:
   that one keeps content available to screen readers, this one must be
   invisible to them too. */
.subscribe-hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

.subscribe-row { display: flex; gap: 0.6rem; flex-wrap: wrap; }
.subscribe-email { flex: 1 1 12rem; display: block; }
.subscribe-email input {
  font: inherit; width: 100%; padding: 0.7rem 0.9rem;
  border: 1.5px solid var(--sub-line); border-radius: 9px;
  background: var(--sub-field-bg); color: var(--sub-text);
}
.subscribe-email input::placeholder { color: var(--sub-muted); opacity: 0.75; }
.subscribe-email input:focus {
  outline: none; border-color: var(--sub-accent);
  box-shadow: 0 0 0 3px rgba(var(--sub-accent-rgb), 0.22);
}
.subscribe-row .btn { flex: 0 0 auto; }

.subscribe-consent { display: flex; align-items: flex-start; gap: 0.5rem; font-size: 0.8rem; color: var(--sub-muted); max-width: 46ch; }
.subscribe-consent input { flex: 0 0 auto; width: 15px; height: 15px; margin: 0.15rem 0 0; accent-color: var(--sub-accent); }
.subscribe-consent a { color: var(--sub-accent); text-decoration: underline; }

/* Footer variant: the fixed dark forest ground, so it uses the same -fixed
   cream the surrounding .footer-col rules do rather than themed --ink, and
   gold for the accent because terracotta is too close to the ground to read
   as a focus ring against it. */
.subscribe--footer {
  --sub-text: var(--cream-fixed);
  --sub-muted: rgba(var(--cream-fixed-rgb), 0.72);
  --sub-line: rgba(var(--cream-fixed-rgb), 0.28);
  --sub-field-bg: rgba(var(--cream-fixed-rgb), 0.09);
  --sub-accent: var(--gold);
  --sub-accent-rgb: var(--gold-rgb);
  margin-top: 1.5rem; max-width: 34ch;
}
.subscribe--footer .subscribe-consent a { color: var(--gold); }

/* Blog variant: a panel under the post grid, built like .waitlist-panel so
   the two read as the same family of block. */
.subscribe-section { background: var(--parchment-2); }
.subscribe--blog {
  --sub-accent-rgb: var(--terracotta-text-rgb);
  max-width: 640px; margin: 0 auto;
  background: var(--cream-card); border: 1px solid var(--line);
  border-radius: var(--radius-lg); box-shadow: var(--shadow-sm);
  padding: clamp(1.6rem, 1rem + 2vw, 2.4rem) clamp(1.3rem, 1rem + 1.5vw, 2rem);
}
.subscribe--blog .subscribe-body { margin-bottom: 0.5rem; }

/* ---------- footer ---------- */
.site-footer { background: var(--forest-deep); color: rgba(var(--cream-fixed-rgb), 0.85); }
.footer-inner { max-width: var(--maxw); margin: 0 auto; padding: clamp(2.5rem, 2rem + 3vw, 4rem) 24px 2rem; display: grid; grid-template-columns: 1.6fr 1fr 1fr 1fr; gap: 2rem; }
.footer-brand img { width: 64px; height: 64px; margin-bottom: 0.9rem; }
.footer-tag { font-size: 0.95rem; max-width: 34ch; margin-bottom: 1.1rem; color: rgba(var(--cream-fixed-rgb),0.78); }
.footer-brand .seal--sm { color: var(--gold); border-color: var(--gold); box-shadow: inset 0 0 0 2px var(--forest-deep), inset 0 0 0 3px rgba(var(--gold-rgb),0.3); }
.footer-col h3 { font-family: var(--serif); font-size: 1.05rem; color: var(--cream-fixed); margin-bottom: 0.9rem; }
.footer-col a { display: block; color: rgba(var(--cream-fixed-rgb), 0.8); padding: 0.28rem 0; font-size: 0.95rem; }
.footer-col a:hover { color: var(--gold); }
.footer-base { border-top: 1px solid rgba(var(--cream-fixed-rgb), 0.16); max-width: var(--maxw); margin: 0 auto; padding: 1.3rem 24px 1.8rem; display: flex; justify-content: space-between; gap: 1rem; flex-wrap: wrap; font-size: 0.82rem; color: rgba(var(--cream-fixed-rgb), 0.6); }

/* ---------- reveal animation ---------- */
.reveal { opacity: 0; transform: translateY(16px); transition: opacity 0.6s ease, transform 0.6s ease; }
.reveal.is-visible { opacity: 1; transform: none; }
/* static preview safety: never hide content if the observer doesn't run */
html.spa .reveal { opacity: 1 !important; transform: none !important; }

/* ---------- responsive ---------- */
@media (max-width: 900px) {
  .feature-grid { grid-template-columns: repeat(2, 1fr); }
  .results-grid { grid-template-columns: repeat(2, 1fr); }
  .assurance-grid { grid-template-columns: repeat(2, 1fr); }
  .footer-inner { grid-template-columns: 1fr 1fr; }
}
@media (max-width: 820px) {
  .nav-toggle { display: flex; }
  .nav-links {
    position: absolute; top: 100%; left: 0; right: 0;
    flex-direction: column; align-items: stretch; gap: 0;
    background: var(--parchment); border-bottom: 1px solid var(--line);
    padding: 0.5rem 24px 1.2rem; box-shadow: var(--shadow-md);
    display: none;
  }
  .nav-links.is-open { display: flex; }
  .nav-links a { padding: 0.75rem 0; border-bottom: 1px solid var(--line); }
  .nav-cta, .nav-cta--quiet { text-align: center; margin-top: 0.7rem; border-bottom: none !important; }
  .hero-inner { grid-template-columns: 1fr; text-align: center; }
  .hero-lede { margin-left: auto; margin-right: auto; }
  .hero-actions { justify-content: center; }
  /* On a phone the hero stacks, so badge + title + lede + buttons + trust
     line ran to roughly 1.3x the screen height - the results cards below
     had no chance of showing. The badge is the biggest single block, so
     that's where the space comes from. */
  .hero-badge { order: -1; margin-bottom: 0.9rem; }
  .badge-ring { width: min(170px, 42vw); box-shadow: 0 0 0 6px var(--parchment), 0 0 0 7px rgba(var(--terracotta-rgb),0.25), var(--shadow-md); }
  .badge-ring::after { inset: 9px; }
  .hero-lede { margin-bottom: 1.2rem; }
  .hero-trust { text-align: center; margin-top: 0.85rem; }
  .seal--float { display: none; }
  .step-grid { grid-template-columns: 1fr; }
  .proof-grid { grid-template-columns: repeat(2, 1fr); gap: 1.8rem 1.4rem; }
  .tier--featured { transform: none; }
  .band-inner, .coach-inner, .dash-grid { grid-template-columns: 1fr; }
  .coach-inner { text-align: center; }
  .coach-copy p { margin-left: auto; margin-right: auto; }

  /* Stat strip on mobile: one single horizontal bar (flex:1 1 0 shares the
     row equally and degrades correctly whether there are 2 stats, before
     the projection unlocks, or 4 - no empty trailing space either way),
     title above the number instead of label-left/value-right, taglines
     dropped except a short "recent" tag on Est. intake, and weight's trend
     collapses into a small green arrow + delta + kg next to the big number
     (core/dashboard.py only populates weight_delta_down_str on an actual
     decrease, so nothing renders there at all when weight is up).

     .stat-item:first-child/::last-child below are needed because the
     desktop rules above target those same pseudo-classes (higher
     specificity than a plain .stat-item) - a plain override here wouldn't
     win over them by source order alone. */
  .stat-strip { background: transparent; border: none; border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); flex-wrap: nowrap; }
  .stat-item {
    display: flex; flex-direction: column; align-items: flex-start;
    flex: 1 1 0; min-width: 0;
    padding: 0.6rem 0.4rem;
    border-left: none; border-right: 1px solid var(--line);
  }
  .stat-item:first-child { padding-left: 0.4rem; border-left: none; }
  .stat-item:last-child { border-right: none; }

  .stat-item-label { margin-bottom: 0.2rem; font-size: 0.6rem; letter-spacing: 0.04em; }
  .stat-item-label-full { display: none; }
  .stat-item-label-short { display: inline; }

  .stat-item-value { display: flex; align-items: baseline; flex-wrap: wrap; column-gap: 0.25em; font-size: 1.05rem; }
  .stat-item-unit { font-size: 0.68rem; margin-left: 0.1em; }

  .stat-item-note { display: block; flex-basis: auto; }
  .stat-item-note-full { display: none; }
  .stat-item-note-short {
    display: inline; margin-top: 0.15rem;
    font-size: 0.6rem; font-weight: 700;
    text-transform: uppercase; letter-spacing: 0.03em;
    color: var(--ink-soft);
  }

  .stat-item-trend-mobile {
    display: inline-flex; align-items: baseline; gap: 0.1em;
    color: var(--forest); font-family: var(--sans); font-weight: 700; font-size: 0.62rem;
  }
  .stat-item-trend-mobile-unit { font-size: 0.85em; }
}
@media (max-width: 520px) {
  body { font-size: 16px; }

  /* Phone fold budget. The hero stacks here, so it has to be cut hard for
     the results cards below to show at all. Measured on a 390x844 viewport,
     the three real costs were: the two CTAs wrapping onto separate rows
     (119px), a six-line lede (167px), and a two-line trust strip (44px) -
     not the logo, which was already down to 164px. */
  /* The sticky nav is part of the fold budget too - it measured 87px here,
     mostly the 44px brand mark plus its padding. */
  .nav-inner { padding: 0.45rem 20px; }
  .brand-mark { width: 34px; height: 34px; }
  .brand-name { font-size: 1.05rem; }

  .hero { padding: clamp(0.75rem, 1.6vh, 1.15rem) 0 clamp(0.7rem, 1.4vh, 1.05rem); }
  .badge-ring { width: min(112px, 30vw); box-shadow: 0 0 0 5px var(--parchment), 0 0 0 6px rgba(var(--terracotta-rgb),0.25), var(--shadow-sm); }
  .badge-ring::after { inset: 6px; }
  .hero-badge { margin-bottom: 0.55rem; }
  .hero-title { font-size: 1.8rem; margin-bottom: 0.55rem; }
  .hero-lede { font-size: 0.93rem; line-height: 1.45; margin-bottom: 0.9rem; }
  /* Shrink the buttons rather than hide one: at this size both labels fit a
     single row, which recovers a whole ~60px button row without taking the
     "See the programmes" path away from phone users. */
  .hero-copy .hero-actions { gap: 0.5rem; flex-wrap: nowrap; }
  .hero-copy .hero-actions .btn { padding: 0.68rem 0.85rem; font-size: 0.87rem; white-space: nowrap; }
  .hero-trust { font-size: 0.75rem; margin-top: 0.6rem; }
  .results--fold { padding-top: clamp(0.8rem, 1.6vh, 1.15rem); }
  .results--fold .section-title { font-size: 1.2rem; }
  .results--fold .results-grid { margin-top: 0.6rem; }

  .feature-grid { grid-template-columns: 1fr; }
  .results-grid, .assurance-grid { grid-template-columns: 1fr; }
  .phase-figure { flex-direction: column; }
  .phase-arrow { transform: rotate(90deg); }
  .footer-inner { grid-template-columns: 1fr; }
  .dash-head { align-items: flex-start; }
}

/* Short phones - iPhone SE class and the 740px-tall Androids. Keyed on
   viewport HEIGHT, because that's the actual constraint: even after the
   cuts above, the hero plus a card peek doesn't fit. The trust strip is the
   least load-bearing line in the hero (the same credentials are stated on
   /about and in the pricing FAQ), so it's what gives way to make room for a
   real client result. Taller phones - iPhone 12/13/14/15 at 844, Pro Max at
   932 - are above this cutoff and keep it. */
@media (max-width: 520px) and (max-height: 780px) {
  .hero-trust { display: none; }
}

/* ---------- reduced motion ---------- */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after { animation-duration: 0.001ms !important; transition-duration: 0.001ms !important; }
  .reveal { opacity: 1 !important; transform: none !important; }
}

/* ---------- focus visibility ---------- */
:focus-visible { outline: 3px solid rgba(var(--terracotta-text-rgb), 0.55); outline-offset: 2px; border-radius: 4px; }

/* ---------- booking page ---------- */
.book-intro { text-align: center; margin-bottom: clamp(1.8rem, 1rem + 3vw, 3rem); }
.book-intro .hero-lede { max-width: 46ch; margin-left: auto; margin-right: auto; }
.book-embed-wrap { max-width: 920px; }
/* The iframe's interior is served and styled by Google, so it will not follow
   the site theme - we only frame it to sit on the page like our own cards. */
.booking-embed {
  background: var(--cream-card);
  border: 1px solid rgba(var(--ink-rgb), 0.10);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-md);
  overflow: hidden;
}
.booking-embed iframe { display: block; width: 100%; border: 0; }
.booking-fallback {
  text-align: center;
  background: var(--cream-card);
  border: 1px solid rgba(var(--ink-rgb), 0.10);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  padding: clamp(1.8rem, 1rem + 3vw, 3rem);
}
.booking-fallback h2 { margin-bottom: 0.6rem; }
.booking-fallback p { color: var(--ink-soft); max-width: 44ch; margin: 0 auto 1.4rem; }
