/* =====================================================================
   thilo.ai public website — shared stylesheet for index.html and the
   generated changelog pages.

   Served straight off nginx's root (static/website/), OUTSIDE the
   Tailwind pipeline — so the design system's semantic tokens cannot be
   consumed as utilities here. DESIGN-SYSTEM EXCEPTION, deliberate: the
   custom properties below are the LITERAL values of the tokens they are
   named after in libs/thilo-design/src/app/globals.css (light in :root,
   dark in the `.dark` block). Change them only when those tokens change
   upstream, and never introduce a colour that is not one of them —
   --accent-dot is the single exception, the mark's own fixed yellow
   (#FADB14 in both THILO-light.svg and THILO-dark.svg), and --accent-ink
   is that same yellow darkened for TEXT (see below).

   THE SITE HAS NO THEME (TCKQKMT9K9). It does not read the OS colour
   scheme, does not remember a choice and has no toggle — a visitor whose
   machine is in dark mode sees exactly this page. What it has instead is
   a TONE PER SECTION: `data-tone="dark|light"` on a section redeclares
   the whole palette for that subtree, the landing page alternates (opening
   on two dark screens, hero + #what, and closing on one), and every
   component below keeps the one set of selectors it already had. See
   docs/developer/60-stage-install.md § Section tones.

   THE PORTAL UNDER /members/ DOES (TCKKU9FF9E), and it is the same one
   mechanism rather than a second one: the theme is a CHOICE OF TONE FOR
   THE WHOLE DOCUMENT, written as `data-tone` on <html> by the page's own
   inline boot script and flipped by `#theme-toggle` in the header. There
   is still exactly ONE dark block in this file, no media query asking the
   OS and no second attribute — a members page in dark IS a document whose
   one tone is the dark one, and every rule below stays as it was. A
   signed-in person is reading a portal, not looking at a designed
   sequence, and that is the whole difference. See
   docs/developer/124-members-area.md § Light and dark.
   ===================================================================== */

/* The light tone = the design system's :root token values. It is also the
   page default, so a document with no tone anywhere (the changelog, a
   members page before its <body data-tone> is read) is light. */
:root,
[data-tone="light"] {
  --background: oklch(1 0 0);
  --foreground: oklch(0.145 0 0);
  --card: oklch(1 0 0);
  --card-foreground: oklch(0.145 0 0);
  --muted-foreground: oklch(0.556 0 0);
  --border: oklch(0.922 0 0);
  --primary: oklch(0.205 0 0);
  --primary-foreground: oklch(0.985 0 0);
  --secondary: oklch(0.97 0 0);
  --secondary-foreground: oklch(0.205 0 0);
  --muted: oklch(0.97 0 0);
  --input: oklch(0.922 0 0);
  --destructive: oklch(0.577 0.245 27.325);
  --destructive-foreground: oklch(0.985 0 0);
  --ring: oklch(0.708 0 0);
  /* Accent-coloured TEXT. --accent-dot is a mark colour: it is legible as a
     fill, a border or a dot, and it is what the logo is drawn in — but as
     type on the light ground it lands at 1.4:1, well under WCAG's 4.5:1,
     and the "How it works" step numbers were unreadable (TCKRWWUJQY). This
     is not a second colour: it is --accent-dot itself, mixed toward the
     palette's own near-black until it clears the bar (5.2:1 on --card).
     The dark tone sets it back to the plain yellow, which is 13:1 there.
     Use it wherever the accent is set as text; leave fills, borders and
     the mark's own dot on --accent-dot. */
  --accent-ink: color-mix(in oklab, var(--accent-dot) 52%, oklch(0.145 0 0));

  /* The floating header pill's glass (TCKHXDEHPC, site.css § header). Three
     tokens rather than three literals, because the pill floats over whatever
     section is under it and its tone flips on a scroll boundary: the ground it
     lets through, the hairline that separates it from that ground and the
     shadow that lifts it all have to change together, and they are what the
     nav-open menu card is drawn in too.

     Ground and border are the tone's own --background and --foreground, mixed
     down to the alpha the glass wants — on the light tone the hairline is a
     thin dark line, on the dark tone a light one, which is the same hairline
     either way. The SHADOW is black in both tones and deliberately not mixed
     from --foreground: a drop shadow is an absence of light, not a palette
     colour, and --foreground is near-white on the dark tone — a white halo
     under a pill is not a shadow. */
  --glass-bg: color-mix(in oklab, var(--background) 62%, transparent);
  --glass-border: color-mix(in oklab, var(--foreground) 8%, transparent);
  --glass-shadow: 0 8px 32px rgb(0 0 0 / 8%);

  /* The two lifts a section's content uses, per tone for the same reason as
     the glass shadow (TCK7FN72UU): a shadow is black alpha in both tones, but
     the amount that reads depends on the ground under it. What lifts a frame
     off white — 35% black, feathered — is invisible on the dark tone's near-
     black, so the dark block below asks for more of it and a lighter spread.
     `--shot-shadow` is the resting lift of a framed picture (the product shot
     in #what, the gallery stage); `--lift-shadow` is the hover lift of a card.
     Nothing else here is a shadow; do not add a third for one element. */
  --shot-shadow: 0 1px 2px rgb(0 0 0 / 6%), 0 24px 68px -24px rgb(0 0 0 / 35%);
  --lift-shadow: 0 16px 40px -20px rgb(0 0 0 / 30%);
}

/* ------------------------------------------------- tone-independent

   Everything that is NOT part of the palette, so a section flipping its
   tone does not redeclare it: the mark's own yellow, the two names the
   splash canvas reads, the radius and the hero band's fixed ground. They
   live on :root once and inherit into every tone. */
:root {
  --accent-dot: #fadb14;          /* the mark's own accent, not a token */
  /* Read by the hero splash's canvas (/splash.js), which resolves colours
     itself — a canvas `font` string is not CSS and a canvas fillStyle
     cannot follow a `var()`, so the engine fetches these two by name.
     --thilo-ai-spark is the design system's token, literal (the same value
     in light and dark upstream). --font-inter names the family the lockup
     is set in: Inter in the app, the website's own Geist here — it is the
     only face this page loads, and the lockup has to match the headline
     under it. */
  --thilo-ai-spark: oklch(0.8 0.15 78);
  --font-inter: "Geist";
  --radius: 0.625rem;

  /* ---------------------------------------------------- the hero band
     DESIGN-SYSTEM EXCEPTION, and a deliberate one (TCKK73JEQ6) — the same
     kind as --screen-bg for phones in doc 60. The band at the top of the
     landing page is ARTWORK: the designer chose its ground and the blue→
     cyan ramp the dots are drawn in when she drew it
     (libs/thilo-design/hero-animation/hero-terrain/), and the palette is
     a property of the picture rather than of the page. The nearest token,
     the dark tone's --background at oklch(0.145 0 0), is a neutral grey and
     it visibly mutes the blue glow — the piece stops being the piece.

     So this is ONE fixed value: the band does not follow a tone at all, the
     way a photograph would not. Everything drawn ON it is still a token —
     the copy block carries `.on-dark-ground` (below) and gets the dark
     tone's own foreground, muted, primary and border. The dots' two colours
     live in tools/website/hero-terrain-engine.ts, next to the code that
     mixes them; no CSS can reach inside a canvas.

     Nothing else on the site may use this. If a second dark band ever wants
     it, that is the moment to ask the designer rather than the moment to
     reach for --hero-ground. */
  --hero-ground: #04060f;
}

/* The dark tone = the design system's `.dark` token values — ONE block,
   because there is one way to be dark: a section (or a header, or a footer)
   that carries `data-tone="dark"`.

   `.on-dark-ground` rides along with it. That is how the hero band's copy
   gets the dark tone's tokens without a second copy of the palette to keep
   in step: the band's ground is fixed artwork (--hero-ground above), so
   everything set on it has to be dark whatever tone the section around it
   carries. Custom properties inherit from the nearest ancestor that sets
   them, so an element with this class hands the dark values down to its
   whole subtree. It is not a "dark mode" class for general use — put it on
   a block that sits on --hero-ground, and nowhere else. */
[data-tone="dark"],
.on-dark-ground {
  --background: oklch(0.145 0 0);
  --foreground: oklch(0.985 0 0);
  --card: oklch(0.205 0 0);
  --card-foreground: oklch(0.985 0 0);
  --muted-foreground: oklch(0.708 0 0);
  --border: oklch(1 0 0 / 10%);
  --primary: oklch(0.922 0 0);
  --primary-foreground: oklch(0.205 0 0);
  --secondary: oklch(0.269 0 0);
  --secondary-foreground: oklch(0.985 0 0);
  --muted: oklch(0.269 0 0);
  --input: oklch(1 0 0 / 15%);
  --destructive: oklch(0.704 0.191 22.216);
  --destructive-foreground: oklch(0.985 0 0);
  --ring: oklch(0.556 0 0);
  --accent-ink: var(--accent-dot);
  /* The pill over a dark section: less of the ground held back (the dark
     terrain reads through it), a brighter hairline, a heavier shadow. */
  --glass-bg: color-mix(in oklab, var(--background) 38%, transparent);
  --glass-border: color-mix(in oklab, var(--foreground) 22%, transparent);
  --glass-shadow: 0 8px 32px rgb(0 0 0 / 40%);
  /* A frame on the dark ground: the feathered shadow twice as dense, with
     the near-contact layer dropped — on near-black it is the frame's own
     1px --border (the light hairline every card and frame already carries)
     that separates it from the ground, and the shadow only gives it depth
     (the reference shot on TCKWF57JJR). */
  --shot-shadow: 0 24px 68px -24px rgb(0 0 0 / 70%);
  --lift-shadow: 0 16px 40px -20px rgb(0 0 0 / 65%);
}

/* What the UA draws for the tone rather than for the page: the form controls
   in #contact, a <select>, a text caret, a scrollbar inside a scroller. On the
   landing page the tone is on a section, so the PAGE-level scrollbar follows
   :root, which carries none and is light. On a members page the tone is on
   <html> itself, so the scrollbar follows the chosen theme — which is what a
   portal drawn edge to edge in the dark palette has to do. */
[data-tone="light"] { color-scheme: light; }
[data-tone="dark"] { color-scheme: dark; }

/* A toned block paints its OWN ground. It has to: `body` cannot, because the
   landing page has no single tone — the sections alternate down it, and the
   edge between two of them is exactly where one background stops and the
   next begins. Sharp, with no gradient between: the ground is a property of
   the section, the way it is on a printed page.

   `.site-header` (site.css) sets a translucent background of its own over
   this, and `.hero-band` its fixed artwork ground; both come later in the
   cascade at the same specificity, which is the whole reason those two
   rules are where they are. */
[data-tone] {
  background: var(--background);
  color: var(--foreground);
}

* { box-sizing: border-box; }

/* `[hidden]` must keep winning. The UA stylesheet's `[hidden]{display:none}`
   loses to ANY author `display` rule — and `.field input` below sets
   `display: block` — so without this the form's conditional "where did you
   hear about us?" input is permanently visible. Site-wide and `!important`
   because the bug is silent: nothing errors, the element is just there. */
[hidden] { display: none !important; }

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

/* The light tone's ground, as a fallback under everything: the landing page
   covers `body` edge to edge with its own toned sections, and every other
   page carries `data-tone="light"` on this element itself. */
body {
  margin: 0;
  background: var(--background);
  color: var(--foreground);
  font-family: "Geist", ui-sans-serif, system-ui, -apple-system, sans-serif;
  font-synthesis-weight: none;
  -webkit-font-smoothing: antialiased;
}

a { color: inherit; }

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

/* ---------------------------------------------------------------- mark */

.mark {
  display: block;
  color: var(--foreground);
}

/* --------------------------------------------------------------- home */

/* The landing page is one centred hero and nothing else. `min-height:
   100svh` (not 100vh) so the mobile browser chrome cannot push the
   buttons under the fold. */
body.home {
  min-height: 100svh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 32px 24px;
}

.hero {
  width: 100%;
  max-width: 420px;
  text-align: center;
}

.hero .mark {
  width: 96px;
  height: 96px;
  margin: 0 auto 24px;
}

.wordmark {
  margin: 0;
  font-size: 40px;
  font-weight: 600;
  letter-spacing: -0.02em;
  line-height: 1.1;
}

.wordmark .dot { color: var(--accent-dot); }

.actions {
  margin-top: 36px;
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  justify-content: center;
}

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

/* Design-system button, `size="default"`: h-9, rounded-md, text-sm,
   font-medium — libs/thilo-design/components/button.md. */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 36px;
  padding: 0 16px;
  border: 1px solid transparent;
  border-radius: calc(var(--radius) * 0.8);
  font-size: 14px;
  font-weight: 500;
  line-height: 1;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
}

.btn-primary {
  background: var(--primary);
  color: var(--primary-foreground);
}

.btn-secondary {
  background: var(--secondary);
  color: var(--secondary-foreground);
  border-color: var(--border);
}

.btn:hover { opacity: 0.9; }

@media (prefers-reduced-motion: no-preference) {
  .btn { transition: opacity 150ms ease, color 150ms ease; }
}

/* ---------------------------------------------------------- changelog */

/* A document page, not a hero: the body scrolls normally. */
body.doc { padding: 0; }

.doc-header {
  border-bottom: 1px solid var(--border);
  background: var(--background);
}

.doc-header-inner,
.doc-main {
  max-width: 720px;
  margin: 0 auto;
  padding: 0 24px;
}

.doc-header-inner {
  height: 64px;
  display: flex;
  align-items: center;
  gap: 12px;
}

.doc-header .mark { width: 28px; height: 28px; }

.doc-header .home-link {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 15px;
  font-weight: 600;
  text-decoration: none;
  letter-spacing: -0.01em;
}

.doc-header .home-link .dot { color: var(--accent-dot); }

.langs {
  margin-left: auto;
  display: flex;
  gap: 6px;
  font-size: 13px;
}

.langs a {
  padding: 4px 8px;
  border-radius: calc(var(--radius) * 0.6);
  color: var(--muted-foreground);
  text-decoration: none;
}

.langs a:hover { color: var(--foreground); }

.langs a[aria-current="page"] {
  background: var(--muted);
  color: var(--foreground);
}

.doc-main {
  padding-top: 40px;
  padding-bottom: 80px;
  font-size: 15px;
  line-height: 1.7;
}

.doc-main h1 {
  margin: 0 0 32px;
  font-size: 30px;
  font-weight: 600;
  letter-spacing: -0.02em;
}

/* Every `## ` in CHANGELOG.md is one released version — give it a rule
   above so the releases read as separate blocks. */
.doc-main h2 {
  margin: 56px 0 16px;
  padding-top: 28px;
  border-top: 1px solid var(--border);
  font-size: 22px;
  font-weight: 600;
  letter-spacing: -0.01em;
}

.doc-main h2:first-of-type { margin-top: 0; padding-top: 0; border-top: 0; }

.doc-main h3 {
  margin: 32px 0 12px;
  font-size: 16px;
  font-weight: 600;
}

.doc-main p { margin: 0 0 16px; }

.doc-main ul, .doc-main ol { margin: 0 0 16px; padding-left: 22px; }

.doc-main li { margin: 0 0 6px; }

.doc-main li > ul, .doc-main li > ol { margin-top: 6px; }

.doc-main strong { font-weight: 600; }

.doc-main code {
  font-family: "Geist Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.875em;
  padding: 0.1em 0.35em;
  border-radius: calc(var(--radius) * 0.4);
  background: var(--muted);
}

.doc-main pre {
  overflow-x: auto;
  padding: 14px 16px;
  border: 1px solid var(--border);
  border-radius: calc(var(--radius) * 0.8);
  background: var(--card);
}

.doc-main pre code { padding: 0; background: none; }

.doc-main a { color: var(--foreground); text-underline-offset: 3px; }

.doc-main hr {
  margin: 40px 0;
  border: 0;
  border-top: 1px solid var(--border);
}

.doc-main blockquote {
  margin: 0 0 16px;
  padding-left: 16px;
  border-left: 2px solid var(--border);
  color: var(--muted-foreground);
}

.doc-empty { color: var(--muted-foreground); }

.doc-back {
  display: inline-block;
  margin-top: 8px;
  font-size: 13px;
  color: var(--muted-foreground);
  text-decoration: none;
}

.doc-back:hover { color: var(--foreground); }

/* --------------------------------------------------------------- form */

/* The Request account page (`request/index.html`). A `.doc` page — same
   header, same 720px column — with a narrower form card in it.
   Field metrics are libs/thilo-design/components/{input,forms}.md:
   `size="default"` = 36px tall, rounded-md, 14px text, and the error
   state is `--destructive` on BOTH the label and the control, never on
   only one of them. */

.form-main {
  max-width: 480px;
  padding-top: 48px;
  padding-bottom: 80px;
}

.form-main h1 {
  margin: 0 0 8px;
  font-size: 28px;
  font-weight: 600;
  letter-spacing: -0.02em;
}

.form-lede {
  margin: 0 0 32px;
  font-size: 15px;
  line-height: 1.6;
  color: var(--muted-foreground);
}

.field { margin-bottom: 18px; }

.field > label {
  display: block;
  margin-bottom: 6px;
  font-size: 14px;
  font-weight: 500;
}

.field .req { color: var(--destructive); margin-left: 2px; }

.field input,
.field select {
  display: block;
  width: 100%;
  height: 36px;
  padding: 0 12px;
  border: 1px solid var(--input);
  border-radius: calc(var(--radius) * 0.8);
  background: var(--background);
  color: var(--foreground);
  font-family: inherit;
  font-size: 14px;
  line-height: 1;
}

.field select {
  /* Native control, so the arrow is the platform's — only the metrics and
     the palette are ours. `appearance: auto` is spelled out because some
     resets in the wild kill it and the arrow with it. */
  appearance: auto;
  padding-right: 8px;
}

.field input::placeholder { color: var(--muted-foreground); }

.field input:focus-visible,
.field select:focus-visible {
  outline: 2px solid var(--ring);
  outline-offset: 2px;
}

/* `aria-invalid` on the control and `data-invalid` on the field — both,
   always, or half the error state is invisible (forms.md). */
.field[data-invalid="true"] > label { color: var(--destructive); }

.field input[aria-invalid="true"],
.field select[aria-invalid="true"] {
  border-color: var(--destructive);
}

.field input[aria-invalid="true"]:focus-visible,
.field select[aria-invalid="true"]:focus-visible {
  outline-color: var(--destructive);
}

.field-error {
  margin: 6px 0 0;
  font-size: 13px;
  color: var(--destructive);
}

.field-hint {
  margin: 6px 0 0;
  font-size: 13px;
  color: var(--muted-foreground);
}

/* The honeypot. Off-screen rather than `display: none`: a bot that reads
   the stylesheet skips a hidden input, and one that does not fills this
   in. `aria-hidden` + `tabindex="-1"` keep it away from real people. */
.hp {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.form-actions {
  margin-top: 28px;
  display: flex;
  align-items: center;
  gap: 12px;
}

.btn[disabled] { opacity: 0.5; cursor: not-allowed; }

/* One status line under the button: the submit failure, or the fallback
   text when the page is opened with no JS. Success replaces the whole
   form instead — see `.form-done`. */
.form-status {
  margin: 16px 0 0;
  font-size: 14px;
  line-height: 1.6;
  color: var(--destructive);
}

.form-done {
  padding: 24px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--card);
  color: var(--card-foreground);
}

.form-done h2 {
  margin: 0 0 8px;
  font-size: 18px;
  font-weight: 600;
}

.form-done p { margin: 0 0 8px; font-size: 14px; line-height: 1.6; }

.form-done p:last-child { margin-bottom: 0; color: var(--muted-foreground); }

.form-done code {
  font-family: "Geist Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.875em;
  padding: 0.1em 0.35em;
  border-radius: calc(var(--radius) * 0.4);
  background: var(--muted);
}

/* ------------------------------------------------- members area (F4) */

/* The members area, the signup and the login (TCKYHP9CQU). Same rule as
   everything above: only the variables declared at the top of this file,
   which are the design system's token VALUES. No new colour is invented
   here, and none may be. */

/* Header. The changelog pages put the language switch in `.langs`; these
   pages have more to fit, so the whole right-hand cluster is one flex row
   that wraps on a narrow phone rather than pushing the mark off-screen. */
.doc-tools {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  justify-content: flex-end;
}

.member-nav {
  display: flex;
  align-items: center;
  gap: 8px;
}

/* The Members link is a `.btn.btn-primary` (site.css § header explains
   why); `.member-link` only keeps it from wrapping. The sign-out and the
   theme toggle are icon buttons sharing one set of metrics — 32px square,
   the same as the language select next to them. */
.member-link { white-space: nowrap; }

.member-signout,
.theme-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: calc(var(--radius) * 0.6);
  background: var(--background);
  color: var(--foreground);
  cursor: pointer;
}

.member-signout:hover,
.theme-toggle:hover { background: var(--muted); }

.member-signout svg,
.theme-toggle svg { width: 16px; height: 16px; }

/* The toggle shows ONE of its two glyphs, and it shows the one you are
   about to get rather than the one you are in: a moon on the light theme,
   a sun on the dark one. Driven straight off the tone the document is
   drawn in — the same attribute that picked the palette — so there is no
   second piece of state for the icon to disagree with, which is exactly
   what the old toggle's state class on <html> was and why it is not back
   (TCKKU9FF9E). The label follows in `initTheme` (site.js), in the
   selected language. */
.theme-toggle .icon-sun { display: none; }
.theme-toggle .icon-moon { display: block; }

[data-tone="dark"] .theme-toggle .icon-sun { display: block; }
[data-tone="dark"] .theme-toggle .icon-moon { display: none; }

/* "Your workspace is being reviewed", above everything. A note, not an
   error: the person did nothing wrong and there is nothing to fix — so
   `--muted`, not `--destructive`. */
.tenant-banner {
  padding: 10px 24px;
  border-bottom: 1px solid var(--border);
  background: var(--muted);
  color: var(--foreground);
  font-size: 13.5px;
  line-height: 1.5;
  text-align: center;
}

.members-main { max-width: 760px; }

.members-crumb { margin: 0 0 24px; font-size: 13px; }

.members-crumb a { color: var(--muted-foreground); text-decoration: none; }

.members-crumb a:hover { color: var(--foreground); text-decoration: underline; }

.members-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 16px;
  margin-top: 32px;
}

.members-card {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 20px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--card);
  color: var(--card-foreground);
  text-decoration: none;
}

.members-card:hover { border-color: var(--ring); }

.members-card h2 { margin: 0; font-size: 16px; font-weight: 600; }

.members-card p {
  margin: 0;
  font-size: 13.5px;
  line-height: 1.6;
  color: var(--muted-foreground);
}

.members-card-go {
  margin-top: auto;
  padding-top: 8px;
  font-size: 13px;
  font-weight: 500;
}

.members-empty { color: var(--muted-foreground); font-size: 14px; }

/* Deployment info (TCK96HRN2P). A NOTE, not an alarm: it is `--muted`
   like the "workspace is being reviewed" banner and never
   `--destructive` — nothing is broken and there is nothing to fix. The
   left rule is what separates it from the prose around it without
   spending a colour on it. */
.deploy-notice {
  margin-top: 32px;
  padding: 20px 24px;
  border: 1px solid var(--border);
  border-left: 3px solid var(--muted-foreground);
  border-radius: var(--radius);
  background: var(--muted);
  font-size: 15px;
  line-height: 1.7;
}

.deploy-notice p { margin: 0 0 12px; }

.deploy-notice p:last-child { margin-bottom: 0; }

/* `.doc-main h2:first-of-type` zeroes the top margin of the first heading
   on a page — right for a document that opens with one, wrong here, where
   it follows the notice box. Selected through `.doc-main` so it outranks
   that rule rather than fighting it with !important. */
.doc-main h2.deploy-latest-title {
  margin: 44px 0 4px;
  padding-top: 0;
  border-top: 0;
  font-size: 20px;
  font-weight: 600;
  letter-spacing: -0.01em;
}

/* The version and date of the section below it — empty until
   /members.js has the answer, so it must not reserve a line. */
.deploy-release {
  margin: 0 0 20px;
  color: var(--muted-foreground);
  font-size: 13px;
}

.deploy-release:empty { display: none; }

/* ------------------------------------------- passkeys (TCKPXEP9EU) */

/* The website's own passkeys: the sign-in affordance on /members/login/
   and the list on /members/passkeys/.

   Both are drawn only when the browser can actually run a ceremony —
   /members.js unhides them — so nothing here reserves space on a page
   where the feature cannot work. */

/* The alternative way in, under the password form. The rule and the word
   are what say "instead of the above" without a heading. */
.passkey-block {
  margin-top: 28px;
  padding-top: 4px;
}

.passkey-or {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 0 0 16px;
  color: var(--muted-foreground);
  font-size: 13px;
}

.passkey-or::before,
.passkey-or::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--border);
}

.passkey-btn { width: 100%; }

.passkey-block .field-hint { margin-top: 10px; }

/* The explanation on /members/passkeys/ — "a passkey belongs to one web
   address". A NOTE, not an alarm, so it borrows the deployment notice's
   metrics rather than a colour of its own. */
.passkey-note {
  margin: 20px 0 0;
  padding: 16px 20px;
  border: 1px solid var(--border);
  border-left: 3px solid var(--muted-foreground);
  border-radius: var(--radius);
  background: var(--muted);
  font-size: 14px;
  line-height: 1.7;
}

.passkey-list { margin-top: 24px; }

.passkey-row {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 16px 20px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--card);
  color: var(--card-foreground);
}

.passkey-row + .passkey-row { margin-top: 12px; }

.passkey-text { min-width: 0; flex: 1; }

.passkey-name {
  margin: 0;
  font-size: 15px;
  font-weight: 500;
  overflow-wrap: anywhere;
}

.passkey-meta {
  margin: 4px 0 0;
  color: var(--muted-foreground);
  font-size: 13px;
}

.passkey-remove { flex: none; }

.passkey-empty { margin: 0; color: var(--muted-foreground); font-size: 14px; }

/* One to three rows, a name and a button: below the narrow breakpoint the
   button goes under the name rather than squeezing it to two characters. */
@media (max-width: 520px) {
  .passkey-row { flex-direction: column; align-items: stretch; gap: 12px; }
  .passkey-remove { width: 100%; }
}

.deploy-all { margin-top: 32px; font-size: 14px; }

/* Videos: unlisted YouTube in a 16:9 box (decision O4). `aspect-ratio`
   rather than the old padding trick — every browser this site targets has
   it, and it survives a container that is not the page width. */
.members-videos {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: 24px;
  margin-top: 32px;
}

.video-embed {
  aspect-ratio: 16 / 9;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--muted);
}

.video-embed iframe { display: block; width: 100%; height: 100%; border: 0; }

/* Documentation: a list, then one document. */
.doc-list { display: flex; flex-direction: column; margin-top: 24px; }

.doc-item {
  padding: 12px 0;
  border-bottom: 1px solid var(--border);
  font-size: 15px;
  text-decoration: none;
}

.doc-item:hover { text-decoration: underline; text-underline-offset: 3px; }

.doc-back-btn {
  margin-bottom: 24px;
  padding: 0;
  border: 0;
  background: none;
  color: var(--muted-foreground);
  font-family: inherit;
  font-size: 13px;
  cursor: pointer;
}

.doc-back-btn:hover { color: var(--foreground); }

/* Rendered markdown — the documentation and the full release notes. The
   `.doc-main` rules above already style a changelog page's markup; this
   repeats them for markup that lands INSIDE a container instead of being
   the page. */
.doc-body { font-size: 15px; line-height: 1.7; }

.doc-body h2 {
  margin: 40px 0 12px;
  padding-top: 24px;
  border-top: 1px solid var(--border);
  font-size: 20px;
  font-weight: 600;
  letter-spacing: -0.01em;
}

.doc-body h2:first-child { margin-top: 0; padding-top: 0; border-top: 0; }

.doc-body h3 { margin: 28px 0 8px; font-size: 16px; font-weight: 600; }

.doc-body h4, .doc-body h5, .doc-body h6 {
  margin: 20px 0 8px;
  font-size: 15px;
  font-weight: 600;
}

.doc-body p { margin: 0 0 16px; }

.doc-body ul, .doc-body ol { margin: 0 0 16px; padding-left: 22px; }

.doc-body li { margin: 0 0 6px; }

.doc-body code {
  font-family: "Geist Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.875em;
  padding: 0.1em 0.35em;
  border-radius: calc(var(--radius) * 0.4);
  background: var(--muted);
}

.doc-body pre {
  margin: 0 0 16px;
  padding: 14px 16px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--muted);
  overflow-x: auto;
}

.doc-body pre code { padding: 0; background: none; }

.doc-body blockquote {
  margin: 0 0 16px;
  padding: 2px 0 2px 16px;
  border-left: 2px solid var(--border);
  color: var(--muted-foreground);
}

.doc-body hr { margin: 32px 0; border: 0; border-top: 1px solid var(--border); }

/* A textarea in a `.field`. The contact card styles its own; the members
   report form uses the shared field, so the metrics live here — same
   border, same radius, same error state as an input, because a control
   that looks like a different design system is how a form stops reading
   as one form. */
.field textarea {
  display: block;
  width: 100%;
  min-height: 140px;
  padding: 10px 12px;
  border: 1px solid var(--input);
  border-radius: calc(var(--radius) * 0.8);
  background: var(--background);
  color: var(--foreground);
  font-family: inherit;
  font-size: 14px;
  line-height: 1.6;
  resize: vertical;
}

.field textarea:focus-visible { outline: 2px solid var(--ring); outline-offset: 2px; }

.field textarea[aria-invalid="true"] { border-color: var(--destructive); }

/* Radio groups: the signup's account type, the report's kind. A
   `fieldset` so the question is a `legend` and a screen reader reads it
   with each option. */
.field.choice { border: 0; margin: 0 0 18px; padding: 0; }

.field.choice legend {
  padding: 0;
  margin-bottom: 8px;
  font-size: 14px;
  font-weight: 500;
}

.choice-option {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin-right: 20px;
  font-size: 14px;
  cursor: pointer;
}

/* `.field input` above is written for a text box: block, full width, 36px
   tall, with a border and padding. A radio inherits all of it and becomes a
   full-width bar with its label stranded on the line above — the same class
   of silent bug as `[hidden]` losing to `display`. So the radio takes its
   own metrics back, explicitly. */
.choice-option input[type="radio"] {
  display: inline-block;
  width: auto;
  height: auto;
  margin: 0;
  padding: 0;
  border: 0;
  accent-color: var(--primary);
}

/* "Already have an account?" under a form. */
.form-alt {
  margin: 24px 0 0;
  font-size: 14px;
  color: var(--muted-foreground);
}

.form-alt a { color: var(--foreground); text-underline-offset: 3px; }

/* The trimmed public changelog's call to action (decision O1): the page
   stays at /changelog/ and gains a line pointing at the full one. */
.changelog-cta {
  margin: 40px 0 0;
  padding: 20px 24px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--card);
  color: var(--card-foreground);
  font-size: 14px;
  line-height: 1.6;
}

.changelog-cta p { margin: 0 0 12px; }

.changelog-cta p:last-child { margin: 0; }

/* ------------------------------------------------------------------ */
/* The mobile app page (TCKEC5UWXX). Two cards, one per platform,
   because the two platforms hand out a build in two entirely different
   ways: iOS through Apple (a form, so we can add the person to
   TestFlight) and Android through us (a link to an APK on our own
   root). Same card metrics as `.members-card` so the members area still
   reads as one place; a `section` rather than an `a`, because the whole
   card is not one target here. */
.platform-card {
  margin: 32px 0;
  padding: 24px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--card);
  color: var(--card-foreground);
}

.platform-card h2 {
  margin: 0 0 8px;
  padding: 0;
  border: 0;
  font-size: 18px;
  font-weight: 600;
}

.platform-card > p {
  margin: 0 0 20px;
  font-size: 14px;
  line-height: 1.7;
  color: var(--muted-foreground);
}

/* `.members-main` is 760px wide so the prose reads; a 760px-wide email
   box does not. The form keeps the form pages' own measure. */
.platform-card form { max-width: 480px; }

.platform-card .form-actions { margin-top: 8px; }

/* Where /members.js draws the download button, or the sentence saying
   there is no build on this installation yet. Reserves its own height so
   the card does not jump when the manifest arrives. */
.apk-box {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px 16px;
  min-height: 40px;
}

/* `.doc-main a` above sets the prose link colour, and it beats `.btn-primary`
   on specificity — so a BUTTON inside an article draws its label in the text
   colour, which on a dark button is invisible. The changelog pages get away
   with it because their call to action is a plain link; the download button is
   not, so the palette is stated again here. */
.doc-main a.btn-primary { color: var(--primary-foreground); }

.doc-main a.btn-secondary { color: var(--secondary-foreground); }

.apk-meta {
  margin: 0;
  font-size: 13px;
  color: var(--muted-foreground);
  font-variant-numeric: tabular-nums;
}

.apk-hint {
  margin: 20px 0 0;
  font-size: 13px;
  line-height: 1.6;
  color: var(--muted-foreground);
}
