/* =====================================================================
   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; }
/* The hidden attribute only carries `display: none` as a user-agent default,
   so any component rule setting display beats it and the element stays on
   screen: that is what shows a chat composer with no conversation behind it.
   Both the templates and the JavaScript here treat `hidden` as "do not show
   this", so it is made to mean that once, rather than per component. */
[hidden] { display: none !important; }
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;
  /* Fraunces is self-hosted as a variable font (fonts.css), so its
     optical-size axis responds to the rendered text size. WONK and SOFT are
     non-standard axes with no CSS-level default, and the font's own default
     is WONK 1, the "wonky" letterforms, so they are pinned to 0. */
  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); }
/* The home hero's eyebrow is two phrases joined by a middot. Wide, it is one
   line; narrow, a <br> carrying this class breaks it after the middot so the
   wrap lands between the phrases rather than inside the second one. */
.eyebrow-break { display: none; }
@media (max-width: 600px) { .eyebrow-break { display: inline; } }

.section-title {
  font-size: clamp(1.9rem, 1.2rem + 2.4vw, 2.9rem);
  letter-spacing: -0.01em;
}
.section-title--center { text-align: center; }
/* Only ever used on a fixed-dark ground (the band, the CTA band), so it
   takes the constant rather than the theme-following --parchment, which on
   the default dark theme is the near-black page colour. */
.section-title--light { color: var(--cream-fixed); }

/* ---------- 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); }
/* The forest fill, worn by the WhatsApp pill on the contact marker. It needs
   no theme rule of its own, and that is not luck: --forest is dark in the
   light theme and light in the dark one, and --parchment flips the opposite
   way, so the pair is legible in both (5.2:1 and 7.0:1). A fixed ink, the way
   .btn--gold takes one above, would fail one theme or the other.

   The hover darkens toward --ink rather than to a --forest-dark token, since
   there is no such token and one colour is not worth adding a pair to
   tokens.css for. The plain background line before it is the fallback for a
   browser without color-mix(), which is the same shape .cstat-bar-fill uses. */
.btn--forest { background: var(--forest); color: var(--parchment); box-shadow: var(--shadow-sm); }
.btn--forest:hover { background: var(--forest); box-shadow: var(--shadow-md); }
.btn--forest:hover { background: color-mix(in srgb, var(--forest) 86%, var(--ink)); }
.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); }
/* A compact variant for a control that sits inline in a row rather than
   terminating a block of text: a full-size button next to a select makes a
   one-line bar two lines tall. */
.btn--sm { padding: 0.38em 0.8em; font-size: 0.85rem; }
.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; }
/* The language switch, beside the theme toggle: a small, quiet link naming
   the other language. Outside .nav-links, so it stays visible on a phone
   without opening the menu. */
.nav-lang {
  font-weight: 700; font-size: 0.86rem; letter-spacing: 0.02em;
  color: var(--ink-soft); padding: 0.35rem 0.55rem; border-radius: 999px;
  border: 1px solid var(--line); transition: color 0.18s ease, border-color 0.18s ease;
}
.nav-lang:hover { color: var(--terracotta-text); border-color: var(--terracotta-text); }

.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; }
/* Same guard as the dark palette in tokens.css, so the icon matches the page it
   sits on even in the one state that has no attribute at all (JavaScript off). */
:root:not([data-theme="light"]) .theme-toggle-icon--sun { display: none; }
:root:not([data-theme="light"]) .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: 1fr; align-items: center;
}
.hero-copy { text-align: 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-copy .eyebrow { margin-bottom: 1.6rem; }
.hero-copy .hero-title {
  font-family: var(--display); font-weight: 600; font-variation-settings: normal;
  font-size: clamp(2.3rem, 1.3rem + 3.75vw, 3.75rem); letter-spacing: 0; line-height: 1.1;
}
.hero-copy .hero-title em { font-family: var(--serif-accent); font-style: italic; font-weight: 700; }
/* The German second line reads better in DM Serif Display than in Playfair.
   It has one weight, so 400: a 700 here would be a synthesised bold. */
.hero-copy .hero-title:lang(de) em { font-family: "DM Serif Display", var(--serif-accent); font-weight: 400; }
.hero-copy .hero-lede { max-width: none; margin-left: auto; margin-right: auto; margin-bottom: 2.2rem; }
.hero-actions { display: flex; flex-wrap: wrap; gap: 0.8rem; }
.hero-actions--center { justify-content: center; }
.hero-cta-note { margin-top: 0.7rem; font-size: 0.9rem; color: var(--ink-soft); font-weight: 500; }

/* ---------- obstacles ---------- */
.obstacles { background: var(--parchment-2); }
/* The eyebrow's type, set under a title rather than over it: the space sits
   above it instead of below, and the grid's own margin-top keeps the gap
   underneath. */
.underbrow {
  font-family: var(--serif); font-weight: 600; font-size: 0.78rem;
  letter-spacing: 0.18em; text-transform: uppercase;
  color: var(--terracotta-text); text-align: center; margin-top: 0.9rem;
}
/* No number: the items are not a sequence. The question is the card, so it
   is large and set first, with a short accent rule above it, and the body
   under it reads as its context. */
.obstacle-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1.5rem; margin-top: 2.6rem; }
.obstacle-card {
  background: var(--cream-card); border: 1px solid var(--line); border-radius: var(--radius-lg);
  padding: 1.9rem 1.7rem 2rem; box-shadow: var(--shadow-sm);
}
.obstacle-card::before {
  content: ""; display: block; width: 2.2rem; height: 3px; border-radius: 2px;
  background: var(--terracotta-text); margin-bottom: 1.2rem;
}
.obstacle-card h3 { font-size: clamp(1.35rem, 1.2rem + 0.5vw, 1.6rem); line-height: 1.2; margin-bottom: 0.8rem; text-wrap: balance; }
.obstacle-card p { color: var(--ink-soft); line-height: 1.55; }
.obstacles-cta { display: flex; justify-content: center; margin-top: 2.4rem; }

.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%; }
/* Already a circle in the file, so it is sized rather than shaped here: a
   little under the disc, which leaves a ring of the card colour between the
   photograph and the frame. Scoped to beat .coach-portrait img above. */
.coach-portrait .coach-photo { width: 92%; height: 92%; object-fit: cover; }
.coach-portrait .seal--sm { position: absolute; bottom: -8px; left: 50%; transform: translateX(-50%); background: var(--parchment); }

/* ---------- OTL Akademie seal ----------
   The awarding body's seal, exactly as drawn: the PNG is the artwork
   unmodified, and the colours are not ours to theme.

   The background is the one thing it needs and is load-bearing rather than
   decorative. The seal is drawn for a white page: its middle is transparent
   (only the lettering is white, so the mortarboard has nothing behind it) and
   its ring measures 1.01:1 against the dark page, which is no contrast at all.
   Dropped on the default theme with no ground under it, the ring and the cap
   both vanish and the lettering is left floating. --cream-fixed is the token
   that does not invert, so this stays the seal's paper in either theme; on the
   light page it is the page colour and reads as nothing.

   Everywhere it appears the credential is also written out in prose beside it,
   so the image is decorative and carries alt="". Do not give it alt text that
   restates the sentence next to it. */
.credential { display: grid; grid-template-columns: auto 1fr; gap: clamp(1.4rem, 4vw, 2.4rem); align-items: start; }
.credential-seal {
  width: clamp(104px, 13vw, 136px); height: auto;
  border-radius: 50%; background: var(--cream-fixed);
}
@media (max-width: 620px) {
  .credential { grid-template-columns: 1fr; gap: 1.3rem; }
}

/* Pinned to the portrait. The parchment disc behind it is the same trick the
   text chip above uses: it masks the portrait's forest border where the two
   circles overlap, so the seal reads as sitting in front rather than welded
   to the rim. */
.coach-portrait .credential-seal {
  position: absolute; right: -6px; bottom: 2px; width: 38%;
  box-shadow: 0 0 0 4px var(--parchment), var(--shadow-sm);
}
.coach-copy p { color: var(--ink-soft); margin-bottom: 1rem; max-width: 52ch; }
.coach-copy .btn { margin-top: 0.4rem; }

/* ---------- 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 obstacles 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);
}
/* Equal HEIGHT, natural width, which is the opposite of what a two-column grid
   would do and is the whole point.

   A before/after pair is matched by anatomy in the source files: the same
   landmarks at the same height in both frames, done by eye. That match only
   survives on the page if both images are DISPLAYED at the same height.
   Rendering them at equal width instead rescales two differently cropped
   frames to different body sizes, which turns a fair comparison into one that
   looks arranged. The widths are expected to differ, since a tighter
   horizontal crop on one frame is a normal result of matching the subject
   rather than the border.

   max-height plus max-width, with width and height both auto, can only ever
   shrink the image inside both bounds, so this cannot distort. The column is
   sized so max-height is the bound that binds; if it ever were not, both
   images would shrink and the pair would stay matched anyway, just smaller.

   The cost is no reserved box before the image loads, so a small layout shift,
   against the house habit of explicit width/height attributes. Reserving the
   box needs one shared aspect ratio, and one shared aspect ratio is exactly
   what a matched pair cannot promise. tools/crop_client_photos.py checks the
   source heights agree, since that is what all of this rests on. */
.result-figures { display: flex; align-items: flex-start; justify-content: center; gap: 0.7rem; }
.result-fig { margin: 0; display: flex; flex-direction: column; align-items: center; min-width: 0; }
.result-fig img {
  display: block; width: auto; height: auto;
  max-width: 100%; max-height: var(--result-fig-h, 300px);
  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-body { display: flex; flex-direction: column; gap: 1rem; flex: 1; }
.result-delta { display: flex; align-items: baseline; gap: 0.5rem; flex-wrap: wrap; border-bottom: 1px solid var(--line); padding-bottom: 0.9rem; margin: 0; }
.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); }
/* Sans, upright, at reading size, because Fraunces is a display face: fine at
   headline size, spidery at a paragraph's, and the quote is the longest run
   of text on the card. The serif stays on the delta above it, where it is one
   figure rather than a paragraph. */
.result-quote { margin: 0; font-family: var(--sans); font-style: normal; font-weight: 500; font-size: 1.02rem; line-height: 1.6; color: var(--ink-body); }
/* The maintenance line. Verdigris rather than forest so it reads as a
   different kind of claim from the delta above it: one is how far they went,
   this is that it held. */
.result-hold { margin: 0; font-size: 0.85rem; font-weight: 700; color: var(--verdigris-text); }
/* The signature. One block at tight leading, so the name, the line under it
   and the recommendation read as one thing; the body's gap is for the
   boundaries between the figure, the quote, the signature and the foot, not
   for the lines inside any of them. */
/* The name and detail head the card, above the photographs. */
.result-person { display: flex; flex-direction: column; gap: 0.15rem; line-height: 1.3; }
.result-person strong { color: var(--ink); font-size: 1.05rem; }
.result-person span { color: var(--ink-soft); font-size: 0.84rem; }
.result-recommends { margin: auto 0 0; font-size: 0.84rem; line-height: 1.45; color: var(--ink-soft); }
/* The foot: link left, incentive disclosure in the bottom-right corner. The
   disclosure is small and right-aligned but never hidden or lightened past
   --ink-soft: it is a disclosure. The link keeps its natural width and the
   disclosure wraps around what is left, so a short name does not leave the
   link broken over two lines. The margin pulls the foot up to the signature,
   since they belong together more than either belongs to the quote. */
.result-foot { display: flex; justify-content: space-between; align-items: flex-end; gap: 1rem; margin-top: -0.35rem; }
.result-more { margin: 0; flex: 0 0 auto; font-size: 0.86rem; }
.result-more a { color: var(--terracotta-text); font-weight: 700; }
.result-disclosure { margin: 0 0 0 auto; flex: 1 1 auto; min-width: 0; text-align: right; font-size: 0.72rem; line-height: 1.35; font-style: italic; color: var(--ink-soft); }

/* ---------- the whole card as the click target ----------
   A stretched pseudo-element rather than wrapping the <article> in an <a>.
   The anchor stays where it is, so it keeps its own accessible name and the
   card is announced as one link to "Read X's story" instead of as several
   paragraphs of link text; the ::after just grows it to the card's bounds.

   Trade-off worth knowing: the overlay sits above the text, so a reader
   cannot drag-select the quote. That is the standard cost of this pattern and
   is the right way round here, since the quote is there to be read and the
   card is there to be clicked. */
.result-card--linked { position: relative; transition: transform 140ms ease, box-shadow 140ms ease, border-color 140ms ease; }
.result-card--linked .result-more a::after { content: ""; position: absolute; inset: 0; }
.result-card--linked:hover { transform: translateY(-2px); box-shadow: var(--shadow-md); border-color: var(--line-strong); }
/* The inner link keeps the site's own focus ring (see the :focus-visible rule
   at the foot of this file), drawn around its text where a keyboard user is
   looking. The card echoes it, so which card is focused reads at a glance
   without a second ring competing with the first. */
.result-card--linked:focus-within { border-color: var(--forest); box-shadow: var(--shadow-md); }
.results-more { display: flex; justify-content: center; margin-top: 2.2rem; }

/* ---------- one story on its own ----------
   .results-grid is a hard three-column grid, which is right for a page of
   clients and wrong for the first one: a single card sits at a third width
   with two empty columns beside it, and reads as a section still waiting for
   content rather than as somebody's result. Turning the card side-on fills
   the row honestly, and the wider column is what makes room for the quote to
   be worth reading. */
.results-grid--solo { grid-template-columns: minmax(0, 760px); justify-content: center; }
/* Only a card that actually has photos goes side-on. Without the --figs guard
   a text-only story would put its words in the photo column. */
.results-grid--solo .result-card--figs {
  /* The photo column is auto rather than a fixed width, because how wide a
     pair is depends on how the client's photographs were framed: these two
     come to 230px at 430px tall, another pair at the same height could be
     150px or 300px. A fixed column set for one of them is dead space or a
     squeeze for the next. --result-fig-h caps the height, the images take
     their natural widths under it, and the column takes what they need. */
  --result-fig-h: clamp(320px, 32vw, 430px);
  display: grid; grid-template-columns: auto minmax(0, 1fr);
  gap: 1.6rem; align-items: center; padding: 1.7rem;
}
.results-grid--solo .result-card--figs .result-person { grid-column: 1 / -1; }
.results-grid--solo .result-quote { font-size: 1.12rem; }

/* ---------- the long-form story ----------
   Sits under the grid on /success-stories/, holding whichever story
   ?story=<slug> named. Prose measure, not card width: this is several
   paragraphs of one person talking, and the cards' 1fr column would run it
   well past a comfortable line length. */
.story-picker { display: flex; flex-wrap: wrap; gap: 0.5rem; justify-content: center; margin-top: 2.6rem; }
.story-tab {
  padding: 0.4rem 0.95rem; border-radius: 999px; font-size: 0.85rem;
  border: 1px solid var(--line-strong); color: var(--ink-soft); background: transparent;
}
.story-tab:hover { border-color: var(--forest); color: var(--forest); }
.story-tab.is-active { background: var(--forest); border-color: var(--forest); color: var(--cream-fixed); font-weight: 700; }
.story-long {
  max-width: 68ch; margin: 2.6rem auto 0;
  background: var(--cream-card); border: 1px solid var(--line);
  border-radius: var(--radius-lg); padding: clamp(1.4rem, 3vw, 2.4rem);
  box-shadow: var(--shadow-sm);
}
.story-long-head { display: flex; align-items: baseline; gap: 0.6rem; flex-wrap: wrap; margin-bottom: 1.1rem; }
.story-long-head .eyebrow { flex-basis: 100%; margin: 0 0 0.3rem; }
.story-long-head h3 { margin: 0; font-family: var(--serif); font-weight: 600; font-size: 1.5rem; line-height: 1.2; color: var(--ink); }
.story-long-head span { font-size: 0.85rem; color: var(--ink-soft); }
.story-long-foot { margin: 1.4rem 0 0; padding-top: 1rem; border-top: 1px solid var(--line); font-size: 0.78rem; font-style: italic; color: var(--ink-soft); }
.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; }

/* ---------- the fold panel ----------
   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.

   The width is set for the headline it carries, a question long enough to
   need the room to hold one line at desktop sizes. Below roughly 1150px the
   title's own clamp is still scaling it down with the viewport, so it keeps
   its line well past the point the panel stops growing. */
.fold-band { background: var(--parchment-2); padding-top: clamp(1.5rem, 3vh, 2.5rem); }
.fold-panel {
  max-width: 820px; 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.4rem, 0.9rem + 1.6vw, 2.2rem) clamp(1.15rem, 0.9rem + 1.2vw, 1.8rem);
}
.fold-panel .section-title { font-size: clamp(1.5rem, 1.1rem + 1.4vw, 2.1rem); }

/* Inside the panel: one row per objection a returning dieter arrives with,
   answer indented under its question.
   Deliberately left aligned inside the centred panel, since three centred
   two-line rows read as a poem rather than as a list. No breakpoint rule: the
   answer already sits under its question at every width, so there is nothing
   left to stack. The marker is drawn from borders rather than set as a "->"
   glyph so a screen reader is not handed an arrow between every pair; the
   <dl> already carries the pairing. */
.fold-rows { margin: 1.4rem auto 1.7rem; max-width: 54ch; text-align: left; }
.fold-rows dt { color: var(--ink); font-weight: 700; }
.fold-rows dd { position: relative; color: var(--ink-soft); margin: 0.3rem 0 1.25rem 1.35rem; }
.fold-rows dd:last-child { margin-bottom: 0; }
.fold-rows dd em { color: var(--ink); }
.fold-rows dd::before {
  content: ""; position: absolute; left: -1.3rem; top: 0.5em;
  width: 0.4rem; height: 0.4rem;
  border-right: 2px solid var(--terracotta-text);
  border-bottom: 2px solid var(--terracotta-text);
  transform: rotate(-45deg);
}

/* ---------- 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; }
/* The band sits on a fixed dark ground in both themes, which is why these two
   are literal white rather than a token: there is no light-mode counterpart
   to swap to. Same reason .cta-band .section-title above is #fff. */
.cta-fineprint { font-size: 0.85rem; color: rgba(255, 255, 255, 0.72); margin: -0.6rem auto 1.6rem; }

/* ---------- 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; }
/* The page head and the section under it share --parchment on every page that
   pairs them, so there is no seam between the two for a full section-worth of
   top padding to justify: it just reads as a screenful of nothing between the
   subheading and the first thing you can do. Trimmed by adjacency rather than
   on .section itself, which keeps its spacing where two differently coloured
   bands actually meet. A future page whose first section is not parchment
   wants the full padding back, so give that one its own rule instead of
   raising this one. */
.page-head + .section { padding-top: clamp(1.5rem, 1rem + 1.5vw, 2.5rem); }

/* ---------- 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; }
/* Stands in for the figure when prices are withheld, on the same element, so
   it MUST stay below .tier-price: the two selectors have the same specificity
   and this one wins only by coming second. Above it, the note loses every
   property the two share and renders at 3rem in the serif, shouting the
   missing price at everybody.
   Deliberately not headline sized: it is a note about where the figure comes
   from, not a figure. Body size though, not fine print: at .tier-compare's
   0.82rem it read as something being hidden, which is the opposite of the
   point. It is one ordinary sentence in the card's own voice, the size of
   .tier-blurb below it, so someone looking for a price reads it where the
   price would be and someone who is not scanning for one carries on to the
   blurb.
   Forest, chosen against --ink-soft, --ink-body and --terracotta-text in a
   sandbox: it is the one brand colour the card has not already spent, so the
   line reads as information rather than as a second call to
   action. Terracotta is what the ribbon, the featured border and the button
   are, and a sentence in it competes with the button underneath it. A token
   either way, so both themes follow with no second value to keep. Every card
   is in this state at once, so the grid stays aligned whatever height the
   line takes. */
.tier-price--tbc {
  font-family: var(--sans); font-weight: 400; color: var(--forest);
  font-size: 0.98rem; line-height: 1.45; margin: 1.1rem 0 0.2rem;
}
.tier-currency { font-size: 1.5rem; vertical-align: super; margin-right: 0.05em; }
/* German order: "229 €", the sign after the figure with a thin gap. */
.tier-currency--after { margin-right: 0; margin-left: 0.12em; }
.tier-period { font-family: var(--sans); font-size: 0.95rem; font-weight: 700; color: var(--ink-soft); }
/* ---------- billing switch ---------- */
/* Driven entirely by two hidden radios and the sibling combinator, no script.
   The radios live in .container as siblings of .billing-switch and .tier-grid,
   which is what makes every rule below reachable; moving them inside the
   switch would break all of it. */
.billing-switch {
  display: flex; justify-content: center; gap: 0.3rem; flex-wrap: wrap;
  /* No bottom margin here. .pricing-controls is a flex column, so a margin on
     a flex item stacks with the parent gap rather than collapsing into it,
     and the switch drifts a whole extra gap away from the toggle under it.
     The parent owns every bit of the spacing below this. */
  width: max-content; max-width: 100%; margin: 0 auto;
  padding: 0.3rem; background: var(--cream-card-2);
  border: 1px solid var(--line); border-radius: 999px;
}
.billing-option {
  display: inline-flex; align-items: center; gap: 0.5rem;
  padding: 0.6rem 1.2rem; border-radius: 999px; cursor: pointer;
  font-family: var(--sans); font-weight: 700; font-size: 0.92rem;
  color: var(--ink-soft); white-space: nowrap;
  transition: background 0.18s ease, color 0.18s ease;
}
.billing-option:hover { color: var(--ink); }
/* The selected segment reads as raised rather than as a colour fill. Both
   tokens flip with the theme together, so it keeps its contrast in dark and
   light without a second rule, which a fixed white-on-accent pair does not:
   --forest is light enough in dark mode that white text on it fails.

   Note the ".pricing-controls" step. These are sibling selectors rooted on
   the hidden radios, so they match by DOM POSITION and not by nesting: a new
   wrapper around these controls has to be named here too, or the selectors
   quietly stop matching and the page loses every trace of which segment is
   selected while the clicks go on working.
   PriceSurfaceTests.test_the_selected_billing_segment_is_actually_styled is
   what catches that. */
#billing-rolling:checked ~ .pricing-controls [for="billing-rolling"],
#billing-package:checked ~ .pricing-controls [for="billing-package"] {
  background: var(--cream-card); color: var(--ink); box-shadow: var(--shadow-sm);
}
/* The label carries the focus ring, since the input it belongs to is
   visually hidden and would otherwise focus invisibly. */
#billing-rolling:focus-visible ~ .pricing-controls [for="billing-rolling"],
#billing-package:focus-visible ~ .pricing-controls [for="billing-package"] {
  outline: 2px solid var(--terracotta); outline-offset: 2px;
}
/* Shared by the switch badge and the per-tier chip on the package card, so
   the two savings claims on the page look like the same claim. */
.billing-save, .save-chip {
  display: inline-block;
  font-family: var(--sans);
  font-size: 0.72rem; font-weight: 800; letter-spacing: 0.04em;
  text-transform: uppercase; padding: 0.25em 0.7em; border-radius: 999px;
  background: rgba(var(--gold-rgb), 0.22); color: var(--ink);
}
/* Terracotta rather than gold, so the founding marker reads as the same thing
   as the toggle that turned it on. Gold is already doing the "this is cheaper
   than the other arrangement" job on the 21-week chip, and one colour cannot
   mean two different savings on the same card. */
.save-chip--found {
  background: rgba(var(--terracotta-rgb), 0.20); color: var(--terracotta-text);
}

/* The swap, on two independent axes rather than one set of four combinations.
   .tier-mode--* chooses the billing arrangement; .tier-fig--* chooses the rate
   INSIDE whichever arrangement is showing. Keeping them orthogonal is what
   lets the 4-week/21-week choice stay available while the founding rate is on,
   and it means neither axis has to know the other exists.

   Both sides of both axes are always in the HTML; only one combination shows.
   A tier with no package keeps its per-period side in both states rather than
   emptying its own card out.

   The second selector in each pair catches the footnotes, which sit directly
   under the grid as siblings of the radios rather than inside a card. Card and
   footnote have to move together: a note explaining a price the reader cannot
   currently see is worse than no note. */
.tier-mode--package { display: none; }
#billing-package:checked ~ .tier-grid .tier:not(.tier--no-package) .tier-mode--rolling,
#billing-package:checked ~ .tier-mode--rolling { display: none; }
#billing-package:checked ~ .tier-grid .tier-mode--package,
#billing-package:checked ~ .tier-mode--package { display: block; }

.tier-fig--found { display: none; }
#founding-on:checked ~ .tier-grid .tier-fig--std,
#founding-on:checked ~ .tier-mode .tier-fig--std { display: none; }
#founding-on:checked ~ .tier-grid .tier-fig--found,
#founding-on:checked ~ .tier-mode .tier-fig--found { display: block; }
/* The switch badge is inline, so it needs its own pair: the block rule above
   would break a chip out of the label it sits in and onto its own line. */
.billing-save.tier-fig--found { display: none; }
#founding-on:checked ~ .pricing-controls .billing-save.tier-fig--found { display: inline-block; }
#founding-on:checked ~ .pricing-controls .billing-save.tier-fig--std { display: none; }

/* ---------- the founding control ---------- */
.pricing-controls {
  display: flex; flex-direction: column; align-items: center; gap: 1.1rem;
  margin-bottom: 2.4rem;
}
.founding-row { display: flex; flex-direction: column; align-items: center; gap: 0.45rem; }
.founding-toggle {
  display: inline-flex; align-items: center; gap: 0.7rem; cursor: pointer;
  padding: 0.5rem 1.1rem; border: 1px solid var(--line); border-radius: 999px;
  background: var(--cream-card); font-size: 0.9rem; font-weight: 700;
  color: var(--ink-soft); text-align: left;
}
.founding-toggle:hover { color: var(--ink); border-color: var(--line-strong); }
#founding-on:focus-visible ~ .pricing-controls .founding-toggle {
  outline: 2px solid var(--terracotta); outline-offset: 2px;
}
.founding-track {
  flex: 0 0 auto; width: 38px; height: 22px; border-radius: 999px;
  background: rgba(var(--ink-rgb), 0.20); position: relative;
  transition: background 0.18s ease;
}
.founding-track::after {
  content: ""; position: absolute; top: 3px; left: 3px; width: 16px; height: 16px;
  border-radius: 50%; background: var(--cream-card); box-shadow: var(--shadow-sm);
  transition: transform 0.18s ease;
}
#founding-on:checked ~ .pricing-controls .founding-toggle { color: var(--ink); border-color: var(--terracotta); }
#founding-on:checked ~ .pricing-controls .founding-track { background: var(--terracotta); }
#founding-on:checked ~ .pricing-controls .founding-track::after { transform: translateX(16px); }
/* Not fine print, and never behind the toggle: this line carries the place
   count, the duration and what is asked in return for the rate. A reader who
   has already flipped the control will not go hunting for them. */
.founding-hint { font-size: 0.82rem; color: var(--ink-soft); max-width: 56ch; text-align: center; }

/* The line under the headline figure. Body-coloured and not shrunk to
   fine-print size on purpose: it names a charge that is part of what you pay,
   and the whole reason it is on the card rather than in the footnote is that
   the big number alone is not the cost. */
/* Block, not flex: as a flex container the marker becomes a flex item and
   picks up the gap, which leaves the * floating a half-space from the word
   it belongs to. The chip carries its own spacing instead. */
.tier-extra {
  font-size: 0.92rem; color: var(--ink-body); margin: 0.5rem 0 0.2rem;
}
.tier-extra .save-chip { margin-right: 0.45rem; vertical-align: middle; }
/* The saving, named against what it is measured from. A sentence rather than
   a chip: "save €200" is a number with nothing attached, and a reader cannot
   tell what it is 200 less than. */
.tier-compare { font-size: 0.82rem; color: var(--ink-soft); margin-top: 0.5rem; }
/* The standard rate, shown beside a founding one. NOT struck through: a
   line-through reads as a former price and brings the reference-price rules
   with it. This is a comparison against a rate still on sale to everyone else. */
.tier-was { font-size: 0.85rem; color: var(--ink-soft); margin-top: 0.15rem; }
/* Footnote markers. Superscripted so they read as references rather than as
   part of the price, and given a hit area big enough to tap. */
.tier-mark {
  font-size: 0.85em; vertical-align: super; line-height: 1;
  color: var(--terracotta-text); font-weight: 800; text-decoration: none;
  padding: 0 0.15em;
}
a.tier-mark:hover { text-decoration: underline; }

.tier-blurb { margin: 1rem 0 1.2rem; color: var(--ink); font-size: 0.98rem; }
/* The starter package, set apart inside the card. Terracotta because it is the
   card's own accent (ribbon, featured border, primary button), so the box reads
   as "this part is different" rather than as a warning. Border and tint through
   --terracotta-rgb and the label through --terracotta-text: the same split
   .save-chip--found uses, and the reason the two tokens exist (see tokens.css).
   A flat hex here would keep the light value in dark mode. */
.tier-starter {
  border: 1px solid rgba(var(--terracotta-rgb), 0.45);
  border-radius: var(--radius);
  background: rgba(var(--terracotta-rgb), 0.06);
  padding: 0.85rem 1rem 0.95rem;
  margin-bottom: 1.1rem;
}
.tier-starter-label {
  font-size: 0.72rem; font-weight: 800; letter-spacing: 0.09em;
  text-transform: uppercase; color: var(--terracotta-text);
  margin-bottom: 0.6rem;
}
.tier-features { list-style: none; padding: 0; display: grid; gap: 0.7rem; margin-bottom: 1.6rem; }
/* The box supplies the gap below itself, so the inner list drops its own. */
.tier-features--starter { margin-bottom: 0; }
.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; }

/* Capped measure: these run the full grid width otherwise, and the payment
   note is long enough that a 1150px line is genuinely hard to track back. */
.tier-footnote {
  text-align: center; margin: 1rem auto 0; color: var(--ink-soft); font-size: 0.85rem;
  max-width: 74ch;
  /* The nav is position: sticky, so a :target lands flush against the top of
     the viewport and the first line of the note, the one line the marker was
     clicked to reach, is the one line the nav covers. The nav has no height
     property and no token, so this is measured rather than derived: 67px at
     desktop widths and 56px once the phone rules shrink the brand mark, which
     6rem clears with 29px and 40px to spare. */
  scroll-margin-top: 6rem;
}
/* The two price notes are paragraphs, not one-liners like the others here.
   Centring them would set several lines of required disclosure ragged on both
   edges, which is the least readable way to present the part a reader most
   needs to be able to actually read. */
/* The mode notes are paragraphs rather than one-liners, and every state has
   two of them, * and **. The first opens the block, the second belongs with
   it, and they are wrapped per state (.tier-mode > .tier-fig > these), so the
   adjacency is scoped by the wrapper and needs no state-specific selector. */
.tier-footnote.is-note { text-align: left; margin-top: 1.6rem; }
.tier-footnote.is-note + .tier-footnote.is-note { margin-top: 0.6rem; }

/* ---------- 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); }
/* Same reasoning as .auth-title + form above: the gap a paragraph between
   the heading and the button would have left. */
.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 and _feedback_card.html),
   written for any widget that wants one rather than for the netweight
   chart's. */
.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: 1.3rem; }
.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.7rem 0; margin-bottom: 1rem; }
.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: 1rem; margin-bottom: 1rem; }
/* 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: 1rem; }
.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); }
/* The portal is a dense page of a dozen cards read in one sitting, where the
   marketing pages are a few cards read one at a time, so it buys back the
   vertical space the roomier padding costs. Scoped rather than changed on
   .card itself for that reason. */
.dash .card { padding: 1.15rem 1.15rem; }
.dash .card-head { margin-bottom: 0.7rem; }
/* 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; }
/* Carries the h1/h2/h3 type so a card can title itself with a <p> where a
   heading would sit above the page's <h1> (the login page's rejected-account
   notice) and still look like every other card title. The properties are
   no-ops on the <h2> uses, which get them from the type rule above. */
.card-title { font-family: var(--serif); font-weight: 600; font-size: 1.3rem; line-height: 1.12; color: var(--ink); }
.card-hint { font-size: 0.78rem; color: var(--ink-soft); text-transform: uppercase; letter-spacing: 0.08em; font-weight: 700; }

/* The setup notice: the dashboard's "you haven't logged anything yet" block,
   the Telegram connect prompt (core/templates/core/_telegram_prompt.html),
   and the muted line a widget shows when it hasn't got enough to work with.

   Verdigris, which is not terracotta, forest or gold on purpose. Those three
   already mean warning, good and informational; a setup step is none of them.
   It is also the only cool hue on the site, so the block reads as a different
   kind of thing at a glance while staying a pigment from the same almanac
   palette (see tokens.css).

   Same 4px-edge-over-a-faint-tint construction as .card--notice above and
   .insights-stale-banner, in verdigris rather than terracotta: those mean
   "something is wrong" (no linked profile, sheet unreachable), and a
   brand-new client who simply hasn't tracked a day yet, or hasn't connected
   Telegram yet, is not an error. Reusing the shape rather than inventing one
   means it reads as a sibling of the site's existing notices, just in a
   different colour.

   .empty-line is the in-card version, for a widget that keeps its heading and
   swaps its body for one sentence (consistency below its day threshold, the
   net weight chart before there is a trend to draw). Mirrors .chat-empty,
   which does the same job inside the chat thread. */
.empty-notice {
  border: 1px solid rgba(var(--verdigris-rgb), 0.30);
  border-left: 4px solid var(--verdigris);
  background: rgba(var(--verdigris-rgb), 0.09);
  border-radius: var(--radius);
  padding: 1.1rem 1.3rem;
  margin-bottom: 1.6rem;
  display: flex; align-items: center; gap: 1.2rem 1.6rem; flex-wrap: wrap;
}
.empty-notice-label { font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.08em; font-weight: 800; color: var(--verdigris-text); margin: 0; flex: 0 0 auto; }
.empty-notice-body { flex: 1 1 22rem; }
.empty-notice-body h2 { font-size: 1.05rem; margin: 0 0 0.25rem; }
.empty-notice-body p { font-size: 0.9rem; color: var(--ink-soft); margin: 0; }
/* The button row on the Telegram prompt; the dashboard's welcome notice has
   no actions and never renders this. */
.empty-notice-actions { display: flex; align-items: center; gap: 0.6rem; flex: 0 0 auto; }
.empty-notice-actions form { margin: 0; }
.empty-line { color: var(--ink-soft); font-size: 0.9rem; margin: 0; }
/* 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, grid left, stats
   beside it. On the rail, display:contents drops this wrapper out of the box
   tree, leaving head, grid and stats as direct children of .consist-rail's
   own flex row: the rail was already side by side, and this only decides the
   card. */
.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; }
/* On the card the grid and the stats sit side by side (.consist-body above),
   so the stats stack in a narrow column beside the grid rather than
   stretching the card's width. The rail is untouched: .consist-body's
   display:contents leaves .cstat-row a direct child of its own row. */
.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 beside the caption. Both, because the sweep alone is easy to
   miss at this size (compared against four other candidates in
   sandbox/consistency.html). The gold band is a plateau, two same-colour
   stops rather than one peak, so more of the bar reads bright at once, and
   the 10%/68% keyframe split keeps the sweep itself taking about 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; }
.note-body p { margin: 0 0 0.6rem; }
.note-body p:last-child { margin-bottom: 0; }

/* The macros the coach recommended, sitting under the action points. The
   figures are read across, so they stay one row and scroll rather than
   reflowing into a block that reads as five separate facts. */
.plan-macros { margin-top: auto; padding-top: 1rem; }
.plan-macros-label { display: block; font-weight: 800; text-transform: uppercase; letter-spacing: 0.08em; font-size: 0.7rem; color: var(--terracotta-text); margin-bottom: 0.5rem; }
.plan-macro-row { display: flex; gap: 1.1rem; flex-wrap: wrap; }
.plan-macro-label { display: block; font-size: 0.68rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; color: var(--ink-soft); }
.plan-macro-value { font-family: var(--serif); font-weight: 600; font-size: 1.15rem; color: var(--ink); line-height: 1.1; }
.plan-macro-unit { font-family: var(--sans); font-size: 0.7rem; font-weight: 700; color: var(--ink-soft); margin-left: 0.15rem; }
.plan-source { margin: 0.9rem 0 0; font-size: 0.85rem; }

/* The coach's copy of the card. The fields are the card: locked they are
   drawn as the text a client sees, unlocked they grow the chrome of a form.
   Every rule below is therefore about hiding a control, never about laying
   the card out again. */
.plan-form { display: flex; flex-direction: column; flex: 1; }
.plan-head-side { display: flex; align-items: center; gap: 0.5rem; }
.plan-edit {
  display: inline-flex; align-items: center; justify-content: center;
  width: 24px; height: 24px; padding: 0; border: none; border-radius: var(--radius);
  background: none; color: var(--ink-soft); cursor: pointer;
}
.plan-edit svg { width: 15px; height: 15px; }
.plan-edit:hover { color: var(--ink); background: rgba(var(--ink-rgb), 0.06); }
.plan-form:not(.is-locked) .plan-edit { visibility: hidden; }

.plan-points {
  font: inherit; color: var(--ink-body); font-size: 0.98rem;
  width: 100%; resize: vertical; min-height: 4.2rem;
  padding: 0.5rem 0.6rem; border-radius: var(--radius);
  border: 1px solid var(--line-strong); background: var(--input-bg);
}
.plan-macro-input {
  font: inherit; color: inherit; width: 3.6rem;
  padding: 0.2rem 0.35rem; border-radius: var(--radius);
  border: 1px solid var(--line-strong); background: var(--input-bg);
}
/* Firefox and WebKit both need telling; a spinner inside a figure that reads
   as text is the thing that gives the input away. */
.plan-macro-input::-webkit-outer-spin-button,
.plan-macro-input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.plan-macro-input { -moz-appearance: textfield; appearance: textfield; }

.plan-form.is-locked .plan-points,
.plan-form.is-locked .plan-macro-input {
  border-color: transparent; background: none; padding-left: 0; padding-right: 0;
  cursor: default; resize: none;
}
.plan-form.is-locked .plan-points { min-height: 0; overflow: hidden; }
/* Shrink-to-fit so the unit hugs the figure the way it does on the client's
   card. Guarded: without field-sizing, `width: auto` on an input is about
   twenty characters, which would push the unit off on its own. */
@supports (field-sizing: content) {
  .plan-form.is-locked .plan-macro-input { width: auto; field-sizing: content; }
}
/* A macro nobody set is not a blank tile on a client's card, so it is not one
   here either until there is somewhere to type the figure. */
.plan-form.is-locked .plan-macro.is-unset { display: none; }
.plan-form.is-locked .plan-actions { display: none; }
.plan-actions { display: flex; gap: 0.5rem; margin-top: 0.9rem; }

.table-card { overflow: hidden; }
/* A header row plus fifteen body rows, so the log stops at a readable height
   and scrolls past that: a real client's history runs to hundreds of rows,
   and a card grown to match is a dead patch of page. Both figures are row
   heights and follow the cell padding on the rule below, so changing one
   moves this. */
.table-scroll { overflow-x: auto; overflow-y: auto; max-height: calc(2.2rem + 2.5rem * 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.5rem 0.75rem; 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 row 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: 0.9rem; }
.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: 0.9rem; }
.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 (weight and 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, .trend-switch { display: flex; gap: 0.35rem; }
.week-count .btn, .trend-switch .btn { padding: 0.2rem 0.55rem; font-size: 0.78rem; line-height: 1; }
.week-count .btn.is-active, .trend-switch .btn.is-active { background: var(--terracotta); color: #fff; border-color: var(--terracotta); }
.week-count .btn.is-active:hover, .trend-switch .btn.is-active:hover { background: var(--terracotta-dark); }
/* The coach's Smooth/Responsive switch on the netweight card. Centred on the
   row it shares with the week selects and the weekly-averages box, which
   .chart-range bottom-aligns. */
.trend-switch { align-self: center; align-items: center; }

/* 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; }
/* The Impressum and the privacy policy each carry a button to the other, set
   beside the title rather than under it so it does not read as part of the
   document. On a narrow screen it wraps below the title. */
.legal-head { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: flex-start; gap: 0 1.2rem; }
.legal-head__other { margin: 0.2rem 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 ---------- */
/* The page-not-found illustration carries its own heading ("error 404, page
   not found") as painted lettering, and its foreground was left empty on
   purpose so the control below it can be a real anchor sitting in that gap
   rather than more paint. Three things follow from that.

   The button stays on the picture at every width, and only the sentence moves.
   An empty foreground is what the art looks like unfinished, so the narrow
   layout that dropped both of them onto the page left a dead brown block
   sitting in the middle of the illustration - obvious in the light theme,
   where it does not blend into the page behind it. The sentence is the part
   that has to hold a reading size, so the sentence is the part that leaves.

   Sizes inside the picture are in container units, not viewport units. The
   plate caps at 1100px, so past that width a vw-based size would keep growing
   while the art behind it stopped, and the words would walk out of the gap.
   cqw tracks the rendered art exactly, at any viewport.

   And the switch is a container query rather than a media query for the same
   reason: what decides it is how wide the picture came out, not how wide the
   window is. */
.notfound { background: var(--parchment); text-align: center; padding: clamp(2.5rem, 1.5rem + 4vw, 4.5rem) 0; }

/* The three locals the rules below read, in the same shape .subscribe uses.
   The button's pair are the values sampled off the painting and never change,
   because the picture it sits on does not change either. Only the sentence's
   ink is handed back to the theme, and only once it has left the picture. */
.notfound-plate {
  --plate-ink: rgba(var(--cream-fixed-rgb), 0.85);
  --plate-btn-bg: var(--terracotta-fixed);
  --plate-btn-ink: var(--cream-fixed);
  container-type: inline-size; position: relative;
  max-width: 1100px; margin: 0 auto;
}
/* Wraps the picture alone, so the button's percentages stay measured against
   the art even once the sentence below has added height to the plate. */
.notfound-plate-frame { position: relative; }
.notfound-plate-art { display: block; width: 100%; height: auto; border-radius: var(--radius); }

.notfound-plate-note {
  position: absolute; left: 50%; top: 71%; transform: translateX(-50%);
  width: 46%; margin: 0;
  color: var(--plate-ink); font-size: 1.95cqw; line-height: 1.32;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.55);
}

/* A rounded body with a pointed tail masked out of each end. The path is the
   silhouette traced off the original artwork row by row, so the control reads
   as the one the illustrator drew rather than a rectangle parked on top of it.
   It is a mask and not a background image so the tails take --plate-btn-bg
   along with the body and the two cannot drift apart on hover. The fill inside
   the data URI is a stencil, not a colour - a mask reads alpha and discards
   hue - which is the one thing here standing outside the "every colour is a
   token" rule.

   Every measurement is in em, so one font-size scales the whole shape: body
   1.66em tall, tail 0.955em by 0.474em, corner 0.18em. Those are the
   painting's own proportions, which is why none of them are round. */
.ribbon-btn {
  --ribbon-tail: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 32"><path d="M16 0C10.2.2 4.4 8.6 0 16c4.4 7.4 10.2 15.8 16 16Z"/></svg>');
  position: absolute; left: 50%; top: 86%; transform: translate(-50%, -50%);
  display: inline-block; white-space: nowrap;
  background: var(--plate-btn-bg); color: var(--plate-btn-ink);
  font-family: var(--serif); font-weight: 700; text-transform: uppercase;
  font-size: 2.42cqw; line-height: 1; letter-spacing: 0.04em;
  padding: 0.33em 0.67em; border-radius: 0.18em;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.4);
  filter: drop-shadow(0 2px 3px rgba(0, 0, 0, 0.45));
  transition: background 0.2s ease;
}
.ribbon-btn::before, .ribbon-btn::after {
  content: ""; position: absolute; top: 50%;
  width: 0.474em; height: 0.955em;
  background: var(--plate-btn-bg);
  -webkit-mask: var(--ribbon-tail) center / 100% 100% no-repeat;
  mask: var(--ribbon-tail) center / 100% 100% no-repeat;
}
.ribbon-btn::before { left: -0.46em; transform: translateY(-50%); }
.ribbon-btn::after { right: -0.46em; transform: translateY(-50%) scaleX(-1); }
.ribbon-btn:hover { --plate-btn-bg: var(--terracotta-dark); }
/* The press has to restate the centring translate, since transform is one
   property and a second declaration replaces the first rather than adding. */
.ribbon-btn:active { transform: translate(-50%, -50%) translateY(1px); }
/* The global :focus-visible further down is a translucent terracotta, the one
   hue that vanishes against this button. Gold is the other ink the
   illustration already uses and carries against both the rust and the ground
   behind it. The radius is restated because that rule sets its own. */
.ribbon-btn:focus-visible { outline: 3px solid var(--gold); outline-offset: 4px; border-radius: 0.18em; }

/* Under 860px of art the gap is too short to hold a readable sentence: 1.95cqw
   lands below 16px. The sentence drops out of the picture to page sizes and
   page ink, the button stays where it was painted and re-centres in the gap
   the sentence left, and its size is bounded at both ends - floored so it
   stays a thumb-sized target on a phone, capped so it does not jump when the
   query releases at 860px. */
@container (max-width: 860px) {
  .notfound-plate-note {
    position: static; transform: none; width: auto; max-width: 44ch;
    margin: 1.4rem auto 0; color: var(--ink-soft);
    font-size: 1rem; text-shadow: none;
  }
  .ribbon-btn { top: 82%; font-size: clamp(1rem, 4.47cqw, 1.4rem); }
}

.notfound-aside { margin: 1.6rem 0 0; font-size: 0.95rem; color: var(--ink-soft); }
.notfound-aside a { font-weight: 700; color: var(--terracotta-text); }
.notfound-aside a:hover { text-decoration: underline; }

/* ---------- 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 .fold-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); }
.footer-fineprint { font-size: 0.78rem; color: rgba(var(--cream-fixed-rgb), 0.7); }

/* ---------- reveal animation ---------- */
.reveal { opacity: 0; transform: translateY(16px); transition: opacity 0.6s ease, transform 0.6s ease; }
.reveal.is-visible { opacity: 1; transform: none; }

/* ---------- responsive ---------- */
@media (max-width: 900px) {
  .feature-grid { grid-template-columns: repeat(2, 1fr); }
  .results-grid { grid-template-columns: repeat(2, 1fr); }
  /* After .results-grid, so it wins: a solo card matches both selectors, and
     half a row is the same lonely card the modifier exists to avoid. */
  .results-grid--solo { grid-template-columns: minmax(0, 1fr); }
  /* The card foot stacks: the link, then the disclosure under it. Side by
     side, the link keeps its natural width and the disclosure wraps in what
     is left, which on a phone-width card in German ("Die Geschichte von
     Natalia lesen" beside the reduced-rate note) was four lines pushed past
     the card's border. Here and not only at 520px because the two-column
     grid at tablet width makes a card narrower than a phone does. Left
     aligned under the link rather than kept in the corner: on a card this
     narrow the corner is the whole line. Still never hidden or lightened,
     it is a disclosure. */
  .result-foot { flex-direction: column; align-items: flex-start; gap: 0.3rem; }
  .result-more { flex: 0 1 auto; min-width: 0; }
  .result-disclosure { flex: 0 1 auto; margin-left: 0; text-align: left; }
  .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; }
  .hero-lede { margin-bottom: 1.2rem; }
  .step-grid, .obstacle-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; }

  /* The billing switch stops being a pill once it wraps: two rows inside a
     999px radius reads as an oval with text floating in it. Full width, two
     stacked segments, square-ish corners. */
  .billing-switch { width: 100%; border-radius: var(--radius-lg); }
  .billing-option { flex: 1 1 100%; justify-content: center; }

  /* 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); }
  .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 English labels
     fit a single row, which recovers a whole ~60px button row without taking
     the "See the programmes" path away from phone users.

     The row wraps, because the German pair ("Kostenloses Erstgespräch
     buchen" beside "Angebote ansehen") does not fit one: nowrap runs it past
     the right edge of the page with nothing scrollable, which reads as a bug
     rather than a long label. flex: 1 1 auto is what makes both cases look
     deliberate: a pair that fits shares one row, and a pair that does not
     becomes two full-width rows rather than a wide button over a narrow one.
     The labels themselves still never break mid-phrase. */
  .hero-copy .hero-actions { gap: 0.5rem; flex-wrap: wrap; }
  .hero-copy .hero-actions .btn { flex: 1 1 auto; min-width: 0; padding: 0.68rem 0.85rem; font-size: 0.87rem; white-space: nowrap; }
  .hero-cta-note { font-size: 0.8rem; margin-top: 0.5rem; }
  .hero-copy .eyebrow { margin-bottom: 1.1rem; }
  .hero-copy .hero-title { font-size: 2rem; }
  .hero-copy .hero-lede { margin-bottom: 1.3rem; }
  .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; }
  /* The home page's story teaser scrolls sideways on a phone. Stacked, three
     cards are most of a screen each and the section runs to three screens of
     scrolling before the rest of the page. A snap-scrolling row keeps the
     section one card tall, and each card is
     deliberately narrower than the viewport so a sliver of the next one
     shows at the right edge: that sliver is the only affordance, so it must
     stay visible (no card may be 100% wide here). The negative margin bleeds
     the row out to the viewport edges, and the padding puts the first card
     back on the container's own line; scroll-padding does the same for the
     snap points. Scoped to the fold section, so /success-stories/ keeps its
     stack of every story, and to grids with more than one card, since a
     solo card has nothing to scroll to. */
  .results--fold .results-grid:not(.results-grid--solo) {
    display: flex; gap: 0.9rem;
    margin-left: -24px; margin-right: -24px; padding: 0.25rem 24px 0.5rem;
    overflow-x: auto; overscroll-behavior-x: contain;
    scroll-snap-type: x mandatory; scroll-padding-inline: 24px;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
  }
  .results--fold .results-grid:not(.results-grid--solo)::-webkit-scrollbar { display: none; }
  .results--fold .results-grid:not(.results-grid--solo) .result-card {
    flex: 0 0 82%; scroll-snap-align: start; min-width: 0;
  }

  .feature-grid { grid-template-columns: 1fr; }
  .results-grid, .assurance-grid { grid-template-columns: 1fr; }
  /* Phone: the solo card stops being side-on. Two portrait photos beside a
     paragraph in 320px of width leaves neither of them readable, so the
     figures go back above the words as they are in the three-up card, with
     the room that gives them. */
  .results-grid--solo .result-card--figs {
    --result-fig-h: clamp(300px, 78vw, 420px);
    display: flex; flex-direction: column; gap: 1rem; padding: 1.5rem 1.4rem;
  }
  .results-grid--solo .result-quote { font-size: 1.02rem; }
  .phase-figure { flex-direction: column; }
  .phase-arrow { transform: rotate(90deg); }
  .footer-inner { grid-template-columns: 1fr; }
  .dash-head { align-items: flex-start; }
}

/* ---------- 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; }
.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);
}
/* Sits under the form embed on /book step one, saying what the next step is.
   Centred and quiet: it is a caption for the card above it, not a claim of
   its own. */
.book-step-note {
  margin: 1rem auto 0;
  max-width: 46ch;
  text-align: center;
  font-size: 0.85rem;
  color: var(--ink-soft);
}
.booking-fallback h2 { margin-bottom: 0.6rem; }
.booking-fallback p { color: var(--ink-soft); max-width: 44ch; margin: 0 auto 1.4rem; }

/* ---------- click-to-load embed ---------- */
/* Generic wrapper for core/templates/core/_click_to_load.html - the booking
   page is the first use, any future third-party embed reuses this as-is.
   The eventual iframe's height is reserved up front via the --embed-height
   custom property (set inline per instance), so swapping the card for the
   iframe on click never shifts the page.
   That height sits on the CARD, not on the wrapper: the wrapper also holds
   the role="status" region and the <noscript> fallback, so making it a flex
   container would put the fallback BESIDE the card rather than below it for
   anyone browsing without JS. */
.embed-consent iframe { display: block; width: 100%; border: 0; }
/* Added by static/js/main.js at the moment the iframe replaces the card, so
   the frame arrives with the embed and the two never stack.
   Both providers paint their own opaque background (core/booking.py says why
   Tally is not asked not to), and neither interior follows our theme,
   so an unframed embed lands on the page as a bare rectangle. Radius and
   overflow sit on this wrapper rather than on the iframe because border-radius
   does not clip a replaced element reliably. Clipping is safe here: the only
   other children are the visually-hidden status region and the <noscript>
   link, and that link only renders when this class is never added. */
.embed-consent--loaded {
  border: 1px solid rgba(var(--ink-rgb), 0.10);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  overflow: hidden;
}
.embed-consent__card {
  width: 100%;
  min-height: var(--embed-height, 700px);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  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);
}
.embed-consent__card h2 { margin-bottom: 0.6rem; }
.embed-consent__card > p { color: var(--ink-soft); max-width: 48ch; margin: 0 auto 1.4rem; }
/* The "external service" eyebrow uses the gold accent rather than the
   terracotta .card--notice uses, so it reads as an informational flag
   rather than a warning - nothing has gone wrong here. Colour only, the
   same way .eyebrow--light varies it: the base .eyebrow carries no padding
   or border-radius, so adding a background here would paint a tight band
   straight onto the letterforms instead of the chip it looks like in
   source. A real chip is what .seal is for. */
.embed-consent__card .eyebrow { color: var(--gold-dark); }
.embed-consent__alt, .embed-consent__link { font-size: 0.85rem; color: var(--ink-soft); margin-top: 0.9rem; }
.embed-consent__link { margin-top: 0.4rem; }

/* ---------- floating contact marker ---------- */
/* core/templates/core/_contact_marker.html, on the marketing pages and /book/.
   The only position:fixed element on the site.

   z-index 90 is deliberately BELOW .site-nav (100) and .skip-link (200), so
   the marker can never cover the navigation or the skip target. It is also
   below anything CookieYes renders, which is the right way round: a consent
   banner must not be obscured by a floating contact pill. main.js raises the
   marker clear of that banner through --contact-marker-bottom when one
   appears. */
.contact-marker {
  position: fixed;
  right: clamp(0.75rem, 2vw, 1.5rem);
  bottom: var(--contact-marker-bottom, clamp(0.75rem, 2vw, 1.5rem));
  z-index: 90;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 0.35rem;
  max-width: min(17.5rem, calc(100vw - 2rem));
}
/* The two pills, stacked, sharing a width. align-items: stretch on this
   wrapper rather than on .contact-marker itself: the note below must stay
   shrink-to-fit and right-aligned, and the wrapper is what lets the shorter
   label match the longer one instead of leaving a ragged left edge in a stack
   whose right edge is pinned to the viewport. */
.contact-marker__actions {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 0.35rem;
  width: max-content;
  max-width: 100%;
}
/* .btn--forest and .btn--primary carry the colour, so this adds geometry only
   and neither needs a dark-theme rule here.

   The font-size is what makes the pill about a fifth smaller than a .btn
   elsewhere on the site, and it is the only size set here on purpose: .btn's
   padding, its gap and the icon below are all em, so one declaration shrinks
   the whole pill in proportion rather than leaving a small label rattling
   around full-size padding. The border-radius stays at .btn's 10px, which
   reads as slightly rounder at this size and is the intended look. */
.contact-marker__btn { display: inline-flex; align-items: center; justify-content: flex-start; gap: 0.5em; font-size: 0.77rem; box-shadow: var(--shadow-lg); }
.contact-marker__icon { width: 1.1em; height: 1.1em; flex: 0 0 auto; }
/* The WhatsApp pill carries two labels and shows one: the full sentence
   here, the two-word one in the phone block below. */
.contact-marker__label--phone { display: none; }
/* The note is cut less far than the pills above it (about an eighth, not a
   fifth). It is the disclosure naming WhatsApp and Tally, so it has to stay
   comfortably readable; the bubble still shrinks with the marker's narrower
   max-width and its own tighter padding. */
.contact-marker__note {
  margin: 0;
  font-size: 0.68rem;
  line-height: 1.4;
  text-align: right;
  color: var(--ink-soft);
  background: rgba(var(--parchment-rgb), 0.92);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 0.3rem 0.5rem;
}
/* The global reset is `a { color: inherit; text-decoration: none }`, so
   without this the policy link in the note would not look like a link at
   all. Same treatment the site's other fine-print links get (see
   .auth-fineprint a). */
.contact-marker__note a {
  color: var(--terracotta-text);
  text-decoration: underline;
  text-underline-offset: 2px;
}
/* The note is never hidden, at any width. It names WhatsApp, and Tally where
   the booking pill reaches it, before either pill is pressed, and both can be
   pressed at every width the marker is visible at, so hiding it anywhere would
   mean somebody leaving the site for a third party without being told which
   one. Trimmed on small screens, never removed, since it has to share a
   phone screen with the pill above it.

   core/tests.py:ContactMarkerTests pins that no rule hides it. If that test is
   in your way, read the comment at the top of the marker IIFE in
   static/js/main.js before changing either. */
@media (max-width: 900px) {
  .contact-marker { gap: 0.26rem; max-width: min(14.5rem, calc(100vw - 1.5rem)); }
  .contact-marker__actions { gap: 0.26rem; }
  .contact-marker__note { font-size: 0.64rem; padding: 0.26rem 0.42rem; }
}
/* There is deliberately no rule hiding the marker itself on small screens.
   A short-phone cutoff hides it at ordinary Android sizes (360x640,
   412x732), which is exactly where the popup works best, and a phone is
   where a WhatsApp link is most at home.

   What a phone does lose is the booking pill: two stacked pills plus the
   note cover too much of the screen (Dan's call, judged on his own phone),
   and the booking link is still in the nav, the hero and the footer. Same
   cutoff as the hero's phone block (520px), so a tablet keeps both. The
   WhatsApp pill that stays swaps to its two-word label and loses a little of
   .btn's padding, and the note drops its Tally clause with the pill that
   clause described; the WhatsApp sentence around it stays, which is what
   ContactMarkerTests pins. */
@media (max-width: 520px) {
  .btn--primary.contact-marker__btn { display: none; }
  .contact-marker__tally { display: none; }
  .contact-marker__label { display: none; }
  .contact-marker__label--phone { display: inline; }
  .contact-marker__btn { font-size: 0.74rem; padding: 0.65em 1.05em; }
  .contact-marker {
    right: 0.6rem;
    bottom: var(--contact-marker-bottom, 0.6rem);
    max-width: min(12.5rem, calc(100vw - 1.2rem));
  }
}

/* ---------- Tally popup, phone sizing ---------- */
/* The only place this stylesheet reaches into another vendor's DOM, so it is
   worth saying exactly what is being overridden and why it is safe.

   Tally injects its own <style> at runtime and ships these two rules:

     @media (width <= 576px) {
       ._popupContainer_HASH iframe { max-height: 70vh }
       ._layoutModal_HASH           { max-width: calc(100% - 40px) }
     }

   That leaves a form on a phone sitting in 70% of the height with a 20px
   gutter each side, which is a lot of wasted screen for something that is the
   only thing on screen.

   Anchored on `.tally-popup`, which is a plain unhashed class Tally writes
   itself ("tally-popup ${layout} tally-form-${id}"). The rules being fought
   live on _popupContainer_1nqjn_204 and _layoutModal_1nqjn_188, whose hashes
   change on any Tally rebuild, so those names are deliberately not mentioned
   here.

   The failure mode if Tally restructures is benign: these selectors stop
   matching and the popup simply returns to Tally's own sizing. Nothing breaks.

   !important and the doubled attribute selector are both needed. Tally's style
   element is appended at runtime, so it comes after this file in source order
   and would win every specificity tie.

   Breakpoint deliberately matches Tally's own 576px, so this overrides exactly
   the rules it means to and nothing on a larger screen.

   TURNED OFF 19.08.2026, pending Tally's written OK. Their terms (Art. 5)
   require written permission to "modify or adapt any part of our Tool", and
   we are on the free plan. Asked rather than assumed. The rules are kept here
   rather than deleted because the answer may well be yes, and because what
   they do and why is the part that took the work to find out.

   Nothing here removes or obscures Tally branding, and nothing reaches inside
   the iframe (cross-origin, so it could not). Do not re-enable without the
   reply.

@media (max-width: 576px) {
  .tally-popup[class] { max-width: calc(100% - 8px) !important; }
  .tally-popup[class] iframe {
    max-height: 96vh !important;
    max-height: 96dvh !important;   / * dvh wins where supported; tracks the collapsing URL bar * /
  }
}
*/

/* ---------- coach insights ---------- */
.insights-controls { display: flex; align-items: center; gap: 0.6rem; margin-bottom: 1.2rem; }
.insights-controls .btn { padding: 0.4em 1em; }
.insights-caption { font-size: 0.85rem; color: var(--ink-soft); max-width: 62ch; margin: 0.8rem 0 1.6rem; }
.insights-table-wrap { overflow-x: auto; margin-bottom: 1.6rem; }
.insights-table { width: 100%; border-collapse: collapse; font-size: 0.9rem; }
.insights-table th, .insights-table td { text-align: left; padding: 0.55em 0.9em; border-bottom: 1px solid var(--line); white-space: nowrap; }
.insights-table th { font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.06em; color: var(--ink-soft); font-weight: 800; }
.insights-table td.num, .insights-table th.num { text-align: right; }
/* The campaign card's utm_content / utm_term pair, under the campaign name
   rather than as two more columns: both are usually blank, and eight nowrap
   columns would overflow a laptop. */
.insights-sub { display: block; font-size: 0.78rem; color: var(--ink-soft); white-space: nowrap; }
/* Same terracotta-accent shape as .card--notice, reused here for the
   stale-rollup warning so it reads consistently with every other on-site
   warning rather than inventing a new colour language for one banner. */
.insights-stale-banner { border-left: 4px solid rgba(var(--terracotta-rgb), 0.55); background: rgba(var(--terracotta-rgb), 0.06); padding: 0.9rem 1.2rem; border-radius: var(--radius); margin-bottom: 1.2rem; }
.insights-health { font-size: 0.82rem; color: var(--ink-soft); }
.insights-health dt { font-weight: 700; color: var(--ink); display: inline; }
.insights-health dd { display: inline; margin: 0 1.2rem 0 0.3rem; }

/* Analytics tab switcher. Same raised-segment recipe as .billing-switch
   further up, and for the same reason: both of its tokens flip with the theme
   together, so the selected tab keeps its contrast in dark and light without a
   second rule. A fixed white-on-terracotta fill (see .week-count above) does
   not, which matters more here because these are the page's own navigation.
   Ordinary classes on links, not the billing switch's sibling combinators:
   those match by DOM position and are rooted on hidden radios. */
.insights-tabs { display: flex; flex-wrap: wrap; gap: 0.3rem; width: max-content; max-width: 100%;
  margin: 0 0 1.4rem; padding: 0.3rem; background: var(--cream-card-2);
  border: 1px solid var(--line); border-radius: 999px; }
.insights-tab { display: inline-flex; align-items: center; padding: 0.5rem 1.1rem; border-radius: 999px;
  font-family: var(--sans); font-weight: 700; font-size: 0.88rem; color: var(--ink-soft);
  white-space: nowrap; transition: background 0.18s ease, color 0.18s ease; }
.insights-tab:hover { color: var(--ink); }
.insights-tab.is-active { background: var(--cream-card); color: var(--ink); box-shadow: var(--shadow-sm); }
.insights-tab:focus-visible { outline: 2px solid var(--terracotta); outline-offset: 2px; }

/* The client roster. Deliberately not a <table>: a <details> cannot wrap
   <tr>s, and the native disclosure is worth more here than table semantics,
   so each row is its own <details> and .people-head shares its
   grid-template-columns. The two must stay in step or the header stops
   lining up with the rows under it. */
.people-head, .person-summary { display: grid; align-items: center; gap: 0.9rem;
  grid-template-columns: minmax(11rem, 2fr) 7rem 9rem 5rem 7rem 18px; }
.people-head { font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--ink-soft); font-weight: 800; padding-bottom: 0.6rem; border-bottom: 1px solid var(--line); }
.person { border-bottom: 1px solid var(--line); }
.person > summary { list-style: none; cursor: pointer; padding: 0.75rem 0; }
.person > summary::-webkit-details-marker { display: none; }
.person[open] .faq-mark::after { transform: rotate(90deg); opacity: 0; }
.person-name { font-weight: 700; }
.people-head .num, .person-summary .num { text-align: right; }
.person-flag { display: inline-block; margin-left: 0.4rem; padding: 0.1rem 0.5rem; border-radius: 999px;
  font-size: 0.68rem; font-weight: 800; letter-spacing: 0.04em; text-transform: uppercase;
  white-space: nowrap; }
.person-flag--warn { background: rgba(var(--gold-rgb), 0.18); color: var(--ink); }
/* Neither a warning nor a good sign, so neither gold nor forest: an enquiry is
   a category, and a tinted ink chip reads as a label rather than a flag. */
.person-flag--enquiry { background: rgba(var(--ink-rgb), 0.10); color: var(--ink-soft); }
.person-dump { padding: 0.2rem 0 1.4rem; display: grid; gap: 1.2rem 2rem;
  grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr)); }
.person-group { font-size: 0.82rem; color: var(--ink-soft); }
.person-group h3 { font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--ink-soft); font-weight: 800; margin: 0 0 0.4rem; }
.person-group dl { margin: 0; }
.person-group dt { font-weight: 700; color: var(--ink); }
.person-group dd { margin: 0 0 0.35rem; word-break: break-word; }

@media (max-width: 720px) {
  /* The middle columns go rather than a sideways scroller: every value in
     them is repeated in the expanded panel below, so nothing is lost. */
  .people-head { display: none; }
  .person-summary { grid-template-columns: 1fr 18px; }
  .person-cell--meta, .person-summary .num { display: none; }
}

/* ---------- coach nav ---------- */
/* The "Coach view" dropdown in the top bar, rendered only for a coach account.
   See core/templates/core/_coach_nav.html for why it is a separate control
   from the site burger, and why it is a native <details> rather than a
   scripted menu.

   The open panel borrows the mobile drawer's recipe from the 820px block
   below: parchment ground, a hairline under each row, bold 0.96rem labels,
   active row in terracotta. It stays a right-aligned dropdown at every width
   rather than going full bleed on phones, because it hangs off a labelled
   button rather than an icon and reads as that button's menu. */
.coach-menu { position: relative; flex: 0 0 auto; }

.coach-toggle {
  list-style: none;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  padding: 0.5em 0.8em;
  border: 1.5px solid var(--line-strong);
  border-radius: 9px;
  font-weight: 700;
  font-size: 0.92rem;
  color: var(--ink);
  white-space: nowrap;
}
.coach-toggle::-webkit-details-marker { display: none; }
.coach-toggle:hover { color: var(--terracotta-text); border-color: rgba(var(--terracotta-rgb), 0.5); }
.coach-menu[open] > .coach-toggle {
  color: var(--terracotta-text);
  border-color: rgba(var(--terracotta-rgb), 0.5);
  background: rgba(var(--terracotta-rgb), 0.08);
}
.coach-caret { width: 14px; height: 14px; flex: 0 0 auto; transition: transform 0.18s ease; }
.coach-menu[open] .coach-caret { transform: rotate(180deg); }

.coach-menu-panel {
  position: absolute;
  top: calc(100% + 0.5rem);
  right: 0;
  z-index: 10;
  min-width: 210px;
  display: flex;
  flex-direction: column;
  background: var(--parchment);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 0.2rem 1rem 0.6rem;
  box-shadow: var(--shadow-md);
}
.coach-menu-panel a {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.6rem;
  padding: 0.75rem 0;
  border-bottom: 1px solid var(--line);
  font-weight: 700;
  font-size: 0.96rem;
  color: var(--ink);
}
.coach-menu-panel a:last-child { border-bottom: none; }
.coach-menu-panel a:hover { color: var(--terracotta-text); }
.coach-menu-panel a.is-active { color: var(--terracotta-text); }

.coach-nav-count {
  background: var(--terracotta);
  color: #fff;
  border-radius: 999px;
  font-size: 0.72rem;
  font-weight: 800;
  padding: 0.1em 0.55em;
}

/* ---------- coach chat ---------- */
.chat-layout { display: grid; grid-template-columns: 260px 1fr; gap: 1.2rem; align-items: start; }
.chat-list { padding-bottom: 0.6rem; }
.chat-people { list-style: none; margin: 0; padding: 0; }
.chat-person {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 0.6rem;
  padding: 0.7rem 0;
  border-bottom: 1px solid var(--line);
  font-size: 0.92rem;
  color: var(--ink);
}
.chat-people li:last-child .chat-person { border-bottom: none; }
.chat-person:hover { color: var(--terracotta-text); }
.chat-person.is-active { color: var(--terracotta-text); font-weight: 700; }
.chat-person-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.chat-person-meta { display: flex; align-items: center; gap: 0.35rem; flex: 0 0 auto; }
.chat-tag {
  font-size: 0.66rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  font-weight: 800;
  color: var(--ink-soft);
  border: 1px solid var(--line-strong);
  border-radius: 999px;
  padding: 0.05em 0.5em;
}
.chat-tag--warn { color: var(--terracotta-text); border-color: rgba(var(--terracotta-rgb), 0.45); }
.chat-unread {
  background: var(--terracotta);
  color: #fff;
  border-radius: 999px;
  font-size: 0.72rem;
  font-weight: 800;
  padding: 0.1em 0.5em;
}

/* The clients with no conversation, grouped under the list above. Their rows
   ARE .chat-person (they open the same panel on the same page, they just
   have no thread behind them), so only the disclosure around them needs
   rules of its own. It is the same native <details> recipe as
   .log-card > summary, which its own comment explains. */
.chat-offline { border-top: 1px solid var(--line); margin-top: 0.4rem; }
.chat-offline > summary {
  list-style: none;
  cursor: pointer;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 0.6rem;
  padding: 0.8rem 0 0.6rem;
  font-size: 0.92rem;
  font-weight: 700;
  color: var(--ink-soft);
}
.chat-offline > summary::-webkit-details-marker { display: none; }
.chat-offline > summary:hover { color: var(--ink); }
.chat-offline[open] .faq-mark::after { transform: rotate(90deg); opacity: 0; }

.chat-thread { display: flex; flex-direction: column; }
/* Capped height with its own scroller, the same shape as .table-scroll above
   and for the same reason: an unbounded list grows the page instead of the
   panel, which puts the composer below the fold exactly when it is needed. */
.chat-scroll {
  overflow-y: auto;
  height: clamp(300px, 52vh, 560px);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--parchment);
  padding: 0.9rem;
}
.chat-messages { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.5rem; }
/* Tinted surface, accent border, ink-body text in both variants: the same
   contrast rule .msg follows, where meaning is carried by the tint and never
   by the text colour, so both stay readable in either theme. */
.chat-msg {
  max-width: 78%;
  padding: 0.55rem 0.8rem;
  border-radius: var(--radius);
  border: 1px solid var(--line);
  font-size: 0.93rem;
  color: var(--ink-body);
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
}
.chat-msg--in {
  align-self: flex-start;
  background: rgba(var(--forest-rgb), 0.12);
  border-color: rgba(var(--forest-rgb), 0.32);
}
.chat-msg--out {
  align-self: flex-end;
  background: rgba(var(--terracotta-rgb), 0.12);
  border-color: rgba(var(--terracotta-rgb), 0.34);
}
/* Preserves the newlines someone typed on their phone without letting a long
   unbroken string push the bubble past its container. */
.chat-msg-text { white-space: pre-wrap; overflow-wrap: anywhere; }
.chat-msg-kind {
  font-size: 0.78rem;
  font-style: italic;
  color: var(--ink-soft);
}
.chat-msg-meta { font-size: 0.72rem; color: var(--ink-soft); align-self: flex-end; }
.chat-msg-failed { color: var(--terracotta-text); font-weight: 700; margin-left: 0.4rem; }
.chat-empty { color: var(--ink-soft); font-size: 0.9rem; }

.chat-composer { display: flex; gap: 0.6rem; align-items: flex-end; margin-top: 0.9rem; }
.chat-composer textarea {
  flex: 1 1 auto;
  resize: vertical;
  min-height: 2.8rem;
  padding: 0.6rem 0.8rem;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  background: var(--input-bg);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 0.95rem;
}
.chat-composer .btn { flex: 0 0 auto; }

/* ---------- telegram, coach bar and client row ---------- */
/* The bar and the row are rows, not cards. A status word plus a control does
   not have enough in it to justify a bordered box. Cards stay for things with
   real internal structure: the chart, the weekly table, the log. */

.coach-bar {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 0.8rem 1.2rem;
  padding: 0.9rem 1.2rem;
}
.coach-bar-pick { display: flex; align-items: center; flex-wrap: wrap; gap: 0.6rem; }
/* A column, so the link input lands directly under the button that produced
   it and the pair reads as one group on the right rather than a control up in
   the bar and a box floating under the whole thing. */
.coach-bar-telegram { display: flex; flex-direction: column; align-items: flex-end; gap: 0.5rem; }
.coach-bar-status { display: flex; align-items: center; gap: 0.6rem; flex-wrap: wrap; justify-content: flex-end; }
.coach-bar-meta { font-size: 0.85rem; color: var(--ink-soft); }
/* .card--notice gives every button a top margin so a stacked one clears the
   text above it. In a single-row bar that pushes the small buttons out of
   alignment with the select beside them. */
.coach-bar .btn { margin-top: 0; }
.coach-bar label { font-size: 0.9rem; color: var(--ink-soft); }

.connect-link-row { display: flex; justify-content: flex-end; width: 100%; }
/* Monospace because it is a credential being read back to a human, and a
   proportional font makes a long opaque token hard to check against whatever
   actually got pasted. */
.connect-link {
  width: min(26rem, 100%);
  padding: 0.4rem 0.65rem;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  background: var(--input-bg);
  color: var(--ink);
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.8rem;
}

/* The client's own row, near the foot of their dashboard. */
.telegram-row {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  flex-wrap: wrap;
  padding-top: 1.2rem;
  border-top: 1px solid var(--line);
  font-size: 0.9rem;
}
.telegram-row-meta { color: var(--ink-soft); flex: 1 1 auto; }
.telegram-row .btn { margin: 0; }

/* Chat collapses to a single column at the same 820px the dashboard grid and
   the nav drawer already use, rather than inventing a fourth breakpoint. The
   conversation list goes on top: on a phone you pick someone, then read. */
@media (max-width: 820px) {
  .chat-layout { grid-template-columns: 1fr; }
  .chat-scroll { height: clamp(260px, 46vh, 420px); }
  .chat-msg { max-width: 88%; }
  .automation-grid { grid-template-columns: 1fr; }
  .automation-row { grid-template-columns: 1fr; }
}


/* ---------- automated messaging panel ---------- */
/* Under the chat layout, full width, two columns: the weekly reminder and the
   one-off queue. A card rather than a row, unlike the coach bar above, because
   this genuinely has internal structure, two forms and a list.

   Almost all of it reuses .field, .check-field and .btn. What is here is the
   arrangement and the two things the rest of the site has no equivalent of: a
   preview of an outgoing message, and a queue with a control on each row. */

.automation { margin-top: 1.4rem; }
.automation-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 1.6rem 2rem; }
/* The reminder on its own, on the page for a client with no conversation to
   schedule anything in. Without this the single column stretches to half the
   page and the form fields go with it. The narrow-viewport rule further down
   already single-columns the pair, so this only bites on a wide screen. */
.automation-grid--solo { grid-template-columns: minmax(0, 34rem); }
.automation-col { display: grid; gap: 0.9rem; align-content: start; }
.automation-title { font-size: 1rem; margin: 0; color: var(--ink); }
.automation-subtitle { font-size: 0.85rem; margin: 0.4rem 0 0; color: var(--ink-soft); }
.automation-form { display: grid; gap: 0.9rem; margin: 0; }
.automation-row { display: grid; grid-template-columns: 1fr 1fr; gap: 0.9rem; }

/* Explanatory lines under a control: what the sheet currently says, where a
   link came from. Quieter than .note-body, which is a paragraph in its own
   right rather than a footnote to the input above it. */
.automation-note { font-size: 0.8rem; color: var(--ink-soft); margin: -0.4rem 0 0; }
.automation-warn { color: var(--ink-body); border-left: 3px solid var(--terracotta); padding-left: 0.7rem; }

/* The /stop notice, above both columns because an opt-out holds both halves of
   what this panel does. A stack rather than a card: it is one warned line, one
   button and a footnote, and boxing three lines would make more of it than a
   status deserves. */
.automation-stopped { display: grid; gap: 0.6rem; justify-items: start; margin-bottom: 1.3rem; }
.automation-stopped form { margin: 0; }

/* The message as it will actually arrive. Bordered and set apart because it is
   the one block on the page that is not a control: it is the consequence of
   the controls, and it is what somebody checks before switching this on. */
.automation-preview {
  display: grid;
  gap: 0.35rem;
  padding: 0.8rem 0.9rem;
  border: 1.5px dashed var(--line-strong);
  border-radius: 9px;
  background: var(--input-bg);
}
.automation-preview p { margin: 0; font-size: 0.9rem; color: var(--ink-body); white-space: pre-wrap; word-break: break-word; }

.automation-actions { display: flex; align-items: center; gap: 0.8rem; flex-wrap: wrap; }
.automation-status { font-size: 0.8rem; color: var(--ink-soft); }

.automation-queue { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.5rem; }
.automation-queue li {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 0.8rem;
  padding: 0.6rem 0.75rem;
  border: 1px solid var(--line);
  border-radius: 9px;
}
.automation-queue form { margin: 0; flex: 0 0 auto; }
.automation-queue--done li { opacity: 0.75; }
.automation-when { display: block; font-weight: 700; font-size: 0.8rem; color: var(--ink); }
.automation-text { display: block; font-size: 0.85rem; color: var(--ink-soft); word-break: break-word; }

/* --- Coach email portal (/coach/email/) ---------------------------------
   Three rules on top of .log-table, which is built for the dashboard's
   numeric log: right-aligned, nowrap, every column narrow. A subject line and
   a recipient address are neither, so they are turned back the other way here
   rather than by giving the table a different base class, which would mean
   restating the sticky header and the striping. */
.email-log th, .email-log td { text-align: left; white-space: normal; }
.email-log td:nth-child(2) { word-break: break-all; }

/* The stored message, shown as the record rather than as prose: the line
   breaks are part of what was sent. */
.email-body {
  font: inherit;
  font-size: 0.92rem;
  white-space: pre-wrap;
  word-break: break-word;
  margin: 0;
  padding: 0.9rem 1rem;
  border: 1px solid var(--line);
  border-radius: 9px;
  background: var(--input-bg);
  color: var(--ink-body);
}

/* Hashes are read a character at a time when they are read at all, so they get
   the monospace face and are allowed to break anywhere. */
.email-hash { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 0.78rem; word-break: break-all; }

/* The chosen attachments, one row each with a Remove button. Built entirely by
   static/js/email.js, so this list is empty and invisible without it, which is
   the intended no-JavaScript state rather than a gap. Same shape as
   .automation-queue above, which solves the same "a list of pending things,
   each with one action" problem. */
.email-attachments { list-style: none; margin: -0.4rem 0 0; padding: 0; display: grid; gap: 0.4rem; }
.email-attachment {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.8rem;
  padding: 0.5rem 0.7rem;
  border: 1px solid var(--line);
  border-radius: 9px;
}
.email-attachment-name { font-weight: 700; font-size: 0.85rem; color: var(--ink); word-break: break-all; }
.email-attachment-size { font-size: 0.8rem; color: var(--ink-soft); margin-left: auto; }
/* An over-size file is refused by the server, so it is worth more than a
   colour: the row says so in words. This just makes it findable at a glance. */
.email-attachment-over { color: var(--terracotta-text); font-weight: 700; }

/* ---------- the one-off consultation ---------- */

/* The panel beside the coaching grid on /pricing/, and the checkout page.

   THE LAYOUT RULE THAT MATTERS: the side-by-side arrangement is done with grid
   columns on .pricing .container, adding NO element to the DOM. Every rule of
   the billing switch above is a sibling combinator rooted on the hidden radios
   (#billing-package:checked ~ .tier-grid), so a wrapper around the panel and
   the grid would break all of them at once, and silently: the clicks would
   keep working, the figures would keep swapping, and the page would just stop
   showing which segment is selected.
   test_the_selected_billing_segment_is_actually_styled parses this file and
   checks real DOM siblinghood for exactly that reason.

   So .consult-panel and .tier-grid are placed into two columns of one row and
   everything else spans both. The radios are position:absolute via
   .visually-hidden, so they occupy no cell. */
.pricing .container {
  display: grid;
  grid-template-columns: minmax(0, 21rem) minmax(0, 1fr);
  gap: 1.5rem;
  align-items: start;
}
/* Full width by default: the switch, every footnote block, the payment note.
   Only the two below opt into a column. */
.pricing .container > * { grid-column: 1 / -1; }
.pricing .consult-panel { grid-column: 1; }
.pricing .tier-grid { grid-column: 2; }

/* One column below the point where a 21rem panel plus the cards stops fitting.
   The panel keeps its place in source order, so it reads first on a phone,
   which is the right order for the cheaper, lower-commitment thing. */
@media (max-width: 1080px) {
  .pricing .container { grid-template-columns: minmax(0, 1fr); }
  .pricing .consult-panel,
  .pricing .tier-grid { grid-column: 1 / -1; }
  .consult-panel { max-width: 380px; margin-inline: auto; }
}

/* Deliberately NOT a .tier. A different product under a different contract, so
   it is a different object on the page: squarer corners, a verdigris edge
   instead of the terracotta/line pair the cards use, and no ribbon. Someone
   should be able to tell at a glance that it is not a fourth coaching option. */
.consult-panel {
  align-self: start;
  background: var(--cream-card);
  border: 1px solid var(--verdigris);
  border-top: 4px solid var(--verdigris);
  border-radius: var(--radius);
  padding: 1.6rem 1.5rem;
  box-shadow: var(--shadow-sm);
}
.consult-panel-eyebrow {
  font-size: 0.72rem; font-weight: 800; letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--verdigris-text); margin: 0;
}
.consult-panel-title { font-size: 1.35rem; margin: 0.35rem 0 0; }
.consult-panel-price {
  font-family: var(--serif); font-weight: 600; font-size: 2.4rem; line-height: 1;
  margin: 0.9rem 0 0.3rem;
}
/* The § 19 UStG line, at readable size rather than fine print. It is a
   required disclosure and travels with every figure on every surface. */
.consult-panel-vat { font-size: 0.85rem; color: var(--ink-soft); margin: 0; }
.consult-panel-lede { font-size: 0.95rem; color: var(--ink); margin: 1rem 0 0; }
.consult-panel-points {
  list-style: none; margin: 1.1rem 0 0; padding: 0;
  display: grid; gap: 0.55rem; font-size: 0.9rem; color: var(--ink-body);
}
.consult-panel-points li { padding-left: 1.1rem; position: relative; }
.consult-panel-points li::before {
  content: ""; position: absolute; left: 0; top: 0.55em;
  width: 6px; height: 6px; border-radius: 50%; background: var(--verdigris);
}
.consult-panel-cta { display: block; text-align: center; margin-top: 1.3rem; }
/* One line under the button, used by the German panel to say the order page
   is in English. Absent from the English panel, which has nothing to add. */
.consult-panel-note { font-size: 0.82rem; color: var(--ink-soft); margin: 0.8rem 0 0; }

/* ---------- the checkout and the withdrawal form ---------- */

/* No .reveal anywhere on either page, so nothing here fades in. Every block on
   the checkout is either a required disclosure or the form itself, and text
   that qualifies a price should not be one network error from vanishing. */
/* What the call is for, above the order process. Same list as the panel on
   /pricing/, so it takes the panel's bullet styling rather than its own. */
.consult-points {
  list-style: none; margin: 0 0 1.8rem; padding: 0;
  display: grid; gap: 0.6rem; font-size: 0.98rem; color: var(--ink-body);
}
.consult-points li { padding-left: 1.2rem; position: relative; }
.consult-points li::before {
  content: ""; position: absolute; left: 0; top: 0.6em;
  width: 6px; height: 6px; border-radius: 50%; background: var(--verdigris);
}
.consult-optional { font-weight: 400; color: var(--ink-soft); }

.consult-methods {
  border: 1px solid var(--line); border-radius: var(--radius);
  padding: 1.1rem 1.3rem; background: rgba(var(--verdigris-rgb), 0.06);
}
.consult-methods-title { font-size: 1rem; margin: 0 0 0.6rem; }
.consult-method-list {
  list-style: none; margin: 0; padding: 0;
  display: flex; flex-wrap: wrap; gap: 0.4rem 0.5rem;
}
.consult-method-list li {
  font-size: 0.85rem; font-weight: 700; color: var(--ink-body);
  border: 1px solid var(--line); border-radius: 999px; padding: 0.3em 0.8em;
  background: var(--cream-card);
}
.consult-methods-note { font-size: 0.82rem; color: var(--ink-soft); margin: 0.7rem 0 0; }

.consult-form { margin-top: 1.8rem; display: grid; gap: 1.8rem; }
.consult-section-title { font-size: 1.15rem; margin: 0 0 0.9rem; }

.consult-price-block { border-bottom: 1px solid var(--line); padding-bottom: 1.5rem; }
.consult-price {
  font-family: var(--serif); font-weight: 600; font-size: 3rem; line-height: 1;
  margin: 0 0 0.35rem;
}
.consult-currency { font-size: 1.4rem; vertical-align: super; margin-right: 0.05em; }
/* German order: "90 €", the sign after the figure with a thin gap. */
.consult-currency--after { margin-right: 0; margin-left: 0.12em; }
.consult-vat { font-size: 0.9rem; color: var(--ink-soft); margin: 0; }

.consult-code { margin-top: 1.3rem; max-width: 24rem; }
.consult-code-row { display: flex; gap: 0.5rem; flex-wrap: wrap; }
.consult-code-row .consult-input { flex: 1 1 10rem; }

.consult-field { display: grid; gap: 0.35rem; margin: 0 0 1.1rem; max-width: 28rem; }
.consult-label { font-weight: 700; font-size: 0.9rem; color: var(--ink); }
.consult-input {
  font: inherit; color: var(--ink); background: var(--input-bg);
  border: 1px solid var(--line-strong); border-radius: 9px; padding: 0.65rem 0.8rem;
  width: 100%;
}
.consult-input:focus-visible { outline: 2px solid var(--verdigris); outline-offset: 1px; }
.consult-hint { font-size: 0.82rem; color: var(--ink-soft); }
.consult-error { font-size: 0.85rem; font-weight: 700; color: var(--terracotta-text); }
.consult-notice {
  border: 1px solid var(--terracotta); border-radius: var(--radius);
  background: rgba(var(--terracotta-rgb), 0.08);
  padding: 0.9rem 1.1rem; margin: 0 0 1.5rem; font-weight: 700;
}

/* Off-screen rather than display:none or hidden, both of which a modern bot
   checks for. aria-hidden and tabindex on the markup keep it away from anybody
   real. Same construction and the same reasoning as .subscribe-hp. */
.consult-hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

/* The documents, as one sentence of links. Accented and underlined, because
   on a page where almost nothing else is a link they have to read as ones:
   the whole point of the block is that each of them gets opened. */
.consult-docs-line { margin: 0; font-size: 0.92rem; line-height: 1.6; color: var(--ink-body); }
.consult-docs-line a {
  color: var(--verdigris-text); font-weight: 700;
  text-decoration: underline; text-underline-offset: 2px;
}
.consult-docs-line a:hover { color: var(--gold-dark); }
.consult-docs-alt { color: var(--ink-soft); }

/* The two withdrawal ticks. Roomy on purpose: these are long sentences that
   somebody is meant to actually read before deciding, not a row of terse
   options to skim past. */
.consult-choices { border: 0; margin: 0; padding: 0; }
.consult-check {
  display: grid; grid-template-columns: auto 1fr; gap: 0.7rem;
  align-items: start; margin-bottom: 1rem;
  font-size: 0.9rem; line-height: 1.55; color: var(--ink-body);
}
.consult-check input { margin-top: 0.28em; width: 1.05rem; height: 1.05rem; accent-color: var(--verdigris); }
.consult-check-note {
  font-size: 0.85rem; color: var(--ink-soft); margin: 0;
  border-left: 3px solid var(--verdigris); padding-left: 0.8rem;
}

/* § 312j Abs. 2 BGB: immediately above the button, prominent, and never
   collapsible. It is not a footnote and must not be styled as one. */
.consult-summary {
  border: 2px solid var(--ink); border-radius: var(--radius);
  padding: 1.2rem 1.4rem; background: var(--cream-card);
}
.consult-summary-line { margin: 0 0 0.6rem; font-weight: 700; color: var(--ink); }
.consult-summary-line:last-child { margin-bottom: 0; }

.consult-submit { display: flex; align-items: center; gap: 1.2rem; flex-wrap: wrap; }
.consult-total { font-size: 1.15rem; font-weight: 800; margin: 0; }
.consult-button { font-size: 1.02rem; }

.consult-summary-list { margin: 0 0 1.5rem; }
.consult-summary-list dt {
  font-size: 0.8rem; font-weight: 800; text-transform: uppercase;
  letter-spacing: 0.06em; color: var(--ink-soft);
}
.consult-summary-list dd { margin: 0 0 0.9rem; font-size: 1.02rem; color: var(--ink); }
.consult-reference { font-size: 0.95rem; color: var(--ink-soft); }

/* The one action on the scheduling page. Spacing only, no box: a status plus a
   button is a row, and there is nothing dense enough here to card. */
.consult-booking-row { margin: 1.6rem 0; }

/* The coach's test-checkout banner, on the real checkout page. Terracotta, the
   same colour the site already uses to say "read this before continuing", and
   deliberately louder than .consult-notice: the only other difference between
   this page and the one that takes real money is invisible. */
.consult-testbar {
  margin: 0 0 1.6rem; padding: 0.9rem 1.1rem; border-radius: var(--radius);
  border: 1px solid rgba(var(--terracotta-rgb), 0.55);
  background: rgba(var(--terracotta-rgb), 0.14);
  color: var(--ink-body); font-size: 0.95rem; line-height: 1.5;
}

/* Coach tools that belong to no particular client, on /coach/. A row, not a
   card: a label and a link is not dense enough to box. */
.coach-tool-row { margin: 0 0 1.4rem; font-size: 0.95rem; color: var(--ink-soft); }

/* § 356a BGB asks for the withdrawal function to be easy to find, and says
   that in a footer it counts only if it is visually set apart. So this is not
   the fourth grey link in the Legal column: it is boxed, and it must stay that
   way. Nothing but this rule keeps it distinguishable.

   .funnel-foot is the ad funnel's one-row footer (base_funnel.html), where
   the same link is owed and would otherwise be one plain link among three.
   Here rather than in funnel.css, so the rule that carries the requirement
   is one rule. */
.footer-col .footer-legal-cta,
.funnel-foot .footer-legal-cta {
  display: inline-block; margin-top: 0.5rem;
  font-weight: 800; color: var(--cream-fixed);
  border: 1px solid rgba(var(--cream-fixed-rgb), 0.45); border-radius: 999px;
  padding: 0.35em 0.9em;
}
.footer-col .footer-legal-cta:hover,
.funnel-foot .footer-legal-cta:hover { border-color: var(--gold); color: var(--gold); }

/* There is deliberately no rule for the withdrawal function in the top nav,
   because there is deliberately no link there. The footer entry above is what
   § 356a BGB is satisfied by; see the comment in base.html's nav. */
