/* Trichroma Lab — marketing site.
   Palette and type come from ../../docs/brand.md. The light column is the one the brand
   reserves for the web frontend; the app itself ships dark only.
   Every value here must match the brand or the build lies. Three tokens are absent from
   brand.md's palette tables and are sourced from TrichromaColours instead — see the note
   beside them below. */

:root {
  /* The darkroom column of docs/brand.md. The site is dark only, for the same reason
     the app is: it is a darkroom, and a second theme is a maintenance cost with no
     user. The light column in the brand spec is currently unused. */
  --paper:        #171511;
  --paper-raised: #201D17;
  --paper-inset:  #221F19;
  --ink:          #ECE6D8;
  --ink-soft:     #A89D8A;
  --line:         #332F27;

  --ch-r: #D46B58;   /* 665 nm */
  --ch-g: #82BEAF;   /* 525 nm */
  --ch-b: #6F98BA;   /* 450 nm */
  --chassis: #7C766B;

  --selection: #D9A441;
  --alarm:     #E25C46;

  /* correction axes — the positive halves; the negatives are the three channels */
  --axis-cyan:    #5FB2BA;
  --axis-magenta: #C4718F;
  --axis-yellow:  #D4AE52;
  /* These three are named by brand.md but never given a value in its tables, so the shipping
     app is the source: desktop/packages/trichroma_design/lib/src/trichroma_colours.dart. */
  --axis-neutral: #4A453C;      /* = TrichromaColours.axisNeutral, exactly */
  /* The step tablet on the D axis. ⭐ These are not decoration and not site-local: the forum
     Worker renders the SAME axisRow() into posts, so a correction has to look identical in a
     thread and in the app. They drifted (#3A3730 / #CFC7B6) until 2026-08-31; corrected to what
     ships, which desktop/docs/ui-console.html also carries as WEDGE_LO / WEDGE_HI. */
  --axis-wedge-lo: #3A3733;     /* = TrichromaColours.wedgeDark */
  --axis-wedge-hi: #EFEAE0;     /* = TrichromaColours.wedgeLight */

  /* The one deliberately non-brand grey: the ground a photograph is judged against. Warmed by
     exactly R−B = 4, because a measured neutral looks blue beside this warm brown. The marketing
     site never needed it; the forum does, the moment somebody posts a frame and asks about its
     colour. = TrichromaColours.imageWell / imageWellGrid. */
  --image-well: #262422;
  --image-well-grid: #302D29;

  --font-display: "Helvetica Neue", Helvetica, Arial, sans-serif;
  --font-body: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --font-mono: "SF Mono", ui-monospace, Menlo, Consolas, monospace;

  --shadow: 0 1px 0 rgba(0,0,0,.3), 0 22px 46px -30px rgba(0,0,0,.8);
  --dot: rgba(230,220,190,.045);
  --blend: screen;
  --focus-ring: var(--selection);   /* 8.1:1 on this ground */

  --maxw: 1180px;
  --maxw-text: 68ch;
  --gutter: clamp(20px, 5vw, 48px);
  --section: clamp(56px, 9vw, 116px);

  color-scheme: dark;
}

/* ── base ─────────────────────────────────────────────────────────── */

*, *::before, *::after { box-sizing: border-box; }

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

body {
  margin: 0;
  background-color: var(--paper);
  background-image: radial-gradient(var(--dot) 1px, transparent 1px);
  background-size: 4px 4px;
  color: var(--ink);
  font-family: var(--font-body);
  font-size: 16.5px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

img, video { max-width: 100%; height: auto; display: block; }

a { color: inherit; text-decoration-color: var(--line); text-underline-offset: 3px; }
a:hover { text-decoration-color: currentColor; }

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

.skip {
  position: absolute; left: -9999px; top: 0; z-index: 100;
  background: var(--ink); color: var(--paper);
  padding: 10px 16px; font-family: var(--font-mono); font-size: 12px;
}
.skip:focus { left: 8px; top: 8px; }

.wrap { max-width: var(--maxw); margin: 0 auto; padding-inline: var(--gutter); }

section { padding-block: var(--section); border-top: 1px solid var(--line); }
section:first-of-type { border-top: 0; }

/* ── type ─────────────────────────────────────────────────────────── */

.eyebrow {
  font-family: var(--font-mono);
  font-size: 12px; letter-spacing: .18em; text-transform: uppercase;
  color: var(--ink-soft); margin: 0 0 20px;
}

h1, h2, h3 { font-family: var(--font-display); text-wrap: balance; margin: 0; }

h1 {
  font-weight: 800; text-transform: uppercase;
  font-size: clamp(34px, 5.1vw, 66px);
  line-height: .96; letter-spacing: -.032em;
}

h2 {
  font-weight: 700; letter-spacing: -.02em;
  font-size: clamp(24px, 3.4vw, 34px);
  line-height: 1.12;
}

h3 {
  font-weight: 700; letter-spacing: -.015em;
  font-size: clamp(18px, 2.1vw, 21px);
  line-height: 1.2;
}

.lede {
  font-family: var(--font-display);
  font-weight: 600; font-size: clamp(18px, 1.75vw, 23px);
  letter-spacing: -.01em; line-height: 1.34;
  max-width: 38ch; margin: 24px 0 0;
  text-wrap: pretty;
}

.sub { color: var(--ink-soft); max-width: 58ch; margin: 18px 0 0; text-wrap: pretty; }
.sub b, .sub strong { color: var(--ink); font-weight: 600; }

.note { color: var(--ink-soft); max-width: var(--maxw-text); font-size: 15px; }

.kicker {
  font-family: var(--font-mono);
  font-size: 11px; letter-spacing: .14em; text-transform: uppercase;
  color: var(--ink-soft); margin: 0 0 12px;
}

.num { font-family: var(--font-display); font-variant-numeric: tabular-nums; }

.sec-head { max-width: var(--maxw-text); margin-bottom: clamp(28px, 4vw, 48px); }
.sec-head h2 + .sub { margin-top: 14px; }

/* ── the correction axis ──────────────────────────────────────────────
   The widget the app is built around (desktop/docs/ui-design.md → The correction
   control). Chromatic axes get a gradient running negative → neutral → positive;
   the achromatic one gets a step tablet, because a density is a different kind of
   quantity from a hue shift. The track IS the identity — it reads with the numbers
   switched off, which is why the caption under a photograph can be this and nothing
   else. */

.axis-row {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 4px 18px;
  font-family: var(--font-display);
  font-variant-numeric: tabular-nums;
}

.axis { display: grid; grid-template-columns: 1ch 1fr 2.5ch; align-items: center; gap: 7px; }

.axis-letter { font-size: 12px; font-weight: 700; line-height: 1; }
.axis[data-axis="D"] .axis-letter { color: var(--ink); }
.axis[data-axis="C"] .axis-letter { color: var(--axis-cyan); }
.axis[data-axis="M"] .axis-letter { color: var(--axis-magenta); }
.axis[data-axis="Y"] .axis-letter { color: var(--axis-yellow); }

.axis-track {
  position: relative; height: 7px; border-radius: 1px;
  border: 1px solid color-mix(in srgb, var(--line), transparent 35%);
}

.axis[data-axis="C"] .axis-track {
  background-image: linear-gradient(90deg, var(--ch-r), var(--axis-neutral) 50%, var(--axis-cyan));
}
.axis[data-axis="M"] .axis-track {
  background-image: linear-gradient(90deg, var(--ch-g), var(--axis-neutral) 50%, var(--axis-magenta));
}
.axis[data-axis="Y"] .axis-track {
  background-image: linear-gradient(90deg, var(--ch-b), var(--axis-neutral) 50%, var(--axis-yellow));
}

/* the step tablet — hard edges, because the band boundaries are the ruler ticks */
.axis[data-axis="D"] .axis-track {
  background-image: linear-gradient(90deg,
    color-mix(in srgb, var(--axis-wedge-lo), var(--axis-wedge-hi)  0%) 0 12.5%,
    color-mix(in srgb, var(--axis-wedge-lo), var(--axis-wedge-hi) 14%) 12.5% 25%,
    color-mix(in srgb, var(--axis-wedge-lo), var(--axis-wedge-hi) 28%) 25% 37.5%,
    color-mix(in srgb, var(--axis-wedge-lo), var(--axis-wedge-hi) 42%) 37.5% 50%,
    color-mix(in srgb, var(--axis-wedge-lo), var(--axis-wedge-hi) 57%) 50% 62.5%,
    color-mix(in srgb, var(--axis-wedge-lo), var(--axis-wedge-hi) 71%) 62.5% 75%,
    color-mix(in srgb, var(--axis-wedge-lo), var(--axis-wedge-hi) 85%) 75% 87.5%,
    color-mix(in srgb, var(--axis-wedge-lo), var(--axis-wedge-hi) 100%) 87.5% 100%);
}

/* One tick per step, so granularity is visible rather than guessed — which means the count is
   NOT free: the colour axes run -12 .. +12, so the track spans 24 steps. It said 18 while
   corrections.ts still carried the pre-2026-08-26 limit of nine, and 18 ticks on a +/-12 track
   is the one thing this rule promises not to do. Keep it at 2 x colourLimit. */
.axis[data-axis="C"] .axis-track::before,
.axis[data-axis="M"] .axis-track::before,
.axis[data-axis="Y"] .axis-track::before {
  content: ""; position: absolute; inset: 0; border-radius: inherit;
  background-image: repeating-linear-gradient(90deg,
    transparent 0 calc(100% / 24 - 1px),
    color-mix(in srgb, var(--ink), transparent 82%) calc(100% / 24 - 1px) calc(100% / 24));
}

.axis-knob {
  position: absolute; top: 50%; left: var(--pos, 50%);
  width: 3px; height: 13px; border-radius: 1px;
  transform: translate(-50%, -50%);
  background: var(--paper-inset);
  box-shadow: 0 0 0 1px var(--ink);
}

.axis-value { font-size: 12px; font-weight: 600; text-align: right; line-height: 1; }

/* An untouched row is quiet — the app dims a zero row, and the whole argument of
   this page is how many rows are quiet. */
.axis-row[data-quiet="true"] { opacity: .42; }

.axis-caption {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 8px 14px;
  margin-top: 12px; font-family: var(--font-mono);
  font-size: 11px; letter-spacing: .14em; text-transform: uppercase;
  color: var(--ink-soft);
}
/* Top-level, not nested under .axis-caption: the forum marks a rig's state with the same
   token (`reported` -> `measured` -> `calibrated`), and one marker should be one rule. It
   carries its own type because outside a caption there is no mono context to inherit — inside
   one the declarations are what .axis-caption was already giving it, so nothing moves. */
.tag {
  display: inline-block;
  font-family: var(--font-mono);
  font-size: 11px; text-transform: uppercase; letter-spacing: .1em;
  color: var(--ink-soft);
  border: 1px solid var(--line); border-radius: 2px; padding: 2px 6px;
}
.tag--auto { color: var(--ch-g); border-color: color-mix(in srgb, var(--ch-g), transparent 45%); }
.tag--touched { color: var(--selection); border-color: color-mix(in srgb, var(--selection), transparent 45%); }

/* ── figures and window chrome ────────────────────────────────────── */

.shot {
  position: relative; border: 1px solid var(--line); border-radius: 10px;
  overflow: hidden; background: var(--paper-inset); box-shadow: var(--shadow);
}
.shot img { width: 100%; }

/* A screenshot of this app already carries its own window and its own traffic
   lights, so the page must not draw a second set around it. */

/* The filmstrip's captions ARE the argument, so it may not be shrunk until they
   stop being readable. Below the width where that happens it scrolls in its own
   box rather than the page scrolling sideways. */
.scroller { overflow-x: auto; overscroll-behavior-x: contain; }
.scroller::-webkit-scrollbar { height: 6px; }
.scroller::-webkit-scrollbar-thumb { background: var(--line); border-radius: 3px; }

@media (max-width: 900px) {
  .scroller > img { min-width: 860px; }
}



figure { margin: 0; }
figcaption { margin-top: 14px; color: var(--ink-soft); font-size: 14.5px; max-width: 62ch; }

/* ── buttons ──────────────────────────────────────────────────────── */

.cta-row { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 30px; align-items: center; }

.btn {
  display: inline-flex; align-items: center; gap: 8px;
  font-family: var(--font-mono); font-size: 12px; letter-spacing: .12em;
  text-transform: uppercase; text-decoration: none;
  padding: 13px 20px; border-radius: 4px; border: 1px solid var(--ink);
  background: var(--ink); color: var(--paper);
  transition: background-color .18s ease, color .18s ease, transform .12s ease;
  touch-action: manipulation; cursor: pointer;
}
.btn:hover { background: color-mix(in srgb, var(--ink), var(--paper) 14%); }
.btn:active { transform: scale(.985); }

.btn--ghost { background: transparent; color: var(--ink); border-color: var(--line); }
.btn--ghost:hover { background: var(--paper-raised); border-color: var(--ink-soft); }

.cta-note {
  font-family: var(--font-mono); font-size: 11.5px; letter-spacing: .1em;
  text-transform: uppercase; color: var(--ink-soft); margin: 18px 0 0;
}

/* ── the hero frame ───────────────────────────────────────────────────
   ⛔ This was a live composite: three channel separations, tinted to the
   additive primaries and screened back together, so the page rebuilt the
   photograph the way the app does. It rendered exactly right in an
   unmanaged context and wrong on a wide-gamut display — a profiled image is
   colour-managed into the display space while a CSS `#f00` behind it is not,
   so multiply/screen no longer reconstructs the original and the picture
   drifts magenta.
   The whole argument of this page is that the colour is correct. A hero whose
   colour depends on the viewer's display profile contradicts that on the
   first image, so the photograph is served as a photograph. The three bands
   are still named underneath, in words. */

.reg {
  display: block; width: 100%; height: auto;
  border-radius: 10px; border: 1px solid var(--line);
  box-shadow: var(--shadow);
}

.reg-legend {
  display: flex; gap: 16px; flex-wrap: wrap; margin-top: 14px;
  font-family: var(--font-mono); font-size: 11px; letter-spacing: .14em;
  text-transform: uppercase; color: var(--ink-soft);
}
.reg-legend span { display: inline-flex; align-items: center; gap: 6px; }
.reg-legend i { width: 8px; height: 16px; border-radius: 1px; }
.reg-legend i.r { background: var(--ch-r); }
.reg-legend i.g { background: var(--ch-g); }
.reg-legend i.b { background: var(--ch-b); }

/* ── the wordmark, out of register ────────────────────────────────────
   Lifted from brand/brand-sheet.html, values and all. The readable word sits
   on top in ink; a red and a blue ghost sit behind it, blended, and they
   arrive from far out of register. Only two ghosts: green would land on top of
   the ink and read as mud, which is why the sheet never had one. */

.wordmark {
  position: relative; display: inline-block;
}
.wordmark .base { position: relative; z-index: 2; color: var(--ink); }
.wordmark .ch {
  position: absolute; inset: 0; z-index: 1;
  mix-blend-mode: var(--blend); will-change: transform;
}
.wordmark .ch.r { color: var(--ch-r); transform: translate(var(--reg-rest, 2px), 0); }
.wordmark .ch.b { color: var(--ch-b); transform: translate(calc(var(--reg-rest, 2px) * -1), 0); }

.js .wordmark.is-registered .ch.r { animation: regR 1s cubic-bezier(.2, .7, .2, 1) both; }
.js .wordmark.is-registered .ch.b { animation: regB 1s cubic-bezier(.2, .7, .2, 1) both; }

@keyframes regR {
  from { transform: translate(var(--reg-from-x, 12px), calc(var(--reg-from-y, 4px) * -1)); }
  to   { transform: translate(var(--reg-rest, 2px), 0); }
}
@keyframes regB {
  from { transform: translate(calc(var(--reg-from-x, 12px) * -1), var(--reg-from-y, 4px)); }
  to   { transform: translate(calc(var(--reg-rest, 2px) * -1), 0); }
}

/* The ghosts land exactly behind the base and disappear under it: the split is
   the arrival, not a permanent state. (brand-sheet.html rests them at 2px and
   keeps the fringe — this page does not, by choice.) */
.wordmark--head { --reg-rest: 0px; --reg-from-x: 14px; --reg-from-y: 5px; }
.wordmark--sign { --reg-rest: 0px; --reg-from-x: 14px; --reg-from-y: 5px; }

.sign {
  font-family: var(--font-display); font-weight: 800; text-transform: uppercase;
  letter-spacing: -.035em; line-height: .9;
  font-size: clamp(40px, 11vw, 132px);
  margin: 0 0 8px;
}

.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0;
}

/* the mark — three even bars, never recoloured, never tapered */
.glyph { display: inline-flex; gap: 2px; vertical-align: -2px; }
.glyph i { width: 6px; height: 18px; border-radius: 1px; }
.glyph i:nth-child(1) { background: var(--ch-r); }
.glyph i:nth-child(2) { background: var(--ch-g); }
.glyph i:nth-child(3) { background: var(--ch-b); }

.lockup {
  display: inline-flex; align-items: center; gap: 12px; text-decoration: none;
  font-family: var(--font-display); font-weight: 800; text-transform: uppercase;
  letter-spacing: -.02em; font-size: 17px;
}

/* ── the comparison ───────────────────────────────────────────────── */

.compare { display: grid; grid-template-columns: repeat(2, 1fr); gap: clamp(14px, 2.4vw, 26px); }
.compare-item { display: flex; flex-direction: column; gap: 12px; }

.compare-label {
  font-family: var(--font-mono); font-size: 11px; letter-spacing: .14em;
  text-transform: uppercase; color: var(--ink-soft); margin: 0;
}
.compare-label b { color: var(--ink); font-weight: 400; }

.compare[data-labels="off"] .compare-reveal { visibility: hidden; }

.compare-controls {
  display: flex; flex-wrap: wrap; align-items: center; gap: 14px;
  margin-top: clamp(20px, 3vw, 30px);
}

.toggle {
  font-family: var(--font-mono); font-size: 11px; letter-spacing: .12em;
  text-transform: uppercase; cursor: pointer;
  background: transparent; color: var(--ink);
  border: 1px solid var(--line); border-radius: 3px; padding: 9px 14px;
  transition: border-color .18s ease, background-color .18s ease;
  touch-action: manipulation;
}
.toggle:hover { border-color: var(--ink-soft); background: var(--paper-raised); }
.toggle[aria-pressed="true"] { border-color: var(--selection); color: var(--selection); }

/* ── grids, lists, faq ────────────────────────────────────────────── */

.cols { display: grid; gap: clamp(24px, 4vw, 52px); }
.cols--2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.cols--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.cols--split { grid-template-columns: minmax(0, 1fr) minmax(0, 1.08fr); align-items: center; }
/* The hero leads with the sentence, so the type column takes the larger share. */
.cols--hero { grid-template-columns: minmax(0, 1.06fr) minmax(0, 1fr); align-items: center; }

.feature { border-top: 1px solid var(--line); padding-top: 16px; }
.feature h3 { margin-bottom: 8px; }
.feature p { color: var(--ink-soft); margin: 0; font-size: 15px; }

/* The chosen one, in the ochre's own idiom: a rule and a wash, never a fill. */
.feature--chosen { border-top-color: var(--selection); border-top-width: 2px; }
.feature--chosen h3 { color: var(--selection); }

.price {
  font-family: var(--font-mono); font-size: 26px; color: var(--ink) !important;
  font-variant-numeric: tabular-nums; margin: 0 0 10px !important;
}
.price span {
  font-size: 12px; color: var(--ink-soft); letter-spacing: .12em;
  text-transform: uppercase; margin-left: 8px;
}

.specs { list-style: none; margin: 0; padding: 0; }
.specs li {
  display: flex; justify-content: space-between; gap: 20px;
  padding: 11px 0; border-bottom: 1px solid var(--line);
  font-family: var(--font-mono); font-size: 12px; letter-spacing: .06em;
}
.specs dt, .specs .k { color: var(--ink-soft); text-transform: uppercase; letter-spacing: .12em; font-size: 11px; }
.specs .v { font-variant-numeric: tabular-nums; text-align: right; }

.faq { margin: 0; }
.faq dt {
  font-family: var(--font-display); font-weight: 700; letter-spacing: -.01em;
  font-size: 17px; padding-top: 22px;
}
.faq dd {
  margin: 8px 0 22px; color: var(--ink-soft);
  border-bottom: 1px solid var(--line); padding-bottom: 22px; max-width: var(--maxw-text);
}
.faq dd:last-of-type { border-bottom: 0; }

.callout {
  border: 1px solid var(--line); border-left: 3px solid var(--selection);
  border-radius: 3px; padding: clamp(20px, 3vw, 30px);
  background: var(--paper-inset); max-width: 68ch;
}
.callout h3 { margin-bottom: 10px; }
.callout p { color: var(--ink-soft); margin: 0 0 12px; }
.callout p:last-child { margin-bottom: 0; }

/* ── nav and footer ───────────────────────────────────────────────── */

.nav {
  position: sticky; top: 0; z-index: 20;
  display: flex; align-items: center; justify-content: space-between; gap: 20px;
  padding-block: 14px;
  background: color-mix(in srgb, var(--paper), transparent 12%);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--line);
}
.nav-links { display: flex; gap: 22px; align-items: center; }
.nav-links a {
  font-family: var(--font-mono); font-size: 11.5px; letter-spacing: .12em;
  text-transform: uppercase; color: var(--ink-soft); text-decoration: none;
}
.nav-links a:hover { color: var(--ink); }

footer { border-top: 1px solid var(--line); padding-block: clamp(40px, 6vw, 64px); }
.disc {
  font-family: var(--font-mono); font-size: 11.5px; line-height: 1.75;
  color: var(--ink-soft); max-width: 74ch; margin: 26px 0 0;
}

/* ── motion ───────────────────────────────────────────────────────── */

.js .reveal { opacity: 0; transform: translateY(12px); }
.js .reveal.is-in {
  opacity: 1; transform: none;
  transition: opacity .6s cubic-bezier(.16, 1, .3, 1), transform .6s cubic-bezier(.16, 1, .3, 1);
}

@media (prefers-reduced-motion: reduce) {
  .js .reveal, .js .reveal.is-in { opacity: 1; transform: none; transition: none; }
  .js .reg-plate, .js .reg.is-registered .reg-plate { transform: none; transition: none; }
  .js .wordmark .ch { animation: none !important; }
  * { animation-duration: .001ms !important; transition-duration: .001ms !important; }
}

/* ── responsive ───────────────────────────────────────────────────── */

@media (max-width: 860px) {
  .cols--2, .cols--3, .cols--split, .cols--hero { grid-template-columns: 1fr; }
  .nav-links a:not(.btn) { display: none; }
}

@media (max-width: 620px) {
  .axis-row { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .specs li { flex-direction: column; gap: 4px; }
  .specs .v { text-align: left; }
}

/* ── legal pages ──────────────────────────────────────────────────────
   Privacy and terms. Long, dense, and read by people looking for one specific answer — so the
   type is set for scanning: a hard 68ch measure, headings that survive being skimmed, and
   definition lists where the content is really a table of "what / why / how long". */

.legal { max-width: var(--maxw-text); }
.legal h2 {
  font-size: clamp(19px, 2.2vw, 23px); margin: 2.4em 0 .6em;
  padding-top: 1.2em; border-top: 1px solid var(--line);
}
.legal h2:first-of-type { border-top: 0; padding-top: 0; margin-top: 1.4em; }
.legal h3 { font-size: 16px; margin: 1.8em 0 .5em; }
.legal p { color: var(--ink-soft); margin: 0 0 1em; text-wrap: pretty; }
.legal p b, .legal p strong, .legal li b, .legal li strong { color: var(--ink); font-weight: 600; }
.legal ul, .legal ol { color: var(--ink-soft); padding-left: 1.3em; margin: 0 0 1.2em; }
.legal li { margin-bottom: .5em; }
.legal a { color: var(--ink); }
.legal code {
  font-family: var(--font-mono); font-size: .88em; color: var(--ink);
  background: var(--paper-inset); border: 1px solid var(--line);
  border-radius: 3px; padding: 1px 5px;
}
.legal dl { margin: 0 0 1.4em; }
.legal dt {
  font-family: var(--font-mono); font-size: 11px; letter-spacing: .12em;
  text-transform: uppercase; color: var(--ink); margin-top: 1.2em;
}
.legal dd { margin: .45em 0 0; padding: 0 0 1em; border-bottom: 1px solid var(--line); color: var(--ink-soft); }
.legal .updated {
  font-family: var(--font-mono); font-size: 11px; letter-spacing: .14em;
  text-transform: uppercase; color: var(--ink-soft);
}

/* ── the contact form ─────────────────────────────────────────────────
   The site has no address written on it any more: `mailto:` in ten places was ten gifts to a
   scraper. Everything now goes through the form, which means the form has to work. */

/* ⛔ The honeypot, hidden the one way that survives: off-screen rather than `display:none`, which
   the better bots have known to skip for years. */
.hp {
  position: absolute; left: -9999px; top: auto;
  width: 1px; height: 1px; overflow: hidden;
}

.notice {
  border: 1px solid var(--line); border-left-width: 2px;
  background: var(--paper-raised); padding: 14px 16px; border-radius: 3px;
  margin: 0 0 26px; font-size: 15px; color: var(--ink-soft); max-width: 58ch;
}
.notice b { color: var(--ink); font-weight: 600; }
.notice--good { border-left-color: var(--ch-g); }
.notice--bad { border-left-color: var(--alarm); }

.field { display: block; margin-top: 22px; }
.field > .label {
  display: block; margin-bottom: 7px;
  font-family: var(--font-mono); font-size: 11px; letter-spacing: .14em;
  text-transform: uppercase; color: var(--ink-soft);
}
.field input[type="text"], .field input[type="email"], .field textarea {
  width: 100%; display: block; background: var(--paper-inset); color: var(--ink);
  border: 1px solid var(--line); border-radius: 4px; padding: 11px 12px;
  font-family: var(--font-body); font-size: 16px; /* ⚠️ 16px, or iOS zooms on focus */
  line-height: 1.5;
}
.field textarea { resize: vertical; min-height: 180px; }
.field input:focus-visible, .field textarea:focus-visible {
  outline: 2px solid var(--focus-ring); outline-offset: 2px; border-color: transparent;
}
.form-actions { margin-top: 24px; display: flex; gap: 16px; align-items: center; flex-wrap: wrap; }
button.btn { font-family: var(--font-mono); font-size: 12px; letter-spacing: .12em; text-transform: uppercase; }

/* Turnstile draws its own box; this only keeps it in the form's rhythm. */
.cf-turnstile { margin-top: 22px; }

/* ── contact: one section, not two ────────────────────────────────────
   The form used to sit in its own <section>, which meant two full `--section` paddings back to
   back (~230px on a wide screen) plus a divider rule across an otherwise empty page. On a page
   whose entire content is six fields, that read as a mistake rather than as air. */
/* No margin of its own: `.sec-head` already carries a bottom margin, and adding to it was
   how the gap got too big in the first place. */
.contact-form { max-width: 52ch; }
.contact-form .field:first-of-type { margin-top: 0; }
.contact-aside { margin-top: 30px; }


/* The forum's own chrome. The palette, the type scale and every component the marketing site
   already has arrive from ../../frontend/src/style.css, which this file is served after — one
   source of truth, no copy, no sync script (verified 2026-08-31: wrangler bundles it byte-exact).
   What lives here is only what a forum needs and a landing page never did.

   The thesis, from docs/forum-plan.md → Wygląd: the forum is the app's right rail, not a notice
   board. Exactly two voices — display for words a human wrote, mono 11px uppercase --ink-soft for
   every word the forum itself says. Lines and hairlines, no cards, no pills, no avatars. */

/* ⛔ THE rule. Global h1 is uppercase (frontend/src/style.css), which is right for the site's own
   headlines and wrong for a title somebody typed. Capitals belong to the service's own words;
   anything a human wrote is a sentence. Without this a thread shouts in someone else's voice —
   and long titles break badly in caps. */
.topic-title, h1.topic-title, .prose h1, .prose h2, .prose h3 {
  text-transform: none;
}

/* ── shell ────────────────────────────────────────────────────────── */

.forum-nav {
  display: flex; align-items: center; justify-content: space-between; gap: 18px;
  padding-block: 20px; border-bottom: 1px solid var(--line);
}

/* ⚠️ style.css:498 hides `.nav-links a:not(.btn)` below 860px. On the marketing page that drops
   section anchors; here it would drop the navigation itself. So the top bar carries only the
   lockup and account state, and categories live in .subnav inside the existing .scroller. */
.forum-nav .who {
  font-family: var(--font-mono); font-size: 11px; letter-spacing: .12em;
  text-transform: uppercase; color: var(--ink-soft);
  display: flex; align-items: center; gap: 14px;
}

.forum-main { padding-block: clamp(28px, 5vw, 56px); }

/* ── a single centred column, for the pages that are a conversation with one person ── */

.narrow { max-width: 46ch; }
.narrow h1 { font-size: clamp(26px, 3.6vw, 34px); letter-spacing: -.02em; }
.narrow .sub { margin-top: 14px; }

/* ── forms ────────────────────────────────────────────────────────── */

.field { display: block; margin-top: 22px; }

.field > .label {
  display: block; margin-bottom: 7px;
  font-family: var(--font-mono); font-size: 11px; letter-spacing: .14em;
  text-transform: uppercase; color: var(--ink-soft);
}

.field input[type="email"],
.field input[type="text"],
.field textarea {
  width: 100%; display: block;
  background: var(--paper-inset); color: var(--ink);
  border: 1px solid var(--line); border-radius: 4px;
  padding: 11px 12px;
  font-family: var(--font-body); font-size: 16px; /* ⚠️ 16px or iOS Safari zooms on focus */
  line-height: 1.5;
}
.field textarea { font-family: var(--font-mono); font-size: 14px; min-height: 220px; resize: vertical; }
.field input:focus-visible, .field textarea:focus-visible {
  outline: 2px solid var(--focus-ring); outline-offset: 2px; border-color: transparent;
}

.hint { margin-top: 8px; color: var(--ink-soft); font-size: 14px; max-width: 52ch; }

.form-actions { margin-top: 24px; display: flex; align-items: center; gap: 16px; flex-wrap: wrap; }

/* A form button has to match the anchor .btn exactly — they sit next to each other. */
button.btn { font: inherit; font-family: var(--font-mono); font-size: 12px;
  letter-spacing: .12em; text-transform: uppercase; }

/* ── notices ──────────────────────────────────────────────────────── */

/* One box, three tones. Ochre is `chosen`, so a plain notice stays neutral and only a refusal
   reaches for --alarm; brand.md reserves that colour for "this cannot be undone", and a failed
   login is close enough to irreversible from where the reader sits. */
.notice {
  border: 1px solid var(--line); border-left-width: 2px;
  background: var(--paper-raised);
  padding: 14px 16px; border-radius: 3px; margin-top: 26px;
  font-size: 15px; color: var(--ink-soft); max-width: 58ch;
}
.notice b, .notice strong { color: var(--ink); font-weight: 600; }
.notice--bad { border-left-color: var(--alarm); }
.notice--good { border-left-color: var(--ch-g); }

.mono-note {
  font-family: var(--font-mono); font-size: 11px; letter-spacing: .12em;
  text-transform: uppercase; color: var(--ink-soft);
}

/* ── the board ────────────────────────────────────────────────────────
   Categories carry NO colour. In this palette every colour already means something — R/G/B are
   the three bands, C/M/Y the correction directions, ochre is `chosen`, --alarm is `irreversible`.
   Spending that vocabulary on "which room is this thread in" would be the worst trade available,
   so a category is a mono tag and the eye is left free for the words. */

.subnav {
  display: flex; gap: 20px; align-items: center;
  padding-block: 14px; border-bottom: 1px solid var(--line);
  font-family: var(--font-mono); font-size: 11px; letter-spacing: .14em;
  text-transform: uppercase; white-space: nowrap;
}
.subnav a { color: var(--ink-soft); text-decoration: none; }
.subnav a:hover, .subnav a[aria-current="page"] { color: var(--ink); }
.subnav a[aria-current="page"] { text-decoration: underline; text-underline-offset: 5px; }

.cats { display: grid; gap: 1px; margin-top: 8px; }
.cat {
  display: grid; grid-template-columns: 1fr auto; gap: 2px 18px; align-items: baseline;
  padding: 15px 12px; margin-inline: -12px; border-bottom: 1px solid var(--line);
  text-decoration: none;
}
.cat:hover { background: var(--paper-raised); }
/* ⚠️ Every child names its own column. An item with an explicit `grid-row` is auto-placed in a
   separate pass, so leaving these implicit let the count take column 1 and pushed the name to the
   right-hand edge. */
.cat-name {
  grid-column: 1; grid-row: 1;
  font-family: var(--font-display); font-weight: 700; font-size: 17px; letter-spacing: -.01em;
}
.cat-desc { grid-column: 1; grid-row: 2; color: var(--ink-soft); font-size: 14.5px; max-width: 62ch; }
.cat-count {
  grid-column: 2; grid-row: 1 / span 2; align-self: center; text-align: right;
  font-family: var(--font-display); font-variant-numeric: tabular-nums;
  color: var(--ink-soft); font-size: 13px;
}

/* ── a list of topics ─────────────────────────────────────────────────
   The reply count is a COLUMN of tabular figures running down the page, not a pill beside each
   title. Read vertically it is an instrument reading — which is exactly what `.specs` already
   does on the marketing site, and what a pill can never do. */

.topics { margin-top: 8px; border-top: 1px solid var(--line); }
.topic {
  display: grid; grid-template-columns: 1fr auto; gap: 3px 20px; align-items: baseline;
  padding: 15px 12px; margin-inline: -12px; border-bottom: 1px solid var(--line);
}
.topic:hover { background: var(--paper-raised); }
.topic-name {
  grid-column: 1; grid-row: 1;
  font-family: var(--font-display); font-weight: 600; font-size: 16.5px;
  letter-spacing: -.01em; text-transform: none; text-decoration: none; text-wrap: pretty;
}
.topic-meta {
  grid-column: 1; grid-row: 2; display: flex; flex-wrap: wrap; gap: 6px 14px; align-items: baseline;
  font-family: var(--font-mono); font-size: 11px; letter-spacing: .12em;
  text-transform: uppercase; color: var(--ink-soft);
}
.topic-meta a { color: inherit; text-decoration: none; }
.topic-meta a:hover { color: var(--ink); }
.topic-count {
  grid-column: 2; grid-row: 1 / span 2; align-self: center; text-align: right;
  font-family: var(--font-display); font-variant-numeric: tabular-nums;
  font-size: 15px; color: var(--ink-soft); min-width: 3ch;
}
.topic-count span {
  display: block; font-family: var(--font-mono); font-size: 9px;
  letter-spacing: .14em; opacity: .7;
}

/* Pinned and closed are said in words, in the forum's own voice, not with an icon nobody reads. */
.topic-flag { color: var(--selection); }

.pager {
  display: flex; gap: 18px; justify-content: space-between; align-items: center;
  margin-top: 28px; font-family: var(--font-mono); font-size: 11px;
  letter-spacing: .12em; text-transform: uppercase; color: var(--ink-soft);
}

/* ── one thread ───────────────────────────────────────────────────────
   Two columns above 860px: a hanging mono rail of metadata, and a 68ch column of prose with a
   hard left edge. The rail is `position: sticky` — that is the entire motion budget of this
   forum, two declarations and no JavaScript at all. */

.topic-head { padding-block: 8px 22px; border-bottom: 1px solid var(--line); }
.topic-head h1 {
  font-size: clamp(24px, 3.2vw, 33px); line-height: 1.1; letter-spacing: -.02em;
  font-weight: 700;
  /* ⛔ Repeated deliberately: global h1 is uppercase and this is somebody's sentence. */
  text-transform: none;
}
.topic-head .topic-meta { margin-top: 12px; }

.post {
  display: grid; grid-template-columns: 170px minmax(0, 68ch); gap: 30px;
  padding-block: 28px; border-bottom: 1px solid var(--line);
}
.post-rail {
  position: sticky; top: 20px; align-self: start;
  font-family: var(--font-mono); font-size: 11px; letter-spacing: .12em;
  text-transform: uppercase; color: var(--ink-soft);
  display: flex; flex-direction: column; gap: 6px;
}
.post-rail .who { color: var(--ink); }
/* A flex column stretches its children, which turned a two-word tag into a full-width box. */
.post-rail .tag, .post-rail .modbar { align-self: start; }
.post-rail a { color: inherit; text-decoration: none; }
.post-rail a:hover { text-decoration: underline; text-underline-offset: 3px; }

@media (max-width: 860px) {
  .post { grid-template-columns: 1fr; gap: 12px; }
  /* Sticky is meaningless in one column, and would pin the rail over the prose. */
  .post-rail { position: static; flex-direction: row; flex-wrap: wrap; gap: 6px 14px; align-items: baseline; }
}

/* ── prose: the only place a human's own markup lands ─────────────── */

.prose { max-width: 68ch; }
.prose > :first-child { margin-top: 0; }
.prose > :last-child { margin-bottom: 0; }
.prose p, .prose ul, .prose ol, .prose blockquote, .prose pre { margin: 0 0 1.05em; }
/* ⭐ A real scale, because a tutorial is a document. Every heading was 18px before, so h2 and h3
   — section and step — were indistinguishable, while h4–h6 had no rule at all and fell back to
   the BODY font: h5 landed at 13.7px and h6 at 11px, i.e. smaller than the text they head.
   ⛔ A post starts at h2. h1 belongs to the topic title, and two h1s on a page is a lie about
   structure that screen readers and search engines both read. */
.prose h1, .prose h2, .prose h3, .prose h4, .prose h5, .prose h6 {
  font-family: var(--font-display); font-weight: 700; letter-spacing: -.015em;
  text-transform: none; line-height: 1.22; text-wrap: balance;
  margin: 1.9em 0 .55em; scroll-margin-top: 20px;
}
.prose h1 { font-size: 26px; }
.prose h2 { font-size: 22px; padding-bottom: .3em; border-bottom: 1px solid var(--line); }
.prose h3 { font-size: 18px; }
.prose h4 { font-size: 16px; }
/* The last two stop growing and start speaking in the forum's own mono voice instead — smaller
   than h4 would be, but never smaller than the prose it introduces. */
.prose h5, .prose h6 {
  font-family: var(--font-mono); font-size: 12px; letter-spacing: .12em;
  text-transform: uppercase; color: var(--ink-soft); font-weight: 400;
}

/* The anchor sits after the words and appears on hover, so it never stands between the reader
   and the heading. Focus reveals it too, or it is unreachable from a keyboard. */
.anchor {
  margin-left: .4em; color: var(--ink-soft); text-decoration: none;
  opacity: 0; transition: opacity .12s ease; font-weight: 400;
}
.prose :is(h1, h2, h3, h4, h5, h6):hover .anchor,
.anchor:focus-visible { opacity: 1; }
.prose a { color: var(--ink); text-decoration-color: var(--ink-soft); }
.prose ul, .prose ol { padding-left: 1.4em; }
.prose li { margin-bottom: .3em; }
.prose blockquote {
  border-left: 2px solid var(--line); padding-left: 16px; margin-left: 0; color: var(--ink-soft);
}
.prose code {
  font-family: var(--font-mono); font-size: .88em;
  background: var(--paper-inset); border: 1px solid var(--line);
  border-radius: 3px; padding: 1px 5px;
}
.prose pre {
  background: var(--paper-inset); border: 1px solid var(--line); border-radius: 4px;
  padding: 14px 16px; overflow-x: auto;
}
.prose pre code { background: none; border: 0; padding: 0; font-size: 13px; }
.prose hr { border: 0; border-top: 1px solid var(--line); margin: 1.8em 0; }

/* ⭐ A photograph posted here lands in `imageWell`, never on the warm page ground. brand.md
   explains why the app has this colour at all: a warm surround SHIFTS the colour judgement, and
   the forum is exactly where somebody asks "why is this cast green". Answering over a warm brown
   would bias the answer they came for. The well reads as grey — R−B of 4, measured, not guessed.
   ⛔ The photograph itself is served as a photograph; this is a background, never a filter. */
.prose figure, .prose p > img { margin: 0 0 1.05em; }
.prose img {
  display: block; max-width: 100%; height: auto;
  background: var(--image-well); padding: 14px; border-radius: 3px;
  border: 1px solid var(--line);
}

/* ── the composer ─────────────────────────────────────────────────── */

.composer { max-width: 68ch; }
.composer .hint code { font-family: var(--font-mono); font-size: 12px; }

/* ── search ───────────────────────────────────────────────────────── */

.hit { padding-block: 4px; }
.searchbar { display: flex; gap: 12px; align-items: flex-end; flex-wrap: wrap; }
.searchbar .field { margin-top: 0; flex: 1 1 280px; }

/* ── an empty room ────────────────────────────────────────────────────
   Said plainly. A forum in its first weeks is mostly empty, and pretending otherwise with
   skeleton rows or "be the first!" exclamation reads worse than the truth. */
.empty {
  padding: 34px 0; color: var(--ink-soft); max-width: 56ch;
  border-bottom: 1px solid var(--line);
}

/* ── moderation ───────────────────────────────────────────────────────
   Five controls, and they look like furniture rather than a control panel: ghost buttons on a
   hairline, tucked under the title. ⛔ None of them wears --alarm. brand.md reserves that colour
   for "this cannot be undone", and every one of these is a soft, reversible UPDATE — spending the
   irreversible colour on a reversible act would make the real warnings mean less. */
.mod {
  display: flex; flex-wrap: wrap; gap: 10px 12px; align-items: center;
  margin-top: 16px; padding-top: 14px; border-top: 1px dashed var(--line);
}
.modbar { display: flex; gap: 8px; align-items: center; margin: 0; }
.modbar .btn { padding: 7px 12px; font-size: 11px; }
.modbar select,
.composer select,
.field select {
  background: var(--paper-inset); color: var(--ink);
  border: 1px solid var(--line); border-radius: 4px; padding: 7px 10px;
  font-family: var(--font-mono); font-size: 11px; letter-spacing: .1em; text-transform: uppercase;
}
.field select { width: 100%; padding: 11px 12px; font-size: 12px; }
/* In the post rail the delete control is one more mono line, not a button that shouts. */
.post-rail .modbar .btn { padding: 0; border: 0; background: none; color: var(--ink-soft); letter-spacing: .12em; }
.post-rail .modbar .btn:hover { color: var(--alarm); }

/* The EXIF warning. Ochre, not --alarm: the brand keeps red for "this cannot be undone", and this
   is a caution before an act, not the act itself. It is still the loudest hint on the page. */
.hint--warn { border-left: 2px solid var(--selection); padding-left: 12px; color: var(--ink); }
.hint--warn b { font-weight: 600; }
.field input[type="file"] {
  width: 100%; font-family: var(--font-mono); font-size: 12px; color: var(--ink-soft);
  background: var(--paper-inset); border: 1px solid var(--line); border-radius: 4px; padding: 10px 12px;
}

/* ── everything else a post can contain ───────────────────────────────
   The audit found these by rendering what the parser can emit and looking for tags with no rule.
   Each one below was an orphan: reachable from ordinary Markdown, styled nowhere. */

/* Tables. ⚠️ The scroll container is not optional: `body { overflow-x: hidden }` in the marketing
   stylesheet CLIPS a wide table instead of scrolling the page, so without this the right-hand
   columns are simply unreachable. The look is `.specs`: a hairline under the head, mono labels,
   tabular figures — a table here is an instrument reading, like everything else. */
.table-scroll { overflow-x: auto; margin: 0 0 1.2em; }
.prose table { border-collapse: collapse; width: 100%; font-size: 15px; }
.prose thead th {
  font-family: var(--font-mono); font-size: 11px; letter-spacing: .12em;
  text-transform: uppercase; color: var(--ink-soft); font-weight: 400;
  text-align: left; padding: 0 14px .7em 0; border-bottom: 1px solid var(--line);
  white-space: nowrap;
}
.prose tbody td {
  padding: .6em 14px .6em 0; border-bottom: 1px solid var(--line);
  vertical-align: baseline; font-variant-numeric: tabular-nums;
}
.prose tbody tr:last-child td { border-bottom: 0; }
.prose th:last-child, .prose td:last-child { padding-right: 0; }

/* A figure IS the image well now — the old `span.well` was emitted by the renderer and styled
   nowhere at all, so the well lived on the img itself and the span did nothing. */
.prose .shot { margin: 1.4em 0; }
.prose .shot img { margin: 0; }
.prose .shot figcaption {
  margin-top: 10px; color: var(--ink-soft); font-size: 14px; line-height: 1.45;
  max-width: 62ch;
}
/* Still reachable for an image inside a sentence, where a figure would break the paragraph. */
.prose .well { display: inline-block; max-width: 100%; }

/* ⚠️ The correction block puts its caption FIRST, so the global figcaption margin opened a gap
   above the label and left none between it and the instrument. */
.prose .embed { margin: 1.4em 0; }
.prose .embed figcaption {
  margin: 0 0 8px; font-family: var(--font-mono); font-size: 11px;
  letter-spacing: .14em; text-transform: uppercase; color: var(--ink-soft);
}

/* The alert label, in the forum's own voice — these words are the service speaking, not the
   author, so they are mono and uppercase like every other piece of chrome. */
.notice-label {
  margin: 0 0 .5em; font-family: var(--font-mono); font-size: 11px;
  letter-spacing: .14em; text-transform: uppercase; color: var(--ink-soft);
}
.notice--warn { border-left-color: var(--selection); }
.prose .notice { margin: 1.3em 0; }
.prose .notice > :last-child { margin-bottom: 0; }

/* Lists. A LOOSE list (blank lines between items) wraps each item in <p>, which then carried the
   full paragraph margin on top of the item margin — so loose and tight lists spaced completely
   differently, and a nested list pushed its parent item open. */
.prose li > p { margin-bottom: .5em; }
.prose li > p:last-child { margin-bottom: 0; }
.prose li > ul, .prose li > ol { margin: .4em 0 0; }
.prose ol { padding-left: 1.5em; }
.prose ol li::marker { font-variant-numeric: tabular-nums; color: var(--ink-soft); }
.prose blockquote > :last-child { margin-bottom: 0; }

/* Strikethrough is reachable from `~~x~~` and had no rule: it read as an accident rather than a
   correction. Dimmed, because struck text is text the author has withdrawn. */
.prose s { color: var(--ink-soft); text-decoration-thickness: 1px; }
.prose em { font-style: italic; }
.prose strong { font-weight: 600; color: var(--ink); }

/* An autolinked URL has no spaces to break at and would otherwise run straight out of the
   68ch column. */
.prose { overflow-wrap: anywhere; }
.prose pre { overflow-wrap: normal; }
/* An image inside a link would otherwise wear the link's underline across its whole width. */
.prose a:has(> .well), .prose a:has(> .shot) { text-decoration: none; }

/* ── contents rail ────────────────────────────────────────────────────
   Only on a post long enough to need it. It rides in the rail that is already `position: sticky`,
   so it costs no new layout and no JavaScript. */
.toc { margin-top: 18px; padding-top: 14px; border-top: 1px solid var(--line); }
.toc-label {
  font-family: var(--font-mono); font-size: 10px; letter-spacing: .14em;
  text-transform: uppercase; color: var(--ink-soft); margin: 0 0 8px;
}
.toc ol { list-style: none; margin: 0; padding: 0; }
.toc li { margin-bottom: .45em; line-height: 1.35; }
.toc li[data-level="3"] { padding-left: 12px; }
.toc a {
  font-family: var(--font-body); font-size: 12.5px; letter-spacing: 0;
  text-transform: none; color: var(--ink-soft); text-decoration: none;
}
.toc a:hover { color: var(--ink); }
@media (max-width: 860px) { .toc { display: none; } }

/* ── the classes that replaced inline styles ──────────────────────────
   ⛔ Not cosmetic housekeeping. CSP here is `style-src 'self'`, which blocks the `style=`
   ATTRIBUTE as well as <style> blocks — so every inline style in these views was being dropped
   silently in production while looking perfect in a local file:// preview, which carries no CSP.
   Measured on the deployed Worker: the footer's `padding-block:28px` computed to 0px.
   ⛔ The fix is classes, never `'unsafe-inline'`: inline style is a real injection surface, and
   buying back some margins with it would be the worst trade on this page. */
.inline { display: inline; }
.foot { padding-block: 28px; border-top: 1px solid var(--line); }
.sec-head--board { margin-block: 36px 0; }
.sec-head--page { margin-block: 32px 0; }
.sec-head--user { margin-block: 24px 0; }
.kicker--spaced { margin-top: 40px; }
.composer--reply { margin-top: 34px; }
.notice--after { margin-top: 34px; }
.form-actions--inline { margin-top: 0; }
.hint--block { display: block; margin-bottom: 8px; }
.hint--spaced { margin-top: 28px; }
.field textarea.short { min-height: 120px; }

/* ── avatars ──────────────────────────────────────────────────────────
   docs/forum-plan.md refused these: "the content here is measurements; a face should not be the
   first thing the eye lands on". The owner reversed that, and the reason is kept by SIZE and
   PLACEMENT instead — 20px beside the name in the metadata rail, never Discourse's 45px at the
   head of a row. The title still wins the eye; the picture only says who.

   ⛔ The tones are a WARM NEUTRAL ramp, never the brand palette. Every colour in this system
   already means something — R/G/B are the capture bands, C/M/Y the correction directions, ochre
   is `chosen`, --alarm is `irreversible` — and spending that vocabulary on "which member is this"
   is the same bad trade the plan refuses for category colours. Warm rather than pure grey for the
   reason brand.md gives about imageWell: a measured neutral looks blue beside this warm brown.
   ⛔ And never the three-bar mark, which brand.md:17 says is never restyled. */
.avatar {
  position: relative; display: inline-grid; place-items: center;
  flex: none; overflow: hidden; border-radius: 3px; vertical-align: -0.18em;
  font-family: var(--font-mono); font-weight: 400; color: var(--ink-soft);
  letter-spacing: 0; line-height: 1; user-select: none;
}
.avatar--sm { width: 20px; height: 20px; font-size: 10px; margin-right: 7px; }
.avatar--md { width: 28px; height: 28px; font-size: 13px; margin-right: 9px; }
/* Layered over the letter, so a removed Gravatar (which answers d=blank) simply reveals the
   monogram again — no JavaScript, no onerror, no broken-image icon. */
.avatar img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }

.avatar[data-tone="0"] { background: #262320; }
.avatar[data-tone="1"] { background: #2E2A25; }
.avatar[data-tone="2"] { background: #36322B; }
.avatar[data-tone="3"] { background: #3E3930; }
.avatar[data-tone="4"] { background: #464036; }
.avatar[data-tone="5"] { background: #4E473B; }

/* The name and its picture are one target, and must not break apart at the end of a line. */
.topic-meta .by, .post-rail .who { display: inline-flex; align-items: center; white-space: nowrap; }
.topic-title.with-avatar { display: flex; align-items: center; gap: 0; }

/* ── the editor ───────────────────────────────────────────────────────
   Two columns above 1100px: what you type on the left, what it becomes on the right. Below that
   they stack, because a 40ch textarea beside a 40ch preview is worse than either alone. */
.editor { display: grid; grid-template-columns: 1fr; gap: 4px 28px; }
@media (min-width: 1100px) {
  .composer { max-width: none; }
  .editor { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); align-items: start; }
  .editor > .field { grid-column: 1; }
  .editor > .hint, .editor > .composer-status { grid-column: 1; }
  .editor > .preview { grid-column: 2; grid-row: 1 / span 5; position: sticky; top: 20px; }
  .editor textarea { min-height: 60vh; }
}

.preview { border-left: 1px solid var(--line); padding-left: 22px; min-width: 0; }
.preview-label {
  margin: 0 0 14px; font-family: var(--font-mono); font-size: 10px;
  letter-spacing: .14em; text-transform: uppercase; color: var(--ink-soft);
}
/* Empty until the first keystroke — a preview pane full of placeholder text is a lie about
   what you have written. */
.preview .prose:empty::before {
  content: "Nothing yet."; color: var(--ink-soft); font-size: 14px;
}

.composer-status {
  margin: 8px 0 0; min-height: 1.2em;
  font-family: var(--font-mono); font-size: 11px; letter-spacing: .12em;
  text-transform: uppercase; color: var(--ink-soft);
}
.composer-status.is-bad { color: var(--alarm); }

/* The drop target says so while a file is over it, and says it in the one colour that means
   `chosen` rather than `wrong`. */
.field textarea.is-dropping { outline: 2px dashed var(--selection); outline-offset: 2px; }
