/* ============================================================================
   TEMAN — prototype design system
   Light, warm and gentle: soft paper, fresh green, generous roundness.
   Product feel borrowed from everyday messaging apps (WhatsApp / WeChat):
   the conversation is the centre, and everything around it stays quiet.
   Fewer sentences, clearer pictures — charts carry the argument.

   Every class used by app.js is defined here — the JS generates markup
   against these names, so nothing may be renamed without changing both.
   ========================================================================== */

/* ------------------------------------------------------------- tokens --- */
:root {
  /* surfaces */
  --paper:      #fbfaf6;
  --surface:    #ffffff;
  --surface-2:  #f1f7f2;

  /* ink */
  --ink:        #24312c;
  --muted:      #5f6f68;
  --faint:      #8b9a92;

  /* greens */
  --pine:       #1f9d63;
  --pine-deep:  #157a4a;
  --pine-soft:  #e4f5ea;
  --wa:         #d9fdd3;   /* outgoing chat bubble — messaging-app green */

  /* warm accent */
  --clay:       #e0764a;
  --clay-soft:  #fdeee4;

  /* pastel door tints — the mindline.sg card language, softened */
  --tint-mint:   #e8f6ee;
  --tint-butter: #fdf4dc;
  --tint-sky:    #e8f2fb;
  --tint-lav:    #eef0fb;
  --tint-blush:  #fdeee6;

  /* the one strong call to action — an ink pill, calm against the pastels */
  --ink-cta:     #1b2a23;

  /* structure */
  --line:       #e9e6dd;
  --sand:       #f4f1e8;
  --focus:      #1f9d63;

  /* states */
  --ok:         #2f9d6b;
  --mild:       #c08a2e;
  --mid:        #d2762f;
  --high:       #c25a4a;

  /* type — Proza Libre throughout, the face mindline.sg sets its body in */
  --sans:  "Proza Libre", "Segoe UI", system-ui, -apple-system, sans-serif;
  --serif: "Proza Libre", "Segoe UI", system-ui, -apple-system, sans-serif;   /* display face */

  /* shape */
  --radius: 20px;
  --radius-sm: 13px;
  --wrap:   1120px;
  /* The landing page runs wider than the reading pages — its doors are a grid,
     not prose. Reading pages keep --wrap; only index.html opts into --wrap-wide. */
  --wrap-wide: 1440px;
  --shadow-sm: 0 1px 2px rgba(36, 49, 44, .05), 0 2px 8px rgba(36, 49, 44, .05);
  --shadow-md: 0 8px 28px rgba(36, 49, 44, .10);
}

/* -------------------------------------------------------------- base --- */
* { box-sizing: border-box; }
html { scroll-behavior: smooth; }
body {
  margin: 0;
  font-family: var(--sans);
  font-size: 16px;
  line-height: 1.6;
  color: var(--ink);
  background: var(--paper);
  -webkit-font-smoothing: antialiased;
}
h1, h2, h3, h4 { font-family: var(--serif); font-weight: 800; line-height: 1.22; letter-spacing: -0.02em; margin: 0 0 0.45em; }
h1 { font-size: clamp(2rem, 4.6vw, 3.1rem); }
h2 { font-size: clamp(1.5rem, 2.9vw, 2.1rem); }
h3 { font-size: 1.16rem; }
h4 { font-size: 1.02rem; }
p { margin: 0 0 1em; }
a { color: var(--pine-deep); text-decoration: none; }
a:hover { text-decoration: underline; }
img { max-width: 100%; }
hr { border: 0; border-top: 1px solid var(--line); margin: 2.5rem 0; }
::selection { background: var(--pine-soft); }
:focus-visible { outline: 3px solid rgba(31, 157, 99, .4); outline-offset: 2px; border-radius: 6px; }

.wrap { max-width: var(--wrap); margin: 0 auto; padding: 0 24px; }
.wrap--wide { max-width: var(--wrap-wide); }
.muted { color: var(--muted); }
.small { font-size: 0.875rem; }
.tiny { font-size: 0.8rem; }
.center { text-align: center; }
.mt0 { margin-top: 0; } .mb0 { margin-bottom: 0; }
.mt1 { margin-top: 0.75rem; } .mt2 { margin-top: 1.5rem; } .mt3 { margin-top: 2.5rem; }
.mb1 { margin-bottom: 0.75rem; } .mb2 { margin-bottom: 1.5rem; } .mb3 { margin-bottom: 2.5rem; }
.big { font-weight: 700; }

/* ------------------------------------------------------------ header --- */
.site-head {
  position: sticky; top: 0; z-index: 60;
  background: rgba(255, 255, 255, .9);
  backdrop-filter: blur(12px);
  border-bottom: 1px solid var(--line);
}
.site-head__in { display: flex; align-items: center; gap: 22px; height: 66px; }
.logo {
  display: inline-flex; align-items: center; gap: 9px; flex-shrink: 0;
  font-family: var(--serif); font-size: 1.4rem; font-weight: 800; color: var(--pine-deep);
}
.logo:hover { text-decoration: none; }
/* The favicon mark, promoted to the logo — two circles, one conversation. */
.logo__mark { width: 30px; height: 30px; flex-shrink: 0; }
.logo__word { display: inline-flex; align-items: baseline; gap: 7px; }
.logo .sub { font-family: var(--sans); font-size: .66rem; font-weight: 500; color: var(--faint); letter-spacing: .04em; white-space: nowrap; }
.ico { width: 16px; height: 16px; flex-shrink: 0; }
.head-cta { margin-left: auto; display: flex; align-items: center; gap: 10px; white-space: nowrap; }

/* The menu button. Everything that is not the chat lives behind it: the
   tools, the references, the 24/7 lines and sign-in. */
.menu-btn {
  display: inline-flex; align-items: center; gap: 10px;
  font-family: var(--sans); font-size: 0.9rem; font-weight: 600;
  color: var(--ink); background: var(--surface);
  border: 1px solid var(--line); border-radius: 999px;
  padding: 9px 18px 9px 16px; cursor: pointer;
  transition: border-color 0.12s, background 0.12s, color 0.12s;
}
.menu-btn:hover { border-color: #cfe8da; background: #f6fdf9; color: var(--pine-deep); }
.menu-btn__bars { display: inline-flex; flex-direction: column; gap: 3px; width: 16px; }
.menu-btn__bars i { display: block; height: 2px; border-radius: 2px; background: currentColor; }
.menu-btn__bars i:nth-child(2) { width: 74%; }
.menu-btn__bars i:nth-child(3) { width: 88%; }

/* The menu panel is a DROPDOWN, not a drawer (2026-10-10). It slid in from
   the right as a full-height sidebar; it now drops from under the button that
   opened it, the way a menu is expected to behave, and the page stays visible
   behind it rather than being covered by a scrim.

   The backdrop is kept but made invisible: it is a full-viewport click
   catcher, not a dimmer. Its job is the click-away, and for that it has to
   keep covering the page — what it must not do is darken a page the person is
   still reading.

   `right` is the alignment that matters. The bar's contents sit in a centred
   .wrap, so the button's right edge is (100vw - wrap)/2 from the viewport
   edge, not 0. Anchoring the panel at a flat 16px would hang it off the
   corner of the screen, away from the button on every desktop width; the
   max() keeps it inside the gutter once the viewport is narrower than the
   wrap. */
.menu-panel { position: fixed; inset: 0; z-index: 90; }
.menu-panel__backdrop { position: absolute; inset: 0; background: transparent; }
.menu-panel__in {
  position: absolute; top: 74px;
  right: max(16px, calc((100vw - var(--wrap)) / 2 + 24px));
  width: min(340px, calc(100vw - 32px));
  max-height: min(72vh, 620px);
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: 0 18px 46px rgba(27, 42, 35, .16);
  padding: 16px 18px 18px; overflow-y: auto;
  animation: menuDrop .16s cubic-bezier(.2, .7, .2, 1) both;
}
@keyframes menuDrop { from { transform: translateY(-7px); opacity: 0; } to { transform: none; opacity: 1; } }
@media (max-width: 620px) {
  .menu-panel__in { top: 66px; right: 12px; left: 12px; width: auto; max-height: 76vh; }
}
.menu-panel__head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 16px; }
.menu-panel__title { font-family: var(--serif); font-weight: 800; font-size: 1.15rem; color: var(--pine-deep); }
.menu-close {
  border: 0; background: transparent; font-size: 1.6rem; line-height: 1;
  color: var(--faint); cursor: pointer; padding: 0 4px; border-radius: 8px;
}
.menu-close:hover { color: var(--ink); }
.menu-list { display: grid; gap: 2px; }
.menu-list a { display: block; padding: 11px 12px; border-radius: var(--radius-sm); color: var(--ink); font-size: 0.98rem; font-weight: 500; }
.menu-list a:hover { background: var(--pine-soft); color: var(--pine-deep); text-decoration: none; }
.menu-list a.active { background: var(--pine-soft); color: var(--pine-deep); font-weight: 600; }
.menu-block { margin-top: 18px; padding-top: 18px; border-top: 1px solid var(--line); }
.menu-help {
  display: flex; align-items: center; gap: 9px; width: 100%;
  font-family: var(--sans); font-size: 0.95rem; font-weight: 600;
  color: #b3542b; background: var(--clay-soft);
  border: 1px solid transparent; border-radius: var(--radius-sm);
  padding: 12px 14px; cursor: pointer;
}
.menu-help:hover { background: #fbe0cf; }
.menu-help .ico { width: 17px; height: 17px; }
.menu-crisis { margin-top: 12px; background: var(--surface-2); border: 1px solid var(--line); border-radius: var(--radius-sm); padding: 14px 16px; }
.menu-crisis__lead { font-size: 0.85rem; margin: 0 0 12px; }
.menu-crisis__row { margin-bottom: 10px; font-size: 0.85rem; }
.menu-crisis__row:last-child { margin-bottom: 0; }
.menu-crisis__row .cn { display: block; font-weight: 700; color: var(--ink); }
.menu-crisis__row .cd { display: block; color: var(--muted); }
.menu-crisis__row .cn-note { display: block; font-size: 0.74rem; color: var(--faint); }
/* Crisis numbers are safety-critical. Underlined at rest, not on hover: on a
   phone there is no hover, and these must read as tappable, not as body text. */
.menu-crisis__row .cd a,
.crisis-card li a { color: var(--pine-deep); text-decoration: underline; text-underline-offset: 2px; font-weight: 600; }
/* The door from the menu's numbers to the page that explains them. Smaller
   than the numbers on purpose — the menu is for the number, the page is for
   the reading. */
.menu-crisis__more { display: inline-block; margin-top: 12px; font-size: 0.8rem; color: var(--pine-deep); text-decoration: underline; text-underline-offset: 2px; }
.menu-account { display: flex; align-items: center; gap: 9px; padding: 11px 12px; border-radius: var(--radius-sm); color: var(--ink); font-weight: 500; }
.menu-account:hover { background: var(--pine-soft); color: var(--pine-deep); text-decoration: none; }
/* The signed-in member's three doors, under the greeting (2026-10-10):
   Member, Payments, Sign out. Same item shape as the menu list one size
   down — the greeting is the way in, these are what is behind it. Empty
   for a guest and for the console shapes, and an empty box draws nothing. */
.menu-member { display: grid; gap: 2px; }
.menu-member:empty { display: none; }
.menu-member a,
.menu-member button {
  display: block; width: 100%; text-align: left;
  padding: 9px 12px; border: 0; background: none; border-radius: var(--radius-sm);
  color: var(--ink); font-family: var(--sans); font-size: 0.94rem; font-weight: 500; cursor: pointer;
}
.menu-member a:hover,
.menu-member button:hover { background: var(--pine-soft); color: var(--pine-deep); text-decoration: none; }

/* Phones: the bar keeps the logo and the menu button. */
@media (max-width: 620px) {
  .logo .sub { display: none; }
  .menu-btn__label { display: none; }
  .menu-btn { padding: 9px 14px; }
  .site-head__in { gap: 12px; }
}

/* ----------------------------------------------------------- buttons --- */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  font-family: var(--sans); font-size: 0.95rem; font-weight: 600;
  padding: 12px 22px; border-radius: 999px; border: 1px solid transparent;
  cursor: pointer; transition: transform 0.12s ease, box-shadow 0.15s ease, background 0.15s ease;
  text-decoration: none; line-height: 1;
}
.btn:hover { text-decoration: none; transform: translateY(-1px); }
.btn:active { transform: translateY(0); }
/* The primary action is an ink pill — mindline's calm contrast against the pastels.
   Pine stays the brand: logo, links, active states, the chat itself. */
.btn--primary { background: var(--ink-cta); color: #fff; box-shadow: 0 6px 16px rgba(27, 42, 35, .18); }
.btn--primary:hover { background: #2a3d33; box-shadow: var(--shadow-md); }
.btn--ghost { background: var(--surface); color: var(--ink); border-color: var(--line); }
.btn--ghost:hover { border-color: #cfe8da; background: #f6fdf9; color: var(--pine-deep); }
.btn--clay { background: var(--clay); color: #fff; }
.btn--clay:hover { background: #cf6539; }
.btn--small { padding: 8px 16px; font-size: 0.85rem; }
.btn--block { width: 100%; }
.btn[disabled] { opacity: 0.5; cursor: not-allowed; transform: none; }

.chip {
  display: inline-flex; align-items: center; gap: 6px;
  border: 1px solid var(--line); background: var(--surface);
  color: var(--ink); border-radius: 999px; padding: 9px 15px;
  font: inherit; font-size: 0.875rem; font-weight: 500; cursor: pointer;
  text-decoration: none;
  transition: border-color 0.12s, background 0.12s;
}
.chip:hover { border-color: #cfe8da; background: #f2fbf6; text-decoration: none; }
.chip--on { border-color: var(--pine); background: var(--pine); color: #fff; }
.chip--on:hover { border-color: var(--pine-deep); background: var(--pine-deep); color: #fff; }
/* The chat page's starting points, which are chips but not the hero's chips:
   square-cornered, spaced apart in the row rather than hugging, and a touch
   smaller so five of them fit a laptop transcript. These four declarations
   were an inline `style` attribute built in app.js until the Chinese scale
   needed to reach them — an inline style beats every selector, so a language
   block could only have overridden it with `!important`, and a size that can
   only be corrected by shouting is a size that will not be corrected.

   Two classes, not one: `button.chip { font: inherit }` sits further down this
   file and is the more specific selector, so a bare `.topic-chip` would set a
   font-size that shorthand then resets to the parent's 16px — which is exactly
   what it did for the length of one browser check. */
.chip.topic-chip {
  justify-content: space-between; padding: 10px 13px;
  border-radius: 11px; font-size: 0.84rem;
}

/* ------------------------------------------------------- application form --- */
/* A chip that wraps a checkbox. Without the checked rule the only sign a focus
   area had been picked was the native tick, which is easy to miss in a row of
   twelve. */
.chip-row { display: flex; flex-wrap: wrap; gap: 8px; }
.chip--pick input { margin: 0; accent-color: var(--pine); }
.chip--pick:has(input:checked) {
  border-color: var(--pine); background: var(--pine-soft); color: var(--pine-deep); font-weight: 600;
}
.consent-row {
  display: flex; align-items: flex-start; gap: 10px; cursor: pointer;
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: var(--surface); padding: 14px 16px; margin-top: 18px;
}
.consent-row input { margin: 3px 0 0; flex: none; accent-color: var(--pine); }

/* ---------------------------------------------------------- booking ------- */
/* The counsellor picker. Two columns rather than three: these are not shop
   cards, they are people, and the credential and the fee are the two things
   anyone actually compares. */
.bk-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
button.bk-pick {
  font: inherit; color: inherit; text-align: left; cursor: pointer; padding: 16px;
  border: 1px solid var(--line); background: var(--surface); border-radius: var(--radius);
  transition: transform 0.15s ease, box-shadow 0.2s ease, border-color 0.2s ease;
}
button.bk-pick:hover { transform: translateY(-3px); box-shadow: var(--shadow-md); border-color: #d5ecdd; }
button.bk-pick.on { border-color: var(--pine); box-shadow: 0 0 0 2px var(--pine-soft); }

/* The day strip scrolls sideways on a phone instead of wrapping into six rows —
   a week is a horizontal thing, and a wrapped grid of dates is read wrong. */
.bk-days { display: flex; gap: 8px; overflow-x: auto; padding: 2px 2px 8px; }
.bk-day {
  font: inherit; cursor: pointer; flex: none; width: 74px; padding: 10px 6px;
  display: flex; flex-direction: column; align-items: center; gap: 1px;
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: var(--surface); color: var(--ink); line-height: 1.15;
}
.bk-day:hover { border-color: #cfe8da; background: #f2fbf6; }
.bk-day.on { border-color: var(--pine); background: var(--pine); color: #fff; }
.bk-day__dow { font-size: 0.68rem; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; opacity: 0.75; }
.bk-day__num { font-size: 1.25rem; font-weight: 700; }
.bk-day__mon { font-size: 0.7rem; opacity: 0.75; }
/* How many hours that day has left. The one number that stops somebody opening
   six days to find the seventh. */
.bk-day__n { font-size: 0.62rem; opacity: 0.6; margin-top: 2px; }
.bk-times { display: flex; flex-wrap: wrap; gap: 8px; }
button.chip { font: inherit; cursor: pointer; }

@media (max-width: 640px) {
  .bk-grid { grid-template-columns: 1fr; }
}

.tag {
  display: inline-block; font-size: 0.72rem; font-weight: 600; letter-spacing: 0.03em;
  color: var(--pine-deep); background: var(--pine-soft);
  border-radius: 999px; padding: 4px 11px;
}
.tag--clay { color: #b3542b; background: var(--clay-soft); }
.tag--muted { color: var(--muted); background: var(--sand); }
.tag--ok { color: #1c7a4b; background: var(--pine-soft); }
.tag--warn { color: #8a6414; background: #fdf3d8; }
.tag--stop { color: #a03e30; background: #fbe9e5; }

/* -------------------------------------------------------------- hero --- */
.hero {
  padding: 72px 0 60px;
  background:
    radial-gradient(880px 400px at 10% -10%, #e2f6ea 0%, rgba(226, 246, 234, 0) 62%),
    radial-gradient(700px 340px at 96% 4%, #fdf0e6 0%, rgba(253, 240, 230, 0) 60%);
}
.hero__grid { display: grid; grid-template-columns: 1.15fr 0.85fr; gap: 52px; align-items: center; }
.hero h1 { margin-bottom: 0.35em; }
.hero .lede { font-size: 1.1rem; color: var(--muted); max-width: 34em; }

/* The landing page's first section is the chat's own furniture — the mark on
   the right, the box under it — so the page reads as the thing it is selling.
   The section already spans the viewport; .wrap--wide is what keeps the copy
   readable inside it, and the entry row takes that full width on purpose. */
.hero--entry .hero__grid { align-items: center; margin-bottom: 40px; }
/* The mark is a link now, so the square is on the STAGE and not on the
   container. `.hero__bot` used to carry `aspect-ratio: 1` because the mark was
   the only thing in it; with a caption underneath, the same rule would make
   the box taller than the mark and the halos — which measure off `inset: 5%`
   of their nearest positioned ancestor — would swell to enclose the caption
   too. The stage is that ancestor, and it is exactly the mark. */
.hero__bot { width: min(100%, 300px); justify-self: center; }
.hero__bot-link { display: block; text-decoration: none; color: inherit; }
.hero__bot-stage { position: relative; display: block; aspect-ratio: 1; }
/* The chat rail sizes the mark off its column; here it fills the stage.
   The halos are deliberately not in this rule: they are positioned by `inset:
   5%` off their own edges, and giving them a width as well anchors them at
   left: 5% and oversizes them, which reads as a ring sliding off the mark.
   Everything else about the beat — the halos' inset, the timings, the
   reduced-motion override — is the rail's, unchanged. */
.hero__bot .bot__mark { width: 100%; height: 100%; }
/* Under the mark, and always visible. It was tempting to show it on hover —
   it would keep the hero clean — but hover does not exist on a phone, and the
   whole reason this line exists is that people did not know the mark was a
   way in. A hint only half the visitors can see answers nothing. */
.hero__bot-cta {
  display: block; margin-top: 6px; text-align: center;
  font-size: 0.86rem; font-weight: 600; color: var(--pine);
}
.hero__bot-link:hover .hero__bot-cta { color: var(--pine-deep); text-decoration: underline; }
/* Ringed rather than beehived: a box-shadow follows the stage's square, so the
   focus ring is drawn as a rounded square that stays off the halos. */
.hero__bot-link:focus-visible { outline: none; }
.hero__bot-link:focus-visible .hero__bot-stage {
  border-radius: 24px; box-shadow: 0 0 0 4px var(--pine-soft);
}

.entry { max-width: 720px; margin: 0 auto; }
.entry__row {
  display: flex; align-items: center; gap: 10px;
  padding: 7px 7px 7px 20px; border-radius: 999px;
  background: var(--card); border: 1.5px solid var(--line);
  box-shadow: var(--shadow-sm);
  transition: border-color .18s ease, box-shadow .18s ease;
}
.entry__row:focus-within { border-color: var(--pine); box-shadow: 0 0 0 4px var(--pine-soft); }
/* 16px, and it stays 16px at every width: below that iOS Safari zooms the whole
   page the moment the box takes focus, which on a phone looks like the layout
   breaking. The chat composer is pinned for the same reason. */
.entry__input {
  flex: 1; min-width: 0; font: inherit; font-size: 16px; color: var(--ink);
  background: none; border: 0; outline: none; padding: 12px 0;
}
.entry__input::placeholder { color: var(--faint); }
.entry__go {
  flex: none; display: grid; place-items: center; width: 46px; height: 46px;
  border: 0; border-radius: 50%; cursor: pointer;
  background: var(--pine); color: #fff; transition: background .18s ease, transform .18s ease;
}
.entry__go:hover { background: var(--pine-deep); }
.entry__go:active { transform: scale(.94); }
.entry__go .ico { width: 21px; height: 21px; }
.entry__note { margin: 12px 0 0; text-align: center; color: var(--faint); font-size: 0.82rem; }
.entry__chips { display: flex; flex-wrap: wrap; justify-content: center; gap: 8px; margin-top: 14px; }

@media (max-width: 880px) {
  .hero { padding: 40px 0 34px; }
  .hero__grid { grid-template-columns: 1fr; gap: 34px; }
  /* Stacked, the copy leads and the mark follows it — the mark is the hook on
     a wide screen, but on a phone the sentence is what has to be read first. */
  .hero--entry .hero__grid { margin-bottom: 26px; }
  /* The BOX widens so the caption under the mark has room to sit on one line.
     Sized off the stage instead, "Continue your conversation" would wrap to two
     and a half lines and read as a paragraph rather than a label.

     THE STAGE IS SIZED OFF THE VIEWPORT NOW, not fixed at 132px (2026-10-10).
     132 was chosen when the mark was decoration; it became the way into the
     chat, and on a phone it was the smallest thing on a screen whose whole
     argument is that mark. The clamp grows it on a phone — 132px measured 74px
     of visible green, because the viewBox keeps a margin around the circles —
     and stops at 200px before the caption's own width starts driving the
     container instead of the stage. `vw` and not a percentage: the container is
     shrink-to-fit, so a percentage would be circular. */
  .hero__bot { width: auto; max-width: 260px; }
  .hero__bot-stage { width: clamp(160px, 48vw, 200px); margin: 0 auto; }
  .hero__bot-cta { font-size: 0.8rem; }
  .entry__row { padding: 5px 5px 5px 16px; }
  .entry__go { width: 42px; height: 42px; }
}


/* ----------------------------------------------------------- sections --- */
.section { padding: 62px 0; }
.section--tint { background: var(--surface-2); }
/* Was the dark green banner — now a soft mint band with dark ink. */
.section--deep {
  background: linear-gradient(165deg, #e6f6ec 0%, #f4fbf6 100%);
  border-top: 1px solid #ddefe4;
  border-bottom: 1px solid #ddefe4;
}
.section--deep .card { border-color: #ddefe4; }
.section_head { max-width: 44rem; margin-bottom: 32px; }
.section_head p { color: var(--muted); font-size: 1.02rem; }

.grid { display: grid; gap: 22px; }
.grid--2 { grid-template-columns: repeat(2, 1fr); }
.grid--3 { grid-template-columns: repeat(3, 1fr); }
.grid--4 { grid-template-columns: repeat(4, 1fr); }
/* A pair, capped at the width three used to fill and centred in it. The home
   page shows what the pricing page shows minus the free tier, and with the
   audio session gone that is two cards — which under `grid--3` left a ~470px
   hole where the third column had been. Capped rather than stretched: two equal
   columns across the 1392px wrap are 684px each, and a plan card that wide
   stops reading as a card. 940px puts each one back at ~459px, within a couple
   of pixels of the width the free tier and Care used to have. */
.grid--pair { grid-template-columns: repeat(2, 1fr); max-width: 940px; margin: 0 auto; }
@media (max-width: 980px) { .grid--4 { grid-template-columns: repeat(2, 1fr); } .grid--3 { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 700px) { .grid--2, .grid--3, .grid--4, .grid--pair { grid-template-columns: 1fr; } }

/* -------------------------------------------------------------- cards --- */
.card {
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 26px; box-shadow: var(--shadow-sm);
}
.card h3 { margin-bottom: 0.35em; }
.card p:last-child { margin-bottom: 0; }
.card--lift { transition: transform 0.15s ease, box-shadow 0.2s ease, border-color 0.2s ease; }
.card--lift:hover { transform: translateY(-3px); box-shadow: var(--shadow-md); border-color: #d5ecdd; }

/* ----------------------------------------------------------------- doors --- */
/* The mindline.sg card language: a tinted door with no border, a one-line
   promise, a small mark, and an arrow. Whole-card links; arrows at the foot. */
.doors { display: grid; grid-template-columns: repeat(3, 1fr); gap: 18px; }
@media (max-width: 980px) { .doors { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 700px) { .doors { grid-template-columns: 1fr; } }

.door {
  display: flex; flex-direction: column; gap: 7px;
  background: var(--surface-2); border-radius: 22px; padding: 24px 26px;
  color: var(--ink); min-height: 172px;
  transition: transform .15s ease, box-shadow .2s ease;
}
.door:hover { text-decoration: none; transform: translateY(-3px); box-shadow: var(--shadow-md); }
.door__title { font-family: var(--serif); font-weight: 800; font-size: 1.15rem; line-height: 1.3; letter-spacing: -0.01em; }
.door__sub { color: var(--muted); font-size: .92rem; max-width: 30em; }
.door__arrow { margin-top: auto; padding-top: 14px; font-weight: 600; font-size: .92rem; color: var(--pine-deep); }
/* the small doors' affordance: one round arrow, bottom right */
.door__go {
  margin-top: auto; align-self: flex-end;
  width: 34px; height: 34px; border-radius: 50%;
  background: rgba(255, 255, 255, .75); color: var(--ink);
  display: grid; place-items: center; font-size: .95rem;
  transition: background .15s ease, transform .15s ease;
}
.door:hover .door__go { background: #fff; transform: translateX(2px); }
.door__art { margin-bottom: 8px; }
.door__top { display: flex; align-items: flex-start; justify-content: space-between; gap: 14px; margin-bottom: 6px; }
.door__top .door__art { margin-bottom: 0; }
.door--mint   { background: var(--tint-mint); }
.door--butter { background: var(--tint-butter); }
.door--sky    { background: var(--tint-sky); }
.door--lav    { background: var(--tint-lav); }
.door--blush  { background: var(--tint-blush); }
.door--sand   { background: var(--sand); }

/* the crisis door's own CTA: the numbers, big enough to read at a glance.
   minmax(0, max-content): identical on desktop, but the columns can give way
   on a narrow phone instead of pushing the whole page sideways. */
.door__lines { display: grid; grid-template-columns: repeat(2, minmax(0, max-content)); gap: 10px 30px; margin-top: 8px; }
.door__lines a { display: flex; flex-direction: column; line-height: 1.15; color: var(--ink); font-size: .78rem; }
.door__lines b { font-family: var(--serif); font-size: 1.55rem; color: #b3542b; letter-spacing: -0.01em; }
.door__lines a:hover { text-decoration: none; }
.door__lines a:hover b { text-decoration: underline; }

/* illustration slot — a generated line drawing drops straight in here;
   the module's mini-graph sits beside it, so the mark is sized for a pair */
.door__art img { display: block; width: 62px; height: 62px; object-fit: contain; }
.door__viz { padding-top: 4px; }
.door__viz svg { display: block; width: 88px; height: auto; }

.card__num {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 42px; height: 42px; padding: 0 11px; margin-bottom: 12px;
  font-family: var(--serif); font-weight: 800; font-size: 0.95rem;
  background: var(--pine-soft); color: var(--pine-deep); border-radius: var(--radius-sm);
}
.kb-points { margin: 14px 0 0; padding: 0; list-style: none; }
.kb-points li { padding: 5px 0 5px 26px; position: relative; color: var(--muted); font-size: 0.92rem; }
.kb-points li::before {
  content: "✓"; position: absolute; left: 0; top: 5px; width: 18px; height: 18px;
  border-radius: 50%; background: var(--pine-soft); color: var(--pine-deep);
  font-size: 0.66rem; font-weight: 700; line-height: 18px; text-align: center;
}

/* quote / callout */
.callout {
  border-left: 4px solid var(--pine); background: var(--pine-soft);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0; padding: 16px 20px; margin: 20px 0;
}
.callout--clay { border-color: var(--clay); background: var(--clay-soft); }
.callout--warn { border-color: var(--high); background: #fff6e6; }
.callout p:last-child { margin-bottom: 0; }

/* steps */
.steps { display: grid; gap: 20px; grid-template-columns: repeat(3, 1fr); }
@media (max-width: 800px) { .steps { grid-template-columns: 1fr; } }
.step__n {
  width: 36px; height: 36px; border-radius: 50%; background: var(--pine); color: #fff;
  display: inline-flex; align-items: center; justify-content: center;
  font-family: var(--serif); font-weight: 800; font-size: 0.95rem; margin-bottom: 12px;
}

/* stat blocks (audit numbers) */
.stat { text-align: left; }
.stat__big { font-family: var(--serif); font-weight: 800; font-size: 2.1rem; color: var(--pine-deep); line-height: 1.05; display: block; margin-bottom: 6px; }

/* ------------------------------------------------------------- pricing --- */
.plan-card { position: relative; display: flex; flex-direction: column; }
.plan-card--popular { border: 2px solid var(--pine); box-shadow: 0 10px 30px rgba(31, 157, 99, .13); }
.plan-card__flag {
  position: absolute; top: -13px; left: 24px; background: var(--pine); color: #fff;
  font-size: 0.72rem; font-weight: 700; letter-spacing: 0.03em;
  padding: 4px 12px; border-radius: 999px;
}
.plan-card__price { font-family: var(--serif); font-weight: 800; font-size: 2.05rem; margin: 10px 0 2px; }
.plan-card__unit { color: var(--faint); font-size: 0.85rem; }
.plan-card__for { color: var(--muted); font-size: 0.92rem; margin: 10px 0 14px; }
.plan-feats { list-style: none; padding: 0; margin: 0 0 22px; flex: 1; }
.plan-feats li { padding: 7px 0 7px 26px; position: relative; font-size: 0.92rem; color: var(--muted); }
.plan-feats li::before { content: "✓"; position: absolute; left: 0; top: 7px; color: var(--pine); font-weight: 700; }

/* The allowance rows. The rule above them separates "what this plan is for"
   from "what it lets you do", which is the split a person deciding actually
   makes — the sentence above is the reason, these three numbers are the deal.
   The value is the serif and the label is not, so the three figures line up
   down the page as the thing being compared. */
.plan-usage { margin: 0 0 16px; padding: 12px 0 2px; border-top: 1px solid var(--line); }
.plan-usage__row { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; padding: 6px 0; }
.plan-usage dt { font-size: 0.86rem; color: var(--muted); }
.plan-usage dd { margin: 0; text-align: right; }
.plan-usage dd strong { font-family: var(--serif); font-size: 1.02rem; font-weight: 700; color: var(--ink); }
.plan-usage dd span { display: block; font-size: 0.7rem; line-height: 1.35; color: var(--faint); }
/* The honest caveat under the button: what the numbers above do NOT mean. */
.plan-card__fine { margin: 14px 0 0; font-size: 0.72rem; line-height: 1.5; color: var(--faint); }

/* -------------------------------------------------------- the founder --- */
/* "The Practitioner Behind the AI" is a claim about a person, and a claim
   about a person lands differently beside their face. From 2026-10-10 the
   face is the transparent cutout, standing in FRONT of the pulsing mark —
   the founder's follow-up, "use the transparent photo … on bigger size
   pulsing logo background; the texts besides it".

   AND FROM THE SAME EVENING, THE MIRROR OF IT — his second look at the live
   page: "Jean's profile is to shift out of the profile card and move to the
   right of the screen, pulsing logo to be bigger to be the background of her
   photo; text introduction to shift to left and align with The practitioner
   behind the AI title". So the block is no longer a card at all (the class
   came off in about.html). Her portrait stands at the right of the page and
   the words take the left, starting on the same x as the section's own
   heading — the card's 26px of padding was all that stood between the old
   text column and that alignment.

   WHY THE LOGO CAN BE THE BACKGROUND AT ALL. The first version framed a
   rectangular photo in a gradient panel; that frame existed to make a
   rectangle look deliberate. A cutout has no rectangle — only her — so the
   mark behind her does the framing instead, and the site gets to say whose
   practitioner this is without a caption. The mark is mark()'s, the beat is
   the green circle's (heartbeat) and the rings are the rail's halo keyframes:
   the block pulses exactly like the chat rail and the home door, through the
   same rules rather than a copy (see the .bot__mark rules below).

   THE GEOMETRY, AND WHY IT IS DRAWN AT A SCALE RATHER THAN SIZED. "Bigger, to
   be the background of her photo" is the change from a disc inside her
   shoulder line (where it showed only around her head) to one wider than she
   is — a plate she stands in front of. The obvious way to do that is to make
   the mark's BOX bigger than the square the photo is measured against, and
   the first attempt did exactly that (138% of the square, positioned so the
   disc stayed centred). It was wrong for a reason worth writing down: a
   transformed box still counts. The mark's viewBox is square, so growing the
   box to 138% and sliding it back to centre the disc left ~55px of border box
   hanging past the wrap — a horizontal scrollbar on any viewport where the
   wrap fills the window, from a piece of the drawing that is not even painted
   there.

   So the box stays the square, and the DRAWING is scaled inside it, per
   circle: `scale: var(--founder-mark)` on the dot, and the same number folded
   into the green circle's own keyframes — the second half of that is the bug
   story in "the founder's own beat", in the heartbeat section below. What
   spills out of the viewBox is INK either way, and ink overflow is not
   scrollable overflow — the page cannot be dragged sideways by a drawing.

   The mark's 32-unit viewBox draws its circles inside x 5–28, y 8–26: the
   green disc is 56.25% of the box, centred at (43.75%, 53.125%) of it. Those
   three fractions are the whole coordinate system here: the rings take their
   size and centre from them times --founder-mark, and the photo centres on
   43.75% so that she, the disc and the rings share one axis. The rings must
   not use a centring transform of their own — the halo keyframes own
   `transform`.

   The scale is 1.38 and cannot go much higher while the drop-shadow is on the
   logo: the shadow is a filter, a filter's region is the element's bounding
   box grown by 10%, and content outside that region is cut. At 1.38 the brown
   dot — the mark's third shape, at viewBox x 18–28, the one part that reaches
   past the disc — lands at 104% of the box, inside the 110%. At 1.6 it would
   be clipped, which would look like a drawing mistake rather than a limit.

   She is 68% of the square wide, anchored to its bottom. Her width and the
   disc's are the two numbers that make the picture read: wider and the green
   disappears behind her shoulders, narrower and the mark swallows her.
   Separating the two is left to her being opaque against a disc that is not;
   a drop-shadow on her was tried first and read as a smudge — the note in
   .founder__photo says why. */
.founder {
  display: grid;
  /* The square is the right column, and `clamp` keeps it from eating the
     text column on the way down to the stack: at 1120px the text keeps ~500px
     (~65 characters), and the pair turns into one column before the text
     would have to go narrower than that. */
  grid-template-columns: minmax(0, 1fr) clamp(300px, 40vw, 460px);
  gap: 56px;
  align-items: center;
}
.founder__stage {
  /* The three numbers, and the only place any is written: how wide she is
     against the square, how far the mark's drawing is scaled up inside its
     own box, and how far she is raised off the square's floor. */
  --founder-photo: 68%;
  --founder-mark: 1.38;
  /* Her floor is the mark's floor. The lift is the gap between the square's
     bottom and the disc's own bottom edge — the disc's centre (53.125%) plus
     its half-height (28.125% × the scale) — so the crop the source file ends
     on lands on the disc's edge rather than hanging below the mark in open
     white. Derived, so a change to --founder-mark carries it along: her hands
     and the mark keep ending together at any scale. */
  --founder-lift: calc(100% - 53.125% - 28.125% * var(--founder-mark));
  position: relative; aspect-ratio: 1;
}
.founder__bg { position: absolute; inset: 0; z-index: 0; }
.founder__logo {
  position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible;
  /* Lighter than the rail's .bot__mark shadow: there the mark floats alone on
     a page, and here part of it is covered by a person, so the shadow that
     lands in the open — under the disc — otherwise reads as a smudge she is
     standing in. */
  filter: drop-shadow(0 12px 24px rgba(31, 157, 99, .16));
}
/* The green circle's growth is NOT the `scale` property here — see
   "the founder's own beat" in the heartbeat section below, which is where the
   keyframes and the reason they exist live. What is left in this spot is the
   dot, which does keep `scale`. */
/* The dot is the exception: it has to grow AWAY from the disc's centre rather
   than about its own, or it would swell in place and the mark would come
   apart. view-box + the disc's own fractions make the origin the centre the
   green circle scales about. */
.founder__logo .mark__brown {
  scale: var(--founder-mark);
  transform-box: view-box;
  transform-origin: 43.75% 53.125%;
}
.founder__ring {
  position: absolute; pointer-events: none;
  /* Disc geometry, from the viewBox fractions: half of 56.25% × the scale,
     subtracted from the disc's centre (43.75%, 53.125%). No transform here —
     the halo keyframes own it (a `translate(-50%, -50%)` centring would be
     overwritten the moment the animation starts). */
  left: calc(43.75% - 28.125% * var(--founder-mark));
  top: calc(53.125% - 28.125% * var(--founder-mark));
  width: calc(56.25% * var(--founder-mark));
  aspect-ratio: 1; border-radius: 50%;
  border: 1.5px solid rgba(31, 157, 99, .32);
  animation: halo 3.6s ease-out infinite;
}
.founder__ring--2 { animation-delay: 1.8s; }
.founder__photo {
  position: absolute; z-index: 1; left: 43.75%; bottom: var(--founder-lift); width: var(--founder-photo);
  height: auto; transform: translateX(-50%);
  /* No drop-shadow on her. One was here first — `filter` follows a cutout's
     silhouette, so it looked like the right way to lift her off the mark —
     but the fade below also applies to the shadow, and a torso-wide shadow
     fading at nine-tenths reads as a grey smudge under her rather than as
     depth. She is separated by being opaque against a disc that is not. */
  /* The file ends by cutting through her clasped hands — the source photo is
     cropped there, not the CSS — and a cutout makes that crop visible as a
     straight edge where a framed photo would have hidden it behind the
     frame. Two things answer it, and they are a pair: the lift above raises
     her so the hands land inside the square rather than at its floor, and
     this fade dissolves only the last hundredth of the file, so the cut
     softens without the hands going with it. It was a tenth of the file
     first, which is a tenth of what? — a tenth is 42px at desktop and it
     swallowed the hands whole, which is exactly what the founder asked to
     stop happening; a fortieth (97.5%) still left ~11px of her genuinely
     translucent, and she sits over the mark's rings there, so the pair
     showed through her — the second thing he asked to stop. At 99% the
     band is ~4px: the crop is softened and nothing behind her reads
     through. The fade is why the shadow above had to go: the mask
     applies to the element, shadow included, so what was meant as depth
     ended as a grey haze in the one place the eye is already reading as
     "she fades here". */
  -webkit-mask-image: linear-gradient(to bottom, #000 99%, transparent 100%);
  mask-image: linear-gradient(to bottom, #000 99%, transparent 100%);
}
/* The fallback for a record with no cutout: the same avatar every other
   counsellor renders, at the photo's own width — the variable, not a second
   copy of the number, because the two drifted apart once already (72 vs 76)
   and a fallback that is a different size from the thing it stands in for is
   a bug nobody sees until the day it renders. Nothing today reaches it — the
   head counsellor has a cutout — but a block that silently collapses to a
   52px circle would be a strange way to find that out. No fade here: a framed
   4:5 avatar has an edge of its own, and dissolving it would leave a
   rectangle fading into the page for no reason. It also sits on the square's
   floor rather than at the cutout's lift, for the same reason — a frame has a
   bottom edge to stand on, and lifting it would only open a gap under it. */
.founder__stage .avatar {
  position: absolute; z-index: 1; left: 43.75%; bottom: 0; width: var(--founder-photo);
  height: auto; aspect-ratio: 4 / 5; border-radius: var(--radius);
  transform: translateX(-50%);
}
.founder__name { font-family: var(--serif); font-weight: 800; font-size: 1.5rem; margin: 0 0 2px; }
.founder__cred { font-weight: 600; color: var(--pine-deep); font-size: 0.92rem; margin: 0 0 2px; }
.founder__role { color: var(--faint); font-size: 0.85rem; margin: 0 0 14px; }
.founder__body p:last-child { margin-bottom: 0; }
@media (max-width: 800px) {
  /* One column below the width where the clamped square would start squeezing
     the text. Her portrait goes back on top here (`order`, not source order —
     the DOM keeps the words first for a screen reader), because a phone
     reader meets the face before the biography the way the desktop layout
     does. The cap is a size choice, not a containment one: nothing here
     bleeds, so the composition can be as small as it needs to be; centring it
     keeps the square off the page's left edge. */
  .founder { grid-template-columns: 1fr; gap: 28px; }
  /* `width: 100%` is load-bearing, not belt-and-braces: auto margins turn the
     grid item's stretch off, and every child of the stage is absolutely
     positioned, so without it the stage's fit-content width is zero — a 0x0
     square, and the photo with it. */
  .founder__stage { width: 100%; max-width: 300px; margin: 0 auto; order: -1; }
}

/* The three cards under the founder: Conceptualisation, Knowledge base,
   In-person care. They were three identical walls of grey text and read as a
   single paragraph in three columns. An icon in a tinted roundel gives each
   one a thing to be recognised by, and the tint is the door palette from the
   home page — the same pastels in the same order of subject — so the deck has
   a colour language already in use rather than a new one. */
.card__ico {
  width: 44px; height: 44px; border-radius: 13px; display: grid; place-items: center;
  margin-bottom: 14px; flex: none;
}
.card__ico .ico { width: 22px; height: 22px; }
.card__ico--sky   { background: var(--tint-sky);   color: #3f7fc4; }
.card__ico--mint  { background: var(--tint-mint);  color: var(--pine-deep); }
.card__ico--blush { background: var(--tint-blush); color: var(--clay); }

/* ---------------------------------------------------------- counsellors --- */
.counsellor { display: flex; flex-direction: column; }
.counsellor__head { display: flex; gap: 14px; align-items: center; margin-bottom: 12px; }
.avatar {
  width: 52px; height: 52px; border-radius: 50%; flex: none;
  background: linear-gradient(140deg, #d9fdd3, #a7e8c4);
  display: flex; align-items: center; justify-content: center;
  font-family: var(--serif); font-size: 1.15rem; color: var(--pine-deep); font-weight: 800;
}
.counsellor__name { font-weight: 700; line-height: 1.25; }
.counsellor__cred { color: var(--muted); font-size: 0.8rem; line-height: 1.4; }
/* A real photo when the profile carries one; the initials tile stays the
   default for every mock profile. */
.avatar--photo { background: none; padding: 0; overflow: hidden; }
.avatar--photo img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* Focus areas, as pills on every profile — Jean Chen's included.
   There used to be a `.counsellor--head` rule here: a wider card above the
   grid, with the Head Counsellor lifted out of it. It went on 2026-10-09 with
   the card itself, because a panel that sets frameworks and takes referrals
   has no head in the sense a reader would take from a highlighted card. The
   `head` flag stays on her record — /about, the booking order and the admin
   console all read it — it just no longer changes this page. */
.focus-tags { display: flex; flex-wrap: wrap; gap: 6px; margin: 10px 0 14px; }
.focus-tags span { background: var(--surface-2); border-radius: 999px; padding: 4px 11px; font-size: 0.78rem; color: var(--muted); }
.counsellor__foot { display: flex; flex-wrap: wrap; gap: 8px 12px; justify-content: space-between; align-items: center; margin-top: auto; padding-top: 12px; border-top: 1px solid var(--line); }

/* ---------------------------------------------------------------- chat --- */
/* The centrepiece — bubbles read like an everyday messaging app.

   The shell is the page now rather than a card on it: Teman has a rail on the
   left and the conversation has the rest, at every width, from the moment the
   page loads. The column that used to open only when voice was switched on is
   simply the layout, which is what consolidating the two surfaces means. */
.chat-wrap { max-width: 1152px; margin: 0 auto; padding: 0 24px; }
/* One line up top, not three. The paragraph that used to sit beside the lede —
   the flash tier, the system prompt, the crisis screen — moved under the shell
   as #chat-mode-note for a while, and is now off the page entirely: the
   disclosure that matters is the one the conversation itself opens with, and
   the rest belongs to the docs. Above the chat it was pushing the chat itself
   off the first screen. */
/* Sized to be passed, not read. This heading is furniture beside a conversation
   that already has a name in the header below it, and at 32px on a desktop it
   was the largest thing on a screen whose subject is the transcript. */
.chat-main .page-head { padding: 20px 0 8px; }
.chat-main .page-head h1 { font-size: clamp(1.3rem, 2.2vw, 1.45rem); }
.chat-main .page-head .lede { font-size: 0.88rem; max-width: none; }
.chat-shell {
  margin: 0 auto;
  display: flex; flex-direction: column; height: min(86vh, 880px);
  background: var(--surface); border: 1px solid var(--line); border-radius: 26px;
  overflow: hidden; box-shadow: var(--shadow-md);
}
/* One row: who, then what you can do. It holds no topic and no tier — those are
   in the transcript now — so the two groups are short enough to stay on one
   line at every width, and the name block is the one thing that gives.
   `min-width: 0` on the block is what lets it: a flex child will not shrink
   below its content without it, so the status line would push the buttons
   off the edge instead of truncating. */
.chat-head {
  display: flex; align-items: center; gap: 10px; padding: 9px 14px;
  border-bottom: 1px solid var(--line); background: var(--surface);
}
.chat-head__who { min-width: 0; }
.chat-head__avatar { width: 36px; height: 36px; font-size: 0.95rem; }
.chat-head .who { font-family: var(--serif); font-weight: 800; font-size: 0.92rem; line-height: 1.2; display: flex; align-items: center; gap: 7px; }
.chat-head .who .tag { font-size: 0.6rem; padding: 2px 7px; }
/* Not a flex row, deliberately: the ellipsis needs a block box around the text,
   and in a flex container the text becomes an anonymous item that
   `text-overflow` cannot reach. The dot is an inline-block instead — it aligns
   the same way and the truncation actually happens. */
.chat-head .status {
  color: var(--ok); font-size: 0.72rem;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.chat-head .status::before {
  content: ""; width: 6px; height: 6px; border-radius: 50%; background: var(--ok);
  display: inline-block; vertical-align: middle; margin-right: 5px;
}
.chat-head__right { margin-left: auto; display: flex; gap: 8px; align-items: center; flex: none; }
/* Everything the conversation is made of, in one column: the transcript that
   scrolls, then the typing row and the composer, which do not. Only the
   transcript is allowed to shrink — `min-height: 0` is what lets it, and
   without it a flex child refuses to go below its content and pushes the
   composer off the bottom of the shell. */
.chat-col { grid-area: 1 / 2; display: flex; flex-direction: column; min-height: 0; }
.chat-col > :not(.chat-body) { flex: none; }
.chat-body {
  flex: 1; min-height: 0; overflow-y: auto; padding: 22px 18px 24px;
  border-left: 1px solid var(--line);
  background-color: #f6f3ec;
  background-image: radial-gradient(rgba(31, 157, 99, .05) 1px, transparent 1px);
  background-size: 18px 18px;
}
/* What the conversation is about, at the top of the transcript. It reads as a
   subject line because that is what it is — the thing the header used to carry
   and no longer does, where it competed with the name for the same row. */
.chat-topic {
  margin: 0 0 14px; font-size: 0.76rem; font-weight: 600; letter-spacing: 0.04em;
  text-transform: uppercase; color: var(--faint);
}
.chat-topic .live-flag { text-transform: none; letter-spacing: 0; }

/* The intro had no rule of its own, so the question inherited the global h3 —
   18.6px over a paragraph over eight buttons, all of it between the top of the
   transcript and the first starting point. It is a prompt, not a page title. */
#chat-intro h3 { font-size: 1.05rem; margin: 0 0 6px; line-height: 1.3; }
#chat-intro p { line-height: 1.5; }

.msg { display: flex; margin-bottom: 10px; }
.msg--ai { justify-content: flex-start; }
.msg--user { justify-content: flex-end; }
.bubble {
  max-width: 78%; padding: 10px 14px; border-radius: 18px 18px 18px 6px;
  font-size: 0.9rem; line-height: 1.55;
  background: var(--surface); border: 1px solid #eae7de; color: var(--ink);
  box-shadow: 0 1px 2px rgba(36, 49, 44, .05);
  animation: rise 0.22s ease both;
}
@keyframes rise { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
.msg--ai .bubble { background: var(--surface); }
.msg--user .bubble { background: var(--wa); border-color: transparent; color: #1d2b24; border-radius: 18px 18px 6px 18px; }
/* The replay button on one of Teman's replies. A sibling of the bubble rather
   than a child, so the bubble next to it never reflows when the audio lands and
   the words stay exactly where they were read. Sized to a thumb rather than to
   the icon: at 375px this is a tap target first and a control second. */
.msg-say {
  flex: 0 0 auto; align-self: flex-end;
  min-width: 32px; min-height: 32px; margin: 0 0 2px 7px; padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid #eae7de; border-radius: 999px; background: var(--surface);
  color: var(--muted); cursor: pointer;
  box-shadow: 0 1px 2px rgba(36, 49, 44, .05);
}
.msg-say .ico { width: 16px; height: 16px; }
.msg-say:hover { color: var(--ink); border-color: #dcd8cc; }
.msg__meta { font-size: 0.72rem; color: var(--faint); margin: 4px 6px 0; }
.typing {
  display: inline-flex; align-self: flex-start; gap: 4px; padding: 14px 18px;
  background: var(--surface); border: 1px solid #eae7de; border-radius: 18px 18px 18px 6px;
  box-shadow: 0 1px 2px rgba(36, 49, 44, .05);
}
.typing i { width: 6px; height: 6px; border-radius: 50%; background: var(--faint); animation: blink 1.2s infinite; }
.typing i:nth-child(2) { animation-delay: 0.15s; } .typing i:nth-child(3) { animation-delay: 0.3s; }
@keyframes blink { 0%, 100% { opacity: 0.25; } 50% { opacity: 1; } }

/* The typing row sits between the transcript and the composer, where the reply
   it is standing in for will actually land. The margin is the breathing room
   that stops it reading as the last line of the conversation. */
#chat-typing { margin: 0 18px 12px; }

/* The typing bar is a keyboard target, not the end of the transcript. The
   transcript above it is a tinted, dotted plane; this is a solid white bar with
   a real top edge and a shadow that only falls upward (a non-inset box-shadow
   is clipped to outside the border box, so the safety row below is untouched).
   That edge is the whole difference between the two. */
.composer {
  position: relative; z-index: 1;
  display: flex; flex-direction: column; gap: 7px; padding: 9px 14px 10px; border-top: 1px solid var(--line);
  background: var(--surface); box-shadow: 0 -6px 16px rgba(36, 49, 44, .06);
}
.composer__row { display: flex; gap: 10px; }
/* The bar is controls and nothing else (2026-10-11): the saving line that sat
   under the input is gone, and the bar was cut down with it. The input keeps a
   tap target worth having — 11px of padding over a 0.95rem line is 43px, and
   the button matches it — but the row no longer pays for a second line of
   text it does not draw. */
.composer input {
  flex: 1; border: 1.5px solid var(--line); border-radius: 999px; padding: 11px 18px;
  font-family: var(--sans); font-size: 0.95rem; background: var(--surface-2); color: var(--ink);
}
.composer input:focus { outline: none; border-color: var(--pine); background: var(--surface); box-shadow: 0 0 0 3px rgba(31, 157, 99, .15); }
.send-btn {
  width: 43px; height: 43px; border-radius: 50%; border: 0; background: var(--pine); color: #fff;
  font-size: 1.1rem; cursor: pointer; display: flex; align-items: center; justify-content: center; flex: none;
  box-shadow: 0 6px 16px rgba(31, 157, 99, .28); transition: background .15s ease, transform .15s ease;
}
.send-btn:hover { background: var(--pine-deep); transform: translateY(-1px); }
.send-btn:disabled, .composer input:disabled { opacity: 0.5; }

/* The 24/7 lines sit inside the chat, one tap from anywhere in the conversation
   — and the tap is in the header now rather than on a row of its own under the
   composer. That row cost the transcript a line at every width and two on a
   phone, and it was furniture around the one control that has to be findable at
   a glance. It is not quieter up here: it keeps the warm colour and the icon,
   and the header is where a person looks for what they can do. */
.chat-help {
  display: inline-flex; align-items: center; gap: 6px;
  font-family: var(--sans); font-size: 0.78rem; font-weight: 600;
  color: #b3542b; background: var(--clay-soft);
  border: 1px solid transparent; border-radius: 999px; padding: 7px 12px; cursor: pointer;
  transition: background 0.12s;
}
.chat-help:hover { background: #fbe0cf; }
.chat-help .ico { width: 14px; height: 14px; flex: none; }
#chat-crisis-card { margin-top: 0; }

.crisis-card {
  border: 1px solid #f6d9c9; border-radius: var(--radius); background: #fff7f3;
  padding: 18px 20px; margin: 6px 0 14px; max-width: 92%;
}
.crisis-card h4 { color: #b3542b; margin-bottom: 8px; font-family: var(--sans); font-size: 0.95rem; letter-spacing: 0.02em; }
.crisis-card ul { margin: 0; padding-left: 18px; }
.crisis-card li { margin-bottom: 6px; font-size: 0.92rem; }
.crisis-card .big { font-weight: 700; }
.crisis-card p a { color: var(--pine-deep); text-decoration: underline; text-underline-offset: 2px; font-weight: 600; }

/* The session card, at the head of a past conversation: what the session was,
   and what Teman did in it. Two columns because the two halves must not read
   as one list — see sessionCardHtml — and one column on a phone, where side by
   side they would be two narrow columns of wrapped prose. The left edge is the
   same pine as a hovered row, because this card is the far end of one. */
.session-card {
  border: 1px solid var(--line); border-left: 3px solid var(--pine);
  border-radius: var(--radius); background: #fbfdfb;
  padding: 14px 18px; margin: 0 0 14px; max-width: 92%;
}
.session-card__grid { display: grid; grid-template-columns: 1fr 1fr; gap: 6px 22px; }
.session-card h5 {
  font-family: var(--sans); font-size: 0.72rem; font-weight: 700; letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--faint); margin: 0 0 6px;
}
.session-card ul { margin: 0; padding-left: 16px; }
.session-card li { font-size: 0.88rem; margin-bottom: 5px; }
/* A flagged care entry takes the clay the crisis card uses, so the one colour
   the product reserves for this means one thing on both. */
.session-card__care .care-row strong { color: #b3542b; }
.session-card__foot { margin: 10px 0 0; max-width: 62ch; }
@media (max-width: 640px) { .session-card__grid { grid-template-columns: 1fr; } }

/* ------------------------------------------------------------ help pages -- */
/* /help-now and /how-the-crisis-screen-works: static markup on purpose — the
   numbers are the content here, not a script's output, so these pages carry
   their own small layout rather than the chat's JS-rendered card. */
.help-lines { display: grid; gap: 14px; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); margin: 18px 0 6px; }
.help-line { background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); padding: 20px 22px; }
.help-line--urgent { background: #fff7f3; border-color: #f6d9c9; }
.help-line h3 { margin: 0 0 4px; font-size: 1.05rem; }
.help-line__num { font-size: 1.55rem; font-weight: 700; line-height: 1.25; margin: 2px 0 8px; color: var(--pine-deep); }
.help-line--urgent .help-line__num { color: #b3542b; }
.help-line__num a { color: inherit; text-decoration: underline; text-underline-offset: 3px; }
.help-line__num .wa { font-size: 0.95rem; font-weight: 600; }
.help-line .btn { margin: 2px 6px 0 0; }
/* A verbatim quote of machine output — the crisis reply, word for word. */
.reply-quote { border-left: 3px solid var(--pine); background: var(--surface-2); border-radius: 0 var(--radius-sm) var(--radius-sm) 0; padding: 14px 18px; margin: 14px 0; }
.reply-quote p { margin: 0; }

/* -- the inline account form --------------------------------------------- */
/* It lives in the transcript on purpose. Navigating to login.html would take
   the conversation with it, and the conversation is the thing the person has
   just spent an hour building — so the form comes to them instead. */
.auth-inline { margin-top: 14px; padding-top: 14px; border-top: 1px solid var(--line); max-width: 400px; }
.auth-inline__tabs { display: flex; gap: 8px; margin-bottom: 13px; }
.auth-inline .field { margin-bottom: 10px; }
.auth-inline .field label { display: block; font-size: 0.78rem; color: var(--muted); margin-bottom: 4px; }
.auth-inline input {
  width: 100%; border: 1px solid var(--line); border-radius: 10px; padding: 10px 13px;
  font-family: var(--sans); font-size: 0.92rem; background: var(--surface-2); color: var(--ink);
}
.auth-inline input:focus { outline: none; border-color: var(--pine); background: var(--surface); box-shadow: 0 0 0 3px rgba(31, 157, 99, .15); }
.auth-inline__msg { margin: 0 0 9px; min-height: 1.2em; color: var(--muted); }
.auth-inline__msg--bad { color: #b3542b; }

/* -- the stage: Teman on the left, the conversation on the right ---------- */
/* One layout for both surfaces. Text and voice are the same conversation in the
   same box — the rail says what Teman is doing, the transcript says what was
   said, and switching voice on changes the rail rather than the arrangement.
   Two modes meant two layouts to keep honest, and the one that drifts is the
   one nobody is reading.

   The transcript never leaves the screen. That is the property the safety case
   rests on, not a layout preference. Everything spoken is written into it before
   it is said out loud, and everything heard is written into it as it is
   recognised — including when Teman has run out of voice and is answering in
   text only. */
/* 340px of rail was a sixth of a laptop screen given to a logo and two buttons,
   and every pixel of it came off the transcript — which is the thing the
   request wanted more of. 250px still fits the orb, the state line and the
   controls without wrapping. */
.chat-stage {
  display: grid; flex: 1; min-height: 0;
  grid-template-columns: minmax(210px, 250px) minmax(0, 1fr);
  align-items: stretch;
}
/* The rail is a column, not the room: Teman on the left at a fixed width, the
   conversation taking everything else. The reverse — a wide bot and a 340px
   transcript — is what the old voice mode did, and it is the arrangement the
   request was to change: the chat box is the thing that needs the width. */
/* The padding came in with the width. At 340px the rail could afford 24px of
   air on each side; at 250px that same air was a fifth of the column, and the
   state line — the one thing on the rail that has to be read — was wrapping to
   three lines inside 202px. */
.voice-stage {
  grid-area: 1 / 1;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 15px; padding: 24px 16px; text-align: center;
  background: linear-gradient(170deg, #e9f7ef 0%, #fbfdfb 72%);
}

/* ---- the bot ----------------------------------------------------------- --
   The logo itself, promoted from the favicon the same way the wordmark was:
   the green circle and the smaller orange one, beating.

   The beat answers the state, so it carries information instead of
   decorating. It quickens when Teman speaks, slows while it thinks, and stops
   when the microphone is off. A bot that beats the same way whether it can
   hear you or not is the one thing on this screen that could mislead. */
.bot {
  position: relative; flex: none; width: min(72%, 165px); aspect-ratio: 1;
  padding: 0; border: 0; background: none; cursor: pointer; border-radius: 50%;
  animation: botIn .5s cubic-bezier(.2, .9, .3, 1.2) both;
}
@keyframes botIn { from { opacity: 0; transform: scale(.7); } to { opacity: 1; transform: none; } }
.bot:focus-visible { outline: 3px solid var(--pine); outline-offset: 8px; }

.bot__mark {
  display: block; width: 100%; height: 100%; overflow: visible;
  filter: drop-shadow(0 18px 34px rgba(31, 157, 99, .3));
}
/* The beat belongs to the green circle, not to the pair.
 *
 * It used to be a transform on the whole svg, anchored at the big circle's
 * centre — so both circles grew together and the mark beat as one object. The
 * green one is the one that means "Teman is here"; the orange one is a smaller
 * satellite, and dragging it along made the logo swell rather than pulse.
 *
 * `transform-box: fill-box` is what makes this possible at all: without it a
 * percentage `transform-origin` on an SVG child resolves against the nearest
 * viewport, so the circle would scale away from its own centre and drift across
 * the mark. With it, `center` means the centre of this circle. */
.bot__mark .mark__green,
.founder__logo .mark__green {
  transform-box: fill-box;
  transform-origin: center;
  animation: heartbeat 3.6s ease-in-out infinite;
}
/* The founder's own beat — the same pulse, around a circle that is already
 * scaled.
 *
 * The mark behind her photo grows because each circle carries `scale:
 * var(--founder-mark)`; the keyframes above animate `transform`, which is a
 * different property, so the two were meant to compose. They do not. Chrome
 * drops the individual `scale` on this element while the animation runs, and
 * the disc paints at 1x: measured on the 460px stage, with the beat disabled
 * the green ink spans x 23→378, with the beat running x 66→334. The plate the
 * whole composition is built around was quietly the small disc again, most of
 * it behind her.
 *
 * So the growth moves *into* the keyframes here, and `scale` is switched off
 * outright rather than left to be dropped. This rule must stay after the
 * shared one above: `animation` is a shorthand, it resets `animation-name`,
 * and anything written before it loses.
 *
 * The two numbers are the shared beat's, multiplied — 1.09 and 1.055 of
 * whatever the mark is scaled to, so a change to `--founder-mark` carries the
 * pulse's amplitude with it, as it carries the lift below. */
.founder__logo .mark__green {
  scale: none;
  animation-name: heartbeat-founder;
}
@keyframes heartbeat-founder {
  0%, 100% { transform: scale(var(--founder-mark)); }
  12%      { transform: scale(calc(var(--founder-mark) * 1.09)); }
  24%      { transform: scale(var(--founder-mark)); }
  36%      { transform: scale(calc(var(--founder-mark) * 1.055)); }
  52%      { transform: scale(var(--founder-mark)); }
}
/* The orange satellite does not beat, and this is where that is written down
   rather than left as an absence to be verified. `!important` because the thing
   it guards against is somebody later animating `.bot__mark` itself — the whole
   svg — which is the version of this that was here before, and which would
   quietly swallow the small circle again without a single test noticing. */
.bot__mark .mark__brown,
.founder__logo .mark__brown { animation: none !important; }
@keyframes heartbeat {
  0%, 100% { transform: scale(1); }
  12%      { transform: scale(1.09); }
  24%      { transform: scale(1); }
  36%      { transform: scale(1.055); }
  52%      { transform: scale(1); }
}
.bot__halo {
  position: absolute; inset: 5%; border-radius: 50%; pointer-events: none;
  border: 1.5px solid rgba(31, 157, 99, .32);
  animation: halo 3.6s ease-out infinite;
}
.bot__halo--2 { animation-delay: 1.8s; }
@keyframes halo {
  0%        { transform: scale(.66); opacity: .65; }
  80%, 100% { transform: scale(1.32); opacity: 0; }
}

.voice-state { color: var(--muted); font-size: 0.85rem; line-height: 1.45; min-height: 2.6em; max-width: 26ch; }
.voice-controls { display: flex; gap: 10px; }
/* Smaller than the 52px .call-btn it inherits: these sit under the orb on a
   rail that is now a third narrower, and at 52px the row read as the subject of
   the column rather than as controls beside Teman. */
.voice-controls .call-btn { width: 38px; height: 38px; font-size: 0.95rem; }
/* Both glyphs are drawn now. The mic used to be an emoji character sitting
   beside a drawing, which is why this rule exists at all — at 16px the drawing
   read as smaller than the emoji it sat next to. The size stays because both
   drawings are detailed enough that below 19px they smudge. */
.voice-controls .call-btn .ico { width: 19px; height: 19px; }
/* The smallest type on the page. It answers the question the two controls above
   it raise — is this thing recording me, and does it write before it speaks —
   and an answer read twice becomes furniture, so it is set like a footnote
   rather than like copy. */
.voice-note { margin: 0; font-size: 0.68rem; line-height: 1.45; color: var(--muted); max-width: 34ch; }
.voice-stage .call-super { margin-top: 2px; font-size: 0.68rem; line-height: 1.4; padding: 5px 11px; max-width: 100%; }

/* Every state override names the green circle and the halo, never the svg.
   The beat is a property of the circle now (see .bot__mark .mark__green), so a
   rule pointing at .bot__mark would set a duration on an element with no
   animation on it and the state would silently stop changing the pace. The
   rules that are about how the mark *looks* — opacity, greyscale — still name
   the svg, because those apply to both circles. */
.voice-stage[data-voice="listening"] .mark__green,
.voice-stage[data-voice="listening"] .bot__halo { animation-duration: 4.6s; }
/* `idle` is the page at rest, before voice is switched on. It needs no rule:
   the base heartbeat below is the slowest beat in the set, which is exactly
   what resting looks like. */
/* `quiet` is the one state that has to be legible the other way round: the
   microphone is live and Teman is not speaking, because the spoken allowance is
   spent. It keeps the listening pace on purpose — slowing or stopping the beat
   would say the microphone is off, and it is not. The state line and the card
   in the transcript are what carry the difference. */
.voice-stage[data-voice="quiet"] .mark__green,
.voice-stage[data-voice="quiet"] .bot__halo { animation-duration: 4.6s; }
.voice-stage[data-voice="speaking"] .mark__green,
.voice-stage[data-voice="speaking"] .bot__halo  { animation-duration: 1.9s; }
.voice-stage[data-voice="thinking"] .mark__green { animation-duration: 2.8s; }
.voice-stage[data-voice="thinking"] .bot__mark   { opacity: .88; }
.voice-stage[data-voice="paused"] .bot__mark,
.voice-stage[data-voice="noMic"]  .bot__mark    { filter: grayscale(.45); }
.voice-stage[data-voice="paused"] .mark__green,
.voice-stage[data-voice="noMic"]  .mark__green  { animation-play-state: paused; }
.voice-stage[data-voice="paused"] .bot__halo,
.voice-stage[data-voice="noMic"]  .bot__halo    { animation-play-state: paused; opacity: .25; }

/* What the microphone is hearing, written into the transcript as it is
   recognised and replaced by the real message the moment the sentence is
   sent. It is the voice-to-text step shown rather than asserted — the person
   watches their own words land in the chat box. */
.bubble--heard {
  background: var(--wa); border-color: transparent; color: #1d2b24;
  border-radius: 18px 18px 6px 18px; opacity: .62; font-style: italic;
}
.bubble--heard::after { content: "▍"; margin-left: 2px; animation: caret 1s steps(2) infinite; }
@keyframes caret { 0%, 50% { opacity: 1; } 51%, 100% { opacity: 0; } }

@media (max-width: 880px) {
  /* The phone. The chat stops being a card floating in a marketing scroll and
     becomes the screen: full width, one visible viewport tall, with the typing
     bar pinned to the bottom edge of it.

     `dvh` rather than `vh` because the URL bar collapses as you scroll and a
     `vh`-sized box is taller than the phone whenever that bar is showing.
     `--kb` is what watchKeyboard() publishes when the soft keyboard takes a bite
     out of the visual viewport: without it the shell keeps its full height and
     the composer ends up behind the keys, which is the "keyboard squeezes
     everything away" complaint. Subtracting it puts the composer back on top.
     66px is the sticky site header, whose height is fixed at styles.css:115. */
  body[data-page="chat"] main {
    height: calc(100dvh - 66px - var(--kb, 0px));
    display: flex; flex-direction: column; overflow: hidden;
  }
  /* The footer comes off, AND the document stops scrolling behind the shell.
     Both, because `--kb` alone was not enough and the live report says why:
     "the keyboard pops up and the whole page is pushed up with the footer over
     the keyboard and the typing bar far away". With the footer still below
     main, the document has exactly the footer's height of scrollable room —
     and when an input takes focus, the browser scrolls the layout viewport to
     "reveal" it. That scroll lifts the shell by the footer's height, drops the
     footer onto the keyboard, and leaves the composer riding up out of reach.
     The calc() was right; there was simply somewhere for the page to scroll.
     With nothing below `main` the document is header + shell = precisely the
     visual viewport (100dvh - 66px - kb + the 66px header), there is no scroll
     to make, and the composer stays where `--kb` put it — glued to the top of
     the keyboard. `overflow: hidden` is the belt to that braces: even a
     rubber-band pan cannot drag the shell now.
     Nothing is lost with the footer: its two doors are on screen already —
     "Get help now" in the chat head carries the crisis numbers, and the menu
     (the header's ☰) carries both them and the counsellor panel. */
  body[data-page="chat"] .site-foot { display: none; }
  body[data-page="chat"] { overflow: hidden; overscroll-behavior: none; }
  /* `margin: 0`, not the desktop `margin: 0 auto`. An auto margin in the cross
     axis cancels `align-items: stretch`, so with `max-width: none` the wrap
     stops filling and shrink-to-fits instead — and its content floor was 389px
     on a 375px phone, which put the right-hand end of the chat header (and the
     End button with it) off the edge of the screen. The auto margin is there to
     centre a capped column; there is no cap any more, so it has no job. */
  .chat-main .chat-wrap { flex: 1; min-height: 0; max-width: none; padding: 0; margin: 0; }
  /* The heading and its paragraph come off entirely, which is what a chat app
     has: no page title above the conversation. "Talk to Teman" is the name in
     the header a few pixels below, and the lede under it was describing the
     screen you are already looking at. On a 746px phone the pair cost about
     60px, and 60px here is two messages. The same words are on the page above
     the shell only on a desktop, where they are not in anything's way. */
  .chat-main .page-head { display: none; }

  /* The shell fills the screen exactly: the stage takes every pixel that is
     left, and the composer at the bottom of the column is the bottom of the
     page. Nothing else has a strip down there any more — the DeepSeek and
     crisis note that used to sit under the shell is off the page entirely,
     and the disclosure it carried lives in the transcript's opening, above
     the first reply. */
  #chat-root { display: flex; flex-direction: column; height: 100%; }
  /* Same auto-margin trap as .chat-wrap above, and the same fix. */
  .chat-shell { flex: 1; min-height: 0; margin: 0; }
  /* The two cards under the shell come off. On a phone the chat is the page,
     and a card stack below a composer pinned to the bottom is pixels nobody
     asked to scroll past — the same reasoning the footnote strip above just
     lost to. Both doors they hold (the crisis numbers, the counsellor panel)
     are still on screen: "Get help now" is in the head, and the footer is one
     scroll below the shell. */
  .chat-aside { display: none; }
  /* The eight starting points go too. They sit at the top of the transcript —
     the first thing anyone sees — and on a phone a starting point is a
     desktop luxury: the input is under a thumb already, and the mic is on the
     strip above it. What remains is the disclosure and one line pointing at
     both, which is the part that had to stay. */
  #chat-intro .grid { display: none; }
  /* Full bleed: on a phone the shell is the screen, so its own frame would be a
     border drawn around the thing you are already inside. */
  .chat-shell { border-radius: 0; border-left: 0; border-right: 0; max-width: none; }

  /* The rail stops being a column and becomes a strip: a small orb, the state
     line, the mic, on one row above the transcript. It keeps the two things that
     matter — that Teman is on screen, and what Teman is doing.
     The screening chip and the microphone note come off here; the head above
     already says "Every message is screened first", so nothing is lost that is
     not still on screen, and the strip gets its height back. */
  .chat-stage { grid-template-columns: 1fr; grid-template-rows: auto minmax(0, 1fr); }
  .voice-stage {
    grid-area: 1 / 1;
    flex-direction: row; flex-wrap: wrap; row-gap: 8px;
    align-items: center; justify-content: flex-start;
    gap: 12px; padding: 9px 14px; text-align: left;
    border-bottom: 1px solid var(--line);
  }
  .chat-col { grid-area: 2 / 1; }
  .bot { width: 44px; }
  /* The state line is the reason the strip exists, so it gets the row to
     itself and the count waits its turn. Four things on one 375px row — orb,
     state, count, controls — left the state line 56px wide and wrapping to
     four lines, which is the strip failing at its one job.
     A real flex-basis, not `flex: 1` with a zero basis: at basis 0 the line
     never overflows on paper, so `flex-wrap` never fires and the count still
     wins the argument on the screen. With 140px stated, the row is honestly
     too wide, the count wraps to its own line, and the state line keeps 195px.
     The budget tag is back at five left — the point at which it is worth a
     line — and until then the wall is what carries it. `order: 1` keeps the
     mic and End on the first row: left in source order the tag wraps first and
     drags the controls down with it, so the one button a person is reaching
     for moves 50px the moment their allowance gets low. */
  .voice-state { flex: 1 1 140px; min-width: 0; font-size: 0.8rem; max-width: none; }
  .voice-controls { flex: none; gap: 8px; }
  #voice-budget { flex: none; order: 1; }
  #voice-budget:not(.tag--warn) { display: none; }
  .voice-stage .call-super, .voice-stage .voice-note { display: none; }
  /* 44px, not 32px. These are the two switches on the strip and they were the
     smallest tap targets on the page — smaller than the thing they control
     (the orb) and smaller than the send button beside them. 32px is what a
     mouse needs; a thumb needs the 44px every platform's own guidance names,
     and the row has the width for it: orb, then two buttons, is 152px of 375. */
  .voice-stage .call-btn { width: 44px; height: 44px; font-size: 0.85rem; }
  .voice-stage .call-btn .ico { width: 20px; height: 20px; }
  .chat-body { border-left: 0; padding: 14px 14px 12px; }

  /* The soft keyboard. Under 16px iOS Safari zooms the whole page when an input
     takes focus, and a page zoomed to 1.3x is one the person then has to pinch
     back out of — which is most of what "pinch should not move it" is about. */
  .composer input { font-size: 16px; padding: 10px 15px; }

  .chat-body { overscroll-behavior: contain; -webkit-overflow-scrolling: touch; }
  /* The composer is now the bottom of the shell, so the safe-area inset that
     used to sit under the help row belongs to it. Without this the send button
     lands on the home indicator on a notched phone — the one control nothing
     else can substitute for.
     Smaller than the desktop bar on purpose: 13px of padding around a 46px
     send button is 72px, a fifth of a 746px screen spent on one text input,
     every second of the conversation. 8px around a 40px button is 56px and
     still past the tap-target floor. The bar itself does not move — the
     column pins it to the bottom of the shell at every size. */
  .composer { gap: 8px; padding: 8px 12px calc(8px + env(safe-area-inset-bottom, 0px)); }
  .send-btn { width: 40px; height: 40px; font-size: 1rem; }
  #chat-typing { margin: 0 14px 10px; }

  /* Two rows, and the split between them is the point. The first is who this
     is — the avatar, the name, and, beside the name rather than under it, the
     status line. It used to wrap under the name, which cost two lines on
     every phone and left "Supervised by human counsell…" reading as a layout
     bug rather than as the claim the product rests on. Beside the name at
     0.7rem it is about 160px of a 351px row and finishes its own sentence.
     The second row is what you can do about it: what you have left, pushed to
     the far left, then the two buttons. */
  .chat-head { flex-wrap: wrap; padding: 8px 12px; gap: 6px 8px; }
  .chat-head__avatar { width: 32px; height: 32px; font-size: 0.85rem; }
  /* A row of its own inside the flex row above, so the name and the claim sit
     on one baseline. `min-width: 0` on both this and the status is what lets
     the claim truncate rather than widen the row; the name itself never gives
     (`flex: none`) because "Teman A…" is worse than a clipped sentence, and
     the sentence is on the safety page in full. */
  .chat-head__who { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: row; align-items: center; gap: 7px; }
  .chat-head .who { flex: none; font-size: 0.88rem; white-space: nowrap; }
  .chat-head .status { min-width: 0; font-size: 0.7rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  /* Full width, forced onto its own line, so the count has a row to start on.
     `margin-left: auto` is the desktop centring and has no job here. */
  .chat-head__right { flex: 1 1 100%; margin-left: 0; }
  #chat-count { margin-right: auto; }
  .chat-help { padding: 6px 10px; font-size: 0.74rem; }
  .chat-head__right .btn--small { padding: 6px 11px; font-size: 0.74rem; }
  .chat-topic { font-size: 0.72rem; margin-bottom: 12px; }
}

/* ----------------------------------------------------------------- call --- */
/* What is left of the standalone call page's styles. The shell, timer, state
   line and scripted transcript went with the page; these three are the
   controls the live voice rail on the chat page actually renders. */
.call-btn {
  position: relative;
  width: 52px; height: 52px; border-radius: 50%; border: 1px solid var(--line);
  background: var(--surface); color: var(--ink); font-size: 1.1rem; cursor: pointer;
  box-shadow: var(--shadow-sm); transition: transform .15s ease;
  display: inline-flex; align-items: center; justify-content: center;
}
.call-btn:hover { transform: translateY(-2px); }
.call-btn--end { background: #ffe9e4; border-color: #f6d0c7; color: #b04a40; }
.call-btn--end:hover { background: #ffdcd4; }
/* Muted reads as muted at a glance: the same speaker with a stroke through it.
   Drawn here rather than as a second icon, because two drawings of one control
   are two drawings that can stop agreeing. The outline is inset from the button
   edge so the strike crosses the glyph, not the circle. */
.call-btn--off { color: var(--muted); background: transparent; box-shadow: none; }
.call-btn--off::after {
  content: ""; position: absolute; left: 50%; top: 50%;
  width: 21px; height: 2px; border-radius: 2px; background: currentColor;
  transform: translate(-50%, -50%) rotate(-45deg);
}
.call-btn--off .ico { opacity: .75; }
/* The microphone is live, and this is the only button on the rail that says so.
   Teman speaking is the default and needs no badge; a microphone that is
   hearing is the state a person has to be able to check at a glance. Colour
   only — the strike above owns the off state, and filling the circle would
   read as a primary action rather than as a state. */
.call-btn--live { color: var(--pine-deep); background: var(--pine-soft); border-color: rgba(31, 157, 99, .5); }
.call-super {
  margin-top: 20px; font-size: 0.8rem; color: var(--pine-deep); display: inline-flex; gap: 8px; align-items: center;
  background: var(--pine-soft); border-radius: 999px; padding: 7px 15px;
}

/* ------------------------------------------------------------ check-in --- */
.instrument { max-width: 780px; margin: 0 auto; }
.q-item { border-bottom: 1px solid var(--line); padding: 20px 0; }
.q-item:last-of-type { border-bottom: 0; }
.q-text { font-weight: 500; margin-bottom: 14px; }
/* The answer is a position on a line, so the control is a range and not a row
   of buttons. The row reads [Rough] ————o———— [Good] 7, and the grid keeps the
   readout column a fixed width so the track does not shift as the number
   changes width from 0 to 10. */
.q-slide-row { display: grid; grid-template-columns: auto minmax(0, 1fr) auto 2.2rem; align-items: center; gap: 12px; }
.q-slide { width: 100%; margin: 0; accent-color: var(--pine); cursor: pointer; }
.q-slide:focus-visible { outline: 2px solid var(--pine); outline-offset: 4px; border-radius: 999px; }
.q-val { text-align: right; font-weight: 600; font-variant-numeric: tabular-nums; }
@media (max-width: 640px) {
  /* The anchors are the first thing to go on a phone: the slider is the
     control, and 0 and 10 are discoverable by dragging it. The readout stays,
     because it is the only exact number on the row. */
  .q-slide-row { grid-template-columns: minmax(0, 1fr) 2.2rem; }
  .q-slide-row > .tiny { display: none; }
}
.score-bar { height: 10px; border-radius: 999px; background: var(--sand); overflow: hidden; margin: 18px 0 6px; }
.score-bar i { display: block; height: 100%; border-radius: 999px; transition: width 0.6s ease; }
/* The four areas of the outcome measure, read side by side rather than as one
   number — a total hides which part of life moved. */
.subscale { margin-top: 14px; }
.subscale .score-bar { margin: 8px 0 0; height: 8px; }
/* The list owns the spacing between scales, so the children stop adding their
   own on top of it — otherwise the gap is 26px between items and 14px before
   the first, which reads as a mistake. */
.subscale-list { display: grid; gap: 14px; margin-top: 12px; }
.subscale-list .subscale { margin-top: 0; }
.result-card { border-radius: var(--radius); padding: 26px 28px; border: 1px solid var(--line); background: var(--surface); }
.result-card.tone-ok { border-color: #cdeada; background: #f2fbf6; } .result-card.tone-ok .score-bar i { background: var(--ok); }
.result-card.tone-mild { border-color: #f0e3c2; background: #fdf9ec; } .result-card.tone-mild .score-bar i { background: var(--mild); }
.result-card.tone-mid { border-color: #f3ddc8; background: #fdf4eb; } .result-card.tone-mid .score-bar i { background: var(--mid); }
.result-card.tone-high { border-color: #f5d3cb; background: #fdefec; } .result-card.tone-high .score-bar i { background: var(--high); }

/* ------------------------------------------------------------- account --- */
.stat-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 16px; }
@media (max-width: 900px) { .stat-grid { grid-template-columns: repeat(2, 1fr); } }
/* One column on a phone. Two 1fr tracks at 390px give each card about 160px,
   and the sparkline is drawn 260px wide — so the tracks grow to fit it, the
   grid overflows the page, and the whole account page scrolls sideways. The
   card's own min-width: 0 is the other half of the fix: a grid item's
   automatic minimum is its min-content width, which is exactly what let a
   260px drawing push a 160px column out of the viewport. */
@media (max-width: 560px) { .stat-grid { grid-template-columns: 1fr; } }
.stat-card { background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); padding: 18px; box-shadow: var(--shadow-sm); min-width: 0; }
/* A stat card that is a door — the Plan card on /account and /member, which
   opens the upgrade (2026-10-10). It keeps the card's look and gains the
   affordances of a link: colour is inherited so the card does not turn into a
   paragraph of green, the lift and border say it is pressable, and the same
   reduced-motion rule every other transform on the site follows. */
.stat-card--door { display: block; color: inherit; transition: border-color .15s ease, transform .15s ease, box-shadow .15s ease; }
.stat-card--door:hover { border-color: var(--pine); transform: translateY(-2px); box-shadow: var(--shadow-md); }
.stat-card--door:focus-visible { outline: 3px solid rgba(31, 157, 99, .35); outline-offset: 2px; }
@media (prefers-reduced-motion: reduce) { .stat-card--door { transition: none; } .stat-card--door:hover { transform: none; } }
.stat-card .k { color: var(--faint); font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.06em; }
.stat-card .v { font-family: var(--serif); font-weight: 800; font-size: 1.35rem; margin-top: 6px; }
.list-rows { display: grid; gap: 8px; }
.row {
  display: flex; align-items: center; gap: 16px; padding: 13px 18px; font-size: 0.93rem;
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius-sm);
}
.row .grow { flex: 1; }
.row .k { color: var(--faint); font-size: 0.8rem; }
/* A row that is a door: the whole line is the link, so the underline every
   other anchor gets on hover would run under the title, the date and the count
   at once. Same pine left edge the console rows take on hover, so "this row
   leads somewhere" looks the same on both sides of the product. */
.row--link { color: inherit; border-left: 3px solid transparent; }
.row--link:hover { text-decoration: none; border-left-color: var(--pine); background: #fbfdfb; }

/* A row that is a door AND carries an action. The anchor is a child of the row
   rather than the row itself, because a <button> inside an <a> is invalid HTML
   and each browser picks a different thing to do with the click. The hover
   moves up to the row so the pine left edge still appears over the whole line
   — the thing that says "this leads somewhere" — while the button sits outside
   the link and swallows its own clicks. */
.row--act { border-left: 3px solid transparent; gap: 10px; }
.row--act:hover { border-left-color: var(--pine); background: #fbfdfb; }
.row--act > .row__open {
  display: flex; align-items: center; gap: 16px; flex: 1; min-width: 0; color: inherit;
}
.row--act > .row__open:hover { text-decoration: none; }

/* The one destructive control a member has by default. Quiet until you go near
   it: a list of loud red buttons turns a history into a minefield, and the row
   already says what it is. It takes the clay the crisis card uses only on
   hover, where the intent is no longer ambiguous. */
.row__del {
  flex: none; font: inherit; font-size: 0.8rem; cursor: pointer;
  background: none; border: 1px solid transparent; border-radius: var(--radius-sm);
  color: var(--faint); padding: 5px 10px;
}
.row__del:hover { color: var(--high); border-color: #f0cfc7; background: #fdefec; }
.row__del:disabled { color: var(--faint); border-color: transparent; background: none; cursor: default; }
.row__del:focus-visible { outline: 2px solid var(--pine); outline-offset: 2px; }
.session-card__act { margin: 12px 0 0; }
/* The destructive button. Ghost until hovered, then clay — the same colour the
   crisis card uses for the same reason: it marks the thing that cannot be
   undone, and it is the only place in the member area that colour appears. */
.btn--danger:hover { color: var(--high); border-color: #f0cfc7; background: #fdefec; }
.badge { font-size: 0.75rem; font-weight: 600; border-radius: 999px; padding: 3px 10px; }
.badge--ok { background: var(--pine-soft); color: var(--pine-deep); }
.badge--plain { background: var(--sand); color: var(--muted); }
/* A check-in that flagged — a positive safety item, or a band that opens a
   case. Same token the high bands use, so the colour means one thing. */
.badge--warn { background: #fdefec; color: var(--high); }
/* The trend line. This selector used to read `.spark svg`, which matches
   nothing — sparkline() puts `class="spark"` on the svg itself, so the rule
   never applied and the drawing kept its hard-coded 260px width at every
   viewport. `svg.spark` is the element that actually exists, and max-width
   lets it shrink inside a narrow card instead of pushing the page sideways. */
svg.spark { display: block; max-width: 100%; height: auto; }

/* ------------------------------------------------------------- receipt ---
   The payment receipt on /payments: a document rendered inside the page and
   opened under its row by the Receipt button. It repeats what a document needs
   with no page around it, so it carries its own frame and its own heading rule,
   and the print button sits inside it — the thing it prints is the thing it
   sits under.

   THE PRINT RULE IS GATED ON `printing-receipt` ON <body>, set while a receipt
   is open and cleared when the last one closes. Ungated, `body * { visibility:
   hidden }` would make Ctrl+P print a blank sheet from every other page on the
   site. The visibility trick is used rather than `display: none` because it
   handles the receipt's whole ancestor chain at once and keeps every box in the
   tree, so nothing reflows on the way to paper; the absolute position lifts the
   block to the top of the sheet where the rest of the page used to be. */
.receipt {
  margin: 0 0 8px; padding: 22px 26px; background: var(--surface);
  border: 1px solid var(--line); border-radius: var(--radius-sm);
}
.receipt__head { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 12px; }
.receipt__head strong { font-family: var(--serif); font-size: 1.15rem; }
.receipt__lines { display: grid; grid-template-columns: auto 1fr; gap: 6px 18px; margin: 0; }
.receipt__lines dt { color: var(--faint); font-size: 0.8rem; }
.receipt__lines dd { margin: 0; font-size: 0.9rem; overflow-wrap: anywhere; }

@media print {
  body.printing-receipt * { visibility: hidden; }
  body.printing-receipt .receipt,
  body.printing-receipt .receipt * { visibility: visible; }
  body.printing-receipt .receipt {
    position: absolute; left: 0; top: 0; width: 100%;
    border: 0; padding: 0; background: #fff;
  }
  body.printing-receipt .receipt button { display: none; }
}

/* ------------------------------------------------------------ checkout --- */
.steps-nav { display: flex; gap: 10px; margin-bottom: 30px; flex-wrap: wrap; }
.step-pill {
  display: flex; align-items: center; gap: 9px; font-size: 0.85rem; color: var(--muted);
  border: 1px solid var(--line); border-radius: 999px; padding: 8px 16px; background: var(--surface);
}
.step-pill .n { width: 20px; height: 20px; border-radius: 50%; background: var(--sand); color: var(--muted); display: flex; align-items: center; justify-content: center; font-size: 0.74rem; font-weight: 700; }
.step-pill.on { color: var(--pine-deep); border-color: var(--pine); background: var(--pine-soft); font-weight: 600; }
.step-pill.on .n { background: var(--pine); color: #fff; }
/* The same pill as a button, because the application form's steps are clickable
   and the checkout's are not. Buttons do not inherit the page font on their own. */
button.step-pill { font: inherit; font-size: 0.85rem; cursor: pointer; }
button.step-pill[aria-selected="true"] { cursor: default; }
/* `.pay-grid`, `.pay-opt`, `.qr-box` and `.qr-box .fakeqr` stood here until
   2026-10-11 and dressed the payment step's mock picker — two buttons choosing
   PayNow or card, a fake QR, pre-filled card fields. The step is one real
   button on HitPay's own page now, and nothing rendered those classes; a style
   rule with no reader is a description of a page that no longer exists.
   `.field` below is site-wide (auth, apply, admin) and stays. */
.field { margin-bottom: 16px; }
.field label { display: block; font-size: 0.85rem; font-weight: 600; margin-bottom: 6px; }
.field input, .field select {
  width: 100%; border: 1px solid var(--line); border-radius: var(--radius-sm);
  padding: 12px 14px; font-family: var(--sans); font-size: 0.95rem; background: var(--surface);
}
.field input:focus, .field select:focus { outline: none; border-color: var(--pine); box-shadow: 0 0 0 3px rgba(31, 157, 99, .15); }

/* -------------------------------------------------------------- footer --- */
.site-foot { background: #eef7f1; border-top: 1px solid #ddefe4; color: var(--ink); margin-top: 40px; padding: 36px 0 26px; }
.site-foot a { color: var(--muted); }
.site-foot a:hover { color: var(--pine-deep); }
.site-foot .logo { color: var(--pine-deep) !important; font-size: 1.2rem; }
.site-foot .logo__mark { width: 24px; height: 24px; }
/* Five links and the mark — every other destination lives behind the menu. */
.foot-row { display: flex; align-items: center; justify-content: space-between; gap: 16px 28px; flex-wrap: wrap; }
.foot-links { display: flex; flex-wrap: wrap; gap: 4px 24px; font-size: 0.88rem; }
.foot-legal { font-size: 0.78rem; color: var(--faint); }

/* -------------------------------------------------------------- charts --- */
/* The visual language: fewer sentences, clearer pictures. */
.chart-card {
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 26px; box-shadow: var(--shadow-sm);
}
.chart-card h3 { margin-bottom: 0.25em; }
.chart-card .sub { font-size: 0.85rem; color: var(--faint); margin-bottom: 20px; }

.bars { display: grid; gap: 16px; }
.bar-row { display: grid; grid-template-columns: 10.5rem 1fr auto; align-items: center; gap: 14px; }
.bar-row .lbl { font-size: 0.88rem; color: var(--muted); }
.bar-track { height: 14px; background: var(--sand); border-radius: 999px; overflow: hidden; }
.bar-fill {
  height: 100%; border-radius: 999px; min-width: 12px;
  background: linear-gradient(90deg, #5fce9b, var(--pine));
  transform-origin: left center; animation: growIn 0.9s cubic-bezier(.2, .7, .2, 1) both;
}
.bar-fill--wa    { background: linear-gradient(90deg, #9fe8c1, #3fbc82); }
.bar-fill--clay  { background: linear-gradient(90deg, #f0a173, var(--clay)); }
.bar-fill--muted { background: linear-gradient(90deg, #dfe5e1, #c2ccc6); }
.bar-row .val { font-family: var(--serif); font-weight: 800; font-size: 0.98rem; }
.bar-note { font-size: 0.78rem; color: var(--faint); margin-top: 2px; }
@keyframes growIn { from { transform: scaleX(0.03); } to { transform: scaleX(1); } }

.donut-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 18px; text-align: center; }
/* The landing page shows two and the consoles three, which used to be done with
   an inline style on the element — and an inline style cannot be overridden by
   a media query, so the pair never collapsed and overflowed a phone. A modifier
   class can. */
.donut-grid--2 { grid-template-columns: repeat(2, 1fr); }
@media (max-width: 620px) { .donut-grid, .donut-grid--2 { grid-template-columns: 1fr; } }
.donut svg { display: block; margin: 0 auto 10px; }
.donut .dv { font-family: var(--serif); font-weight: 800; font-size: 1.1rem; }
.donut .dl { font-size: 0.82rem; color: var(--muted); }
.donut circle { fill: none; stroke-width: 9; stroke-linecap: round; }
.donut .track { stroke: var(--sand); }
.donut .arc { stroke: var(--pine); }

.legend { display: flex; flex-wrap: wrap; gap: 16px; font-size: 0.8rem; color: var(--muted); margin-top: 16px; }
.legend i { display: inline-block; width: 11px; height: 11px; border-radius: 4px; margin-right: 7px; vertical-align: -1px; }

.flow { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; }
.flow-step { background: var(--surface); border: 1px solid var(--line); border-radius: 18px; padding: 20px; text-align: center; box-shadow: var(--shadow-sm); }
.flow-step .ic {
  width: 46px; height: 46px; margin: 0 auto 10px; border-radius: 15px;
  background: var(--pine-soft); color: var(--pine-deep);
  display: flex; align-items: center; justify-content: center; font-size: 1.3rem;
}
.flow-step .t { font-family: var(--serif); font-weight: 800; font-size: 0.98rem; margin-bottom: 4px; }
.flow-step .d { font-size: 0.84rem; color: var(--muted); }

.mini-stats { display: flex; flex-wrap: wrap; gap: 10px; }
.mini-stat {
  display: inline-flex; align-items: baseline; gap: 7px;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: 999px; padding: 8px 16px; font-size: 0.85rem; color: var(--muted);
}
.mini-stat strong { font-family: var(--serif); font-size: 1rem; color: var(--pine-deep); }

.live-flag { font-size: 0.72rem; font-weight: 600; color: var(--pine-deep); background: var(--pine-soft); border-radius: 999px; padding: 3px 10px; }

/* --------------------------------------------------------- utilities  --- */
.stack { display: flex; flex-direction: column; gap: 14px; }
.inline { display: flex; gap: 12px; align-items: center; flex-wrap: wrap; }
.right { margin-left: auto; }
.hide { display: none !important; }
.page-head { padding: 46px 0 8px; }
.page-head .lede { color: var(--muted); font-size: 1.06rem; max-width: 46rem; }

/* ------------------------------------------------------------ accounts --- */
/* Sign-in / sign-up: one quiet card, Google first, email second. */
.auth-shell { max-width: 480px; margin: 0 auto; }
.auth-card { padding: 32px 34px; }
.auth-card h1 { font-size: 1.75rem; margin: 10px 0 6px; }
.auth-divider { display: flex; align-items: center; gap: 12px; margin: 20px 0 18px; color: var(--faint); font-size: 0.8rem; }
.auth-divider::before, .auth-divider::after { content: ""; flex: 1; height: 1px; background: var(--line); }
.btn--google { background: var(--surface); color: var(--ink); border-color: var(--line); font-weight: 600; }
.btn--google:hover { background: #f8f8f6; border-color: #d8d5cb; }
.g-logo { width: 18px; height: 18px; flex: none; }
.auth-error { color: var(--high); font-size: 0.85rem; background: #fdefec; border: 1px solid #f5d3cb; border-radius: var(--radius-sm); padding: 10px 14px; margin: 4px 0 14px; }
/* Reserves its own line so the change-password card does not jump the moment a
   refusal appears under it. */
.pw-msg { min-height: 1.1em; margin: 4px 0 10px; }
/* The sign-in page's email door. Google is the default, so the form behind
   this control is a disclosure: the control reads as a quiet line of text,
   not a second button competing with the one above it. */
.auth-toggle {
  display: block; width: 100%; margin: 12px 0 0; padding: 8px 0;
  background: none; border: 0; cursor: pointer; font: inherit;
  font-size: 0.875rem; color: var(--pine-deep); text-align: center;
  text-decoration: underline; text-underline-offset: 3px;
}
.auth-toggle:hover { color: var(--pine); }
.auth-toggle:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; border-radius: 6px; }
#email-form-wrap { margin-top: 14px; }
/* Google Identity Services renders its own iframe at a fixed width; centre it
   in the card rather than letting it sit left. */
#gsi-button { display: flex; justify-content: center; }
.auth-strip { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; background: var(--surface-2); border: 1px solid var(--line); border-radius: var(--radius); padding: 14px 18px; }

/* --------------------------------------------------------- admin console --- */
/* ------------------------------------------------------------ app shell --- */
/* The signed-in pages — my account, the counsellor console, the admin console
   — share one frame: a rail on the left carrying who you are and where you can
   go, and the working area beside it. The rail is the same object on all three
   so moving between them does not move the furniture, and a person who is both
   a member and a counsellor does not have to relearn the page.

   It is a rail and not a tab strip because the admin console now has nine
   destinations with counts on them. A strip of nine chips wraps onto two lines
   at this width and stops reading as navigation. */
.app { display: grid; grid-template-columns: 244px minmax(0, 1fr); gap: 26px; align-items: start; }

.app-side {
  position: sticky; top: 16px;
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 14px 12px; box-shadow: var(--shadow-sm);
}
.app-side__brand { display: flex; align-items: center; gap: 9px; padding: 2px 6px 12px; text-decoration: none; color: var(--ink); }
.app-side__brand span { font-weight: 600; font-size: 0.98rem; letter-spacing: -0.01em; }
.app-side__brand em { display: block; font-style: normal; font-weight: 400; font-size: 0.68rem; text-transform: uppercase; letter-spacing: 0.09em; color: var(--faint); }
.app-side__logo { width: 24px; height: 24px; flex: none; }
.app-side__me { display: flex; gap: 11px; align-items: center; padding: 12px 6px 13px; border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); }
.app-side__me .tiny { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 160px; }
.app-side__me strong { display: block; font-size: 0.92rem; line-height: 1.25; }
.app-side__me .avatar { flex: none; }

.app-nav { display: grid; gap: 2px; margin: 10px 0; }
.app-nav__group { font-size: 0.68rem; text-transform: uppercase; letter-spacing: 0.09em; color: var(--faint); padding: 13px 11px 5px; }
.app-nav__item {
  display: flex; align-items: center; gap: 10px; width: 100%; text-align: left;
  padding: 9px 11px; border: 0; border-left: 3px solid transparent; border-radius: 4px 10px 10px 4px;
  background: none; cursor: pointer; font: inherit; font-size: 0.9rem; color: var(--ink);
  transition: background .12s ease, border-color .12s ease;
}
.app-nav__item:hover { background: var(--surface-2); }
.app-nav__item.is-on { background: var(--pine-soft); color: var(--pine-deep); font-weight: 600; border-left-color: var(--pine); }
.app-nav__item .ico { width: 17px; height: 17px; flex: none; opacity: .85; }
.app-nav__n { margin-left: auto; font-size: 0.72rem; color: var(--faint); font-variant-numeric: tabular-nums; }
.app-nav__item.is-on .app-nav__n { color: var(--pine-deep); }
.app-nav__n--alert { background: var(--tag-warn-bg, #fdf3d8); color: #8a6414; border-radius: 999px; padding: 1px 7px; font-weight: 700; }
.app-side__foot { border-top: 1px solid var(--line); padding-top: 11px; display: grid; gap: 7px; }

.app-main { min-width: 0; }
.app-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 16px; flex-wrap: wrap; margin-bottom: 18px; }
.app-head h1 { font-size: 1.85rem; margin: 6px 0 2px; }

@media (max-width: 900px) {
  .app { grid-template-columns: 1fr; gap: 16px; }
  /* min-width: 0 on the rail is what makes the scrolling nav below work at all.
     A grid item's automatic minimum size is its min-content width, so without
     this the 1fr track grows to fit the whole nav strip — the strip never
     scrolls, the page does, and the account page is 1348px wide on a 390px
     phone. The overflow-x on .app-nav cannot help until something lets the
     column be narrower than its contents. */
  .app-side { position: static; min-width: 0; }
  .app-nav { grid-auto-flow: column; grid-auto-columns: max-content; overflow-x: auto; gap: 6px; padding-bottom: 4px; margin: 8px 0; }
  .app-nav__group { display: none; }
  .app-nav__item { white-space: nowrap; }
  /* Same trap on the footer: it becomes a row too, and on the member pages it
     carries the language toggle (the sign-out left the rail for the header
     menu on 2026-10-10; the consoles still keep their own controls here). */
  .app-side__foot { grid-auto-flow: column; overflow-x: auto; }
}

/* ---- management tables -------------------------------------------------- --
   A list you can work rather than read: a filter bar, columns that line up
   down the page, a selectable row with its own actions, and a footer that says
   what is being shown of what. The console is a tool, and tools get used in
   bulk — one row at a time is a demo, not a management screen. */
.mg-bar { display: flex; gap: 9px; align-items: center; flex-wrap: wrap; margin-bottom: 16px; }
.mg-bar .a-search { flex: 1 1 260px; }
.mg-count { font-size: 0.78rem; color: var(--faint); font-variant-numeric: tabular-nums; }

.mg { border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface); overflow: hidden; box-shadow: var(--shadow-sm); }
.mg__head, .mg__row { display: grid; align-items: center; gap: 12px; padding: 11px 16px; }
.mg__head {
  background: var(--surface-2); border-bottom: 1px solid var(--line);
  font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.06em; color: var(--faint); font-weight: 700;
}
.mg__row { border-bottom: 1px solid var(--line); cursor: pointer; transition: background .12s ease; }
.mg__row:last-child { border-bottom: 0; }
.mg__row:hover { background: #fbfdfb; }
.mg__row.is-sel { background: var(--pine-soft); }
.mg__row.is-off { opacity: .58; }
.mg__cell { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mg__who { display: flex; gap: 10px; align-items: center; min-width: 0; }
.mg__who strong { display: block; overflow: hidden; text-overflow: ellipsis; }
.mg__empty { padding: 30px 16px; text-align: center; color: var(--muted); font-size: 0.9rem; }
.mg__foot { display: flex; justify-content: space-between; align-items: center; gap: 12px; flex-wrap: wrap; padding: 12px 16px; border-top: 1px solid var(--line); background: var(--surface-2); font-size: 0.8rem; color: var(--muted); }

.mg__check { width: 16px; height: 16px; accent-color: var(--pine); cursor: pointer; margin: 0; }
.mg__acts { display: flex; gap: 4px; justify-content: flex-end; }
.mg__act {
  border: 1px solid transparent; background: none; cursor: pointer; border-radius: 8px;
  padding: 4px 8px; font: inherit; font-size: 0.76rem; color: var(--muted);
}
.mg__act:hover { background: var(--surface); border-color: var(--line); color: var(--ink); }
.mg__act--go:hover { color: var(--pine-deep); border-color: #cfe8da; background: #f2fbf6; }
.mg__act--stop:hover { color: #a03e30; border-color: #f6d0c7; background: #fdefec; }

/* The bulk bar replaces the filter bar's right side once rows are selected —
   an action that needs a selection must not be clickable without one, so it is
   not on screen without one. */
.mg-bulk {
  display: flex; gap: 10px; align-items: center; flex-wrap: wrap;
  background: var(--pine-soft); border: 1px solid #cfe8da; border-radius: var(--radius-sm);
  padding: 9px 14px; margin-bottom: 14px; font-size: 0.85rem; color: var(--pine-deep);
}
.mg-pager { display: flex; gap: 6px; align-items: center; }

/* ---- record sheet ------------------------------------------------------- --
   The detail panel for one account or one counsellor. Sub-tabs rather than one
   long scroll: "who they are", "what they have done here" and "what can be
   done to them" are three different jobs and get three panes. */
.sheet { border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface); box-shadow: var(--shadow-md); overflow: hidden; }
.sheet__top { display: flex; gap: 14px; align-items: flex-start; padding: 18px 20px; border-bottom: 1px solid var(--line); background: linear-gradient(160deg, #f3faf6 0%, var(--surface) 70%); }
.sheet__top h3 { margin: 0 0 2px; }
.sheet__tabs { display: flex; gap: 4px; padding: 0 20px; border-bottom: 1px solid var(--line); background: var(--surface); flex-wrap: wrap; }
.sheet__tab {
  border: 0; border-bottom: 2px solid transparent; background: none; cursor: pointer;
  padding: 11px 12px; font: inherit; font-size: 0.86rem; color: var(--muted);
}
.sheet__tab:hover { color: var(--ink); }
.sheet__tab.is-on { color: var(--pine-deep); border-bottom-color: var(--pine); font-weight: 600; }
.sheet__body { padding: 20px; }
.sheet__kv { display: grid; grid-template-columns: 150px minmax(0, 1fr); gap: 9px 16px; font-size: 0.88rem; align-items: baseline; }
.sheet__kv dt { color: var(--faint); font-size: 0.8rem; }
.sheet__kv dd { margin: 0; word-break: break-word; }
.sheet__grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); gap: 14px; }

.admin-tabs { display: flex; gap: 8px; flex-wrap: wrap; margin: 20px 0 22px; }
.drawer { background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); padding: 22px 24px; box-shadow: var(--shadow-sm); }
.a-input {
  width: 100%; border: 1px solid var(--line); border-radius: var(--radius-sm);
  padding: 9px 12px; font-family: var(--sans); font-size: 0.9rem; background: var(--surface); color: var(--ink);
}
.a-input:focus { outline: none; border-color: var(--pine); box-shadow: 0 0 0 3px rgba(31, 157, 99, .15); }
textarea.a-input { resize: vertical; }
.a-search {
  flex: 1; min-width: 220px; border: 1px solid var(--line); border-radius: 999px;
  padding: 10px 16px; font-family: var(--sans); font-size: 0.9rem; background: var(--surface); color: var(--ink);
}
.a-search:focus { outline: none; border-color: var(--pine); box-shadow: 0 0 0 3px rgba(31, 157, 99, .15); }
.row[data-user], .row[data-case], .row[data-counsellor] { border-left: 3px solid transparent; }
.row[data-user]:hover, .row[data-case]:hover, .row[data-counsellor]:hover { border-left-color: var(--pine); background: #fbfdfb; }

/* ------------------------------------------- phones: one type scale ------- --
   THE DISPLAY SIZES WERE WRITTEN FOR A LAPTOP. `h1`'s clamp bottoms out at
   2rem, which is what a 390px phone gets: measured on the live site on
   2026-10-10, the greeting took two lines at 32px and the lede under it ran to
   five more. The first screen then held a headline, a paragraph and a pulsing
   mark — and the box you type into, which is the only thing that page is for,
   sat below the fold.

   ONE SCALE FOR THE WHOLE SITE, set in one place, rather than per component.
   The public pages had already drifted into near-duplicates — `.hero .lede` at
   1.1rem against `.page-head .lede` at 1.06rem — and four hundredths of a rem
   is not a distinction anybody can see; it is two authors rounding differently.
   The founder's ask was that About us and Legal match the home page, so they
   now share one rule instead of being tuned one at a time. A fourth and fifth
   copy of the scale is how the drift started.

   WHY 640 AND NOT 880. 880 is where the hero GRID stacks, which is a layout
   decision a tablet shares; a tablet has the width for desktop type. 640 is
   where the measure gets tight enough that the type has to come down.

   Everything here is a phone override of a rule that appears earlier in this
   file, so it belongs at the end — a media query loses to a later rule of equal
   specificity, and the component rules above (`.hero h1`, `.auth-card h1`)
   would otherwise win. */
@media (max-width: 640px) {
  h1 { font-size: 1.6rem; }
  h2 { font-size: 1.22rem; }
  /* Both ledes, one value — see above. */
  .hero .lede, .page-head .lede { font-size: 0.97rem; }
  /* The header padding comes in with the type. 46px of air above a 26px
     heading was measured against the 32px one it was written for. */
  .page-head { padding: 30px 0 6px; }
  /* The two headings that carry a size of their own, and that would otherwise
     end up LARGER than every page title on the same device: a sign-in card at
     1.75rem and a console header at 1.85rem, against the 1.6rem a page title
     now uses. That ranking is defensible on a desktop and reads as a mistake on
     a phone, where both are the only heading on screen. */
  .auth-card h1 { font-size: 1.45rem; }
  .app-head h1 { font-size: 1.5rem; }
}

@media (prefers-reduced-motion: reduce) {
  /* The mark and its halo were missing from this list, which made the one
     animation the product leads with — the mark beating on the landing page and
     on the chat rail — the one piece of motion that ignored the setting. With
     the animation off the halo rests at its own inset rather than mid-pulse, so
     it reads as a still ring around the mark instead of a shrunken one.

     `.mark__green`, not `.bot__mark`: the beat is on the green circle now, and
     a list naming the svg would leave the part that actually moves running.
     `.founder__ring` is the halo's twin on the about page — same keyframes,
     different element — so the list has to name it too. */
  .bubble, .bar-fill, .mark__green, .bot__halo, .founder__ring { animation: none !important; transform: none !important; }
  html { scroll-behavior: auto; }
}

/* -------------------------------------------------------------- language --- */
/* EN · 中文 · BM — the toggle, in the header bar and in the member rail.
   Deliberately quiet: it is a door, not a feature. The current language is
   ink and the others are muted, so the state is legible in a glance and the
   control never competes with the conversation. */
.lang { display: inline-flex; align-items: center; gap: 7px; font-size: 0.8rem; line-height: 1; white-space: nowrap; }
.lang__a { color: var(--faint); text-decoration: none; padding: 2px 1px; }
.lang__a:hover, .lang__a:focus-visible { color: var(--ink); }
.lang__a--on { color: var(--ink); font-weight: 700; }
.lang__dot { color: var(--faint); opacity: 0.6; }
.lang--rail { display: block; padding: 5px 2px 2px; }
/* [hidden] is a UA-sheet display:none and loses to any author rule that sets
   display — which .lang--rail does. Without this, a page carrying both the
   header toggle and the rail one shows the rail's too (i18n.js hides it with
   the attribute; the attribute has to actually win). */
.lang[hidden] { display: none; }

/* Chinese is set in faces that have the glyphs. Proza Libre does not, and a
   fallback chain that ends at a Latin-only family renders every character as
   a tofu box — the page loads, the copy is there, and nobody can read it.
   The Latin face stays first so the brand and the numbers keep their voice. */
html[lang="zh-Hans"] body,
html[lang="zh-Hans"] button,
html[lang="zh-Hans"] input,
html[lang="zh-Hans"] select,
html[lang="zh-Hans"] textarea {
  font-family: "Proza Libre", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Noto Sans CJK SC", "Segoe UI", system-ui, sans-serif;
}

/* The other half of setting Chinese: the sizes. The founder — "for all 中文
   pages, the texts need to be downsized to properly suit the overall layout
   and design aesthetics as Chinese characters are larger in shape than
   English".

   A Han glyph is a full em square; a Latin lowercase letter is about half of
   one. So the same px size is two different sizes to the eye, and the gap
   widens as the type grows — which is why the reduction below grows with it:
   almost nothing on the 15px body (15px of Han is still comfortably smaller
   than 16px of English), a tenth on the card titles, a sixth on the h2s and
   the prices, a fifth on the h1.

   Two things travel with the sizes, because smaller type on its own reads as
   smaller type rather than as the same page set in another script.

   Tracking goes the other way. The headings' `-0.02em` is a Latin display
   correction — Proza Libre's caps sit loose at 3rem — and on Han it closes
   gaps the glyphs already carry, so headings get `normal` here. The uppercase
   eyebrows keep theirs: at 0.03em on an 11px label the effect is a third of a
   pixel, and the Latin words inside them are set with it.

   Leading opens up, in the headings and in the body. Han has no ascenders or
   descenders to spread a line, and no lowercase shapes for the eye to run
   along, so 1.22 on the headings reads as bricks stacked and 1.6 in a
   paragraph reads as a wall. 1.32 and 1.7 are the usual answers.

   Sits here, at the end, for the same reason the font rule above does: it is
   a language override, and an equal-specificity rule that comes later wins —
   so the mobile media query's own h1/h2 sizes are beaten by these without it
   having to know they exist. `:lang(zh-Hans)` would say the same thing and
   travel better, but the file already asks this question with the attribute,
   in the rule above, and two spellings of one idea is how drift starts. */
html[lang="zh-Hans"] body { font-size: 15px; line-height: 1.7; }
html[lang="zh-Hans"] h1,
html[lang="zh-Hans"] h2,
html[lang="zh-Hans"] h3,
html[lang="zh-Hans"] h4 { line-height: 1.32; }
html[lang="zh-Hans"] h1 { font-size: clamp(1.45rem, 3.9vw, 2.55rem); }
html[lang="zh-Hans"] h2 { font-size: clamp(1.15rem, 2.5vw, 1.78rem); }
html[lang="zh-Hans"] h3 { font-size: 1.05rem; }
html[lang="zh-Hans"] h4 { font-size: 0.95rem; }
html[lang="zh-Hans"] h1,
html[lang="zh-Hans"] h2,
html[lang="zh-Hans"] h3,
html[lang="zh-Hans"] h4,
html[lang="zh-Hans"] .door__title,
html[lang="zh-Hans"] .door__lines b,
html[lang="zh-Hans"] .founder__name { letter-spacing: normal; }
/* The display sizes that are not headings: each is a line of Han sitting in a
   component built around its English length, so each comes down by the same
   one-to-two-fifths as the heading level it stands beside. */
html[lang="zh-Hans"] .hero .lede { font-size: 1rem; }
html[lang="zh-Hans"] .page-head .lede { font-size: 0.98rem; }
html[lang="zh-Hans"] .section_head p { font-size: 0.95rem; }
html[lang="zh-Hans"] .door__title { font-size: 1.05rem; }
html[lang="zh-Hans"] .door__lines b { font-size: 1.45rem; }
html[lang="zh-Hans"] .plan-card__price { font-size: 1.75rem; }
html[lang="zh-Hans"] .stat__big { font-size: 1.85rem; }
html[lang="zh-Hans"] .help-line__num { font-size: 1.4rem; }
html[lang="zh-Hans"] .founder__name { font-size: 1.35rem; }
html[lang="zh-Hans"] .auth-card h1 { font-size: 1.5rem; }
html[lang="zh-Hans"] .app-head h1 { font-size: 1.55rem; }
html[lang="zh-Hans"] .stat-card .v { font-size: 1.2rem; }
/* The chips, which the first pass of this block missed and the founder found:
   "the texts are still too oversized, especially 工作让我喘不过气 / 我的脑子停不下来
   / 我睡不着 / 家里气氛很紧张 / 我感到孤单 / 我觉得自己很失败 / 我失去了一个人".

   They were missed for a reason worth writing down, because the same reason
   hides anything else set the same way: every other size on this page is
   inherited, so the 15px body carried the reduction into it — a chip is the
   one element that sets its own `font-size` (0.875rem), and 0.875rem of Han
   is not a reduced size at all. It reads as the largest text in the hero row
   it sits in, which is exactly how he read it.

   The reduction is the same tenth the card titles take, and the two padding
   numbers come down with it — a pill whose box held its old size around
   smaller type reads as cramped rather than as smaller. 1.5 leading, not the
   body's 1.7: on one line of a pill the line-height is the pill's height, so
   here it belongs with the type rather than with the paragraphs.

   `.topic-chip` is the chat page's grid of the same seven starting points,
   and it keeps its own step below the hero's chips — 0.77 against 0.8, the
   same relationship as 0.84 against 0.875 — because the two rows are the same
   menu twice and a Han reduction that flattened them into one size would be
   the tail wagging the page. */
html[lang="zh-Hans"] .chip { font-size: 0.8rem; line-height: 1.5; padding: 8px 13px; }
html[lang="zh-Hans"] .topic-chip { font-size: 0.77rem; padding: 9px 13px; }
