/**
 * The page itself.
 *
 * Every size here is a ratio against the reader's text-size setting rather than
 * a fixed pixel value, and every line height is a multiple of its own font size.
 * A fixed `line-height` against a scaling `font-size` clips at large sizes —
 * which are exactly the sizes this site exists to serve.
 */

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

html {
  /* Nothing on this site is far enough down to need a jump, and anchored
     movement is unpleasant for a fair share of this audience. */
  scroll-behavior: auto;
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  background: var(--ground);
  color: var(--ink-soft);
  font-family: var(--font);
  font-size: calc(1.0625rem * var(--scale));
  line-height: calc(1.55 * var(--leading));
  letter-spacing: calc(var(--tracking) * 1em);
  /* Left-aligned, never justified. Justification opens vertical rivers of white
     space through a paragraph, which is a specific and documented problem here. */
  text-align: left;
  text-wrap: pretty;
}

/* ---------- Type scale ---------------------------------------------------- */

h1,
h2,
h3 {
  margin: 0 0 var(--space-md);
  color: var(--ink-soft);
  /* Tracking is set here and added to the reader's, never replacing it. */
  letter-spacing: calc(var(--tracking) * 1em - 0.01em);
}

h1 {
  font-size: calc(1.875rem * var(--scale));
  line-height: calc(1.25 * var(--leading));
  font-weight: 700;
  margin-bottom: var(--space-base);
}

h2 {
  font-size: calc(1.375rem * var(--scale));
  line-height: calc(1.3 * var(--leading));
  font-weight: 650;
  margin-top: var(--space-xl);
}

h3 {
  font-size: calc(1.125rem * var(--scale));
  line-height: calc(1.35 * var(--leading));
  font-weight: 600;
  margin-top: var(--space-lg);
}

p,
ul,
ol,
dl {
  margin: 0 0 var(--space-base);
}

li {
  margin-bottom: var(--space-sm);
}

small,
.caption {
  font-size: calc(0.8125rem * var(--scale));
  line-height: calc(1.4 * var(--leading));
  color: var(--ink-muted);
}

strong,
b {
  font-weight: 700;
}

/**
 * Italic is the worst-performing style in the reading research, so publisher
 * emphasis is carried by weight instead — the same call the app makes.
 */
em,
i {
  font-style: normal;
  font-weight: 700;
}

a {
  color: var(--accent-ink);
  text-decoration-thickness: 0.08em;
  text-underline-offset: 0.18em;
}

a:hover {
  text-decoration-thickness: 0.14em;
}

/**
 * A focus ring that survives every palette above. Two rings — one in the ink,
 * one in the ground — so it is visible whichever way round the contrast runs.
 */
:focus-visible {
  outline: 3px solid var(--ink-soft);
  outline-offset: 2px;
  border-radius: 2px;
}

/* ---------- Layout -------------------------------------------------------- */

/**
 * Available to a screen reader, absent from the page.
 *
 * Used where a control's visible text is a sample rather than a name — a swatch
 * reading "Aa" tells a sighted reader everything and a screen-reader user
 * nothing, so the tint's name rides along here.
 */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.skip {
  position: absolute;
  left: -9999px;
  top: 0;
  background: var(--raised);
  color: var(--ink-soft);
  padding: var(--space-md) var(--space-base);
  border-radius: 0 0 var(--radius-sm) 0;
  z-index: 20;
}

.skip:focus {
  left: 0;
}

.wrap {
  width: 100%;
  max-width: 68rem;
  margin: 0 auto;
  padding: 0 var(--space-lg);
}

/**
 * Prose is capped around 66 characters. Long measures make the return sweep to
 * the next line fail, which reads as losing your place.
 *
 * The cap goes on the lines, not on the column. Measure is a property of a
 * *paragraph* — the app makes the same distinction — so putting it on the
 * container would squeeze the tables and cards sitting between the paragraphs
 * down to the width of a sentence.
 */
/**
 * Centred inside the wrap, not left-aligned within it.
 *
 * Without `margin-inline`, a 52rem column inside a 68rem wrap sits hard against
 * the left of it — on a wide screen that read as 161px of margin on one side and
 * 377px on the other, which looks like a layout that failed rather than one that
 * caps its measure on purpose. The text inside stays left-aligned; it is the
 * column that centres.
 */
.prose {
  /**
   * Sized so the column is not much wider than the lines inside it.
   *
   * At 52rem there were 227px of empty column to the right of every paragraph,
   * because the measure cap below holds a line to about 66 characters and the
   * container was far wider than that needs. It read as a layout that had come
   * apart rather than one capping its measure on purpose.
   *
   * Scaled by `--scale` because the measure cap is in `ch`, which grows with the
   * text, while `rem` does not. A fixed column would become the binding
   * constraint at large text sizes and quietly overrule the reader's own line
   * width setting — at exactly the sizes this site exists to serve.
   */
  max-width: calc(42rem * var(--scale));
  margin-inline: auto;
}

.prose p,
.prose li,
.prose dd,
.prose h2,
.prose h3 {
  max-width: calc(var(--measure) * 1ch);
}

main {
  padding: var(--space-xl) 0 var(--space-xxl);
}

/* ---------- Header and footer --------------------------------------------- */

.site-header {
  position: relative; /* The menu dropdown hangs off this. */
  border-bottom: 1px solid var(--border);
  background: var(--ground);
}

.site-header .wrap {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-base);
  padding-top: var(--space-md);
  padding-bottom: var(--space-md);
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: var(--space-sm);
  font-weight: 700;
  font-size: calc(1.1875rem * var(--scale));
  color: var(--ink-soft);
  text-decoration: none;
  min-height: var(--touch);
}

.brand img {
  width: calc(1.75rem * var(--scale));
  height: calc(1.75rem * var(--scale));
  border-radius: 25%;
  display: block;
}

/**
 * The header holds three things: who this is, where else to go, and the control
 * that changes how it all reads.
 *
 * It carried no navigation for as long as there was one substantive page, on the
 * grounds that a "Home" link beside a logo that already goes home is furniture.
 * That reasoning expired the moment there was somewhere to navigate to.
 *
 * Only the four pages somebody might actually be looking for. Privacy, terms and
 * support stay in the footer, which is where people look for them and where they
 * do not compete with the reading control — which on this site is a feature
 * rather than chrome, and has to stay early in the tab order.
 */
.site-nav {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-xs) var(--space-lg);
  align-items: center;
}

/**
 * Body size, not a smaller nav size, and the reason is alignment rather than
 * taste.
 *
 * The wordmark and the links sit in boxes of the same height, both centred — so
 * the boxes line up exactly and the *text* does not. A larger glyph centred in a
 * box has a lower baseline than a smaller one, and at 15px against the
 * wordmark's 19px the links rode 1.9px high, which is small enough to look like
 * a mistake rather than a choice. Matching the sizes is what fixes it; nudging
 * one of them with a magic number would only hide it until something changed.
 *
 * 17px leaves 0.45px, which is below a device pixel on an ordinary screen. Exact
 * zero needs the links at the wordmark's own 19px, and four of them at that size
 * crowd the row and wrap it sooner than they need to.
 */
.site-nav a {
  display: inline-flex;
  align-items: center;
  min-height: var(--touch);
  color: var(--ink-muted);
  font-size: calc(1.0625rem * var(--scale));
  font-weight: 600;
  text-decoration: none;
}

.site-nav a:hover {
  color: var(--ink-soft);
  text-decoration: underline;
  text-decoration-thickness: 0.08em;
  text-underline-offset: 0.18em;
}

/**
 * The current page is marked by a rule under it *and* by the ink going full
 * strength. Two signals, because a calibrated palette is low-chroma by design
 * and a colour shift alone would vanish in exactly the themes this site exists
 * to serve.
 */
.site-nav a[aria-current='page'] {
  color: var(--ink-soft);
  box-shadow: inset 0 -3px 0 var(--accent);
}

/**
 * The menu button, and the dropdown it opens.
 *
 * Every rule below is written against `.nav-toggle + .site-nav` rather than the
 * nav on its own. The button only exists if `scripts/nav.js` ran, so the
 * selector cannot match without it — and a reader with JavaScript off gets the
 * plain row of links this started as, rather than a nav that has been hidden by
 * a stylesheet with nothing left to reveal it.
 */
.nav-toggle {
  display: none;
}

/** Three bars, drawn rather than fetched, in whatever ink the button is using. */
.nav-toggle-bars {
  position: relative;
  display: block;
  width: 1.15em;
  height: 2px;
  border-radius: 2px;
  background: currentColor;
}

.nav-toggle-bars::before,
.nav-toggle-bars::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  height: 2px;
  border-radius: 2px;
  background: currentColor;
}

.nav-toggle-bars::before {
  top: -0.4em;
}

.nav-toggle-bars::after {
  top: 0.4em;
}

@media (max-width: 47.99rem) {
  /**
   * Last in the row, past the reading control, and square.
   *
   * `order` rather than a different insertion point in the markup, because the
   * button has to keep coming *before* the nav it discloses in the focus order —
   * tabbing off a menu button and not landing in the menu is the one thing a
   * disclosure must not do. The cost is that the button is reached second and
   * drawn last, which is a jump between two adjacent controls in the same row.
   *
   * Above this breakpoint nothing is reordered: the wide header is laid out in
   * document order exactly as it reads.
   */
  .nav-toggle {
    order: 1;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: var(--touch);
    min-height: var(--touch);
    padding: 0;
    border: 1px solid var(--border);
    border-radius: var(--radius-pill);
    background: var(--raised);
    color: var(--ink-soft);
    font-family: inherit;
    cursor: pointer;
  }

  .nav-toggle[aria-expanded='true'] {
    background: var(--accent-ink);
    color: var(--ground);
    border-color: var(--accent-ink);
  }

  /**
   * `.site-nav` sets `display: flex`, which is the same specificity as the
   * browser's own `[hidden]` rule and comes later — so without this the script
   * would set `hidden` and nothing would happen.
   */
  .nav-toggle + .site-nav[hidden] {
    display: none;
  }

  .nav-toggle + .site-nav {
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    z-index: 25;
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    padding: var(--space-xs) var(--space-lg) var(--space-sm);
    background: var(--raised);
    border-bottom: 2px solid var(--border);
  }

  .nav-toggle + .site-nav a {
    font-size: calc(1.0625rem * var(--scale));
    border-bottom: 1px solid var(--border);
  }

  .nav-toggle + .site-nav a:last-child {
    border-bottom: none;
  }

  /* A rule down the side rather than under the text, which the row border
     already occupies. Still two signals: the mark and the full-strength ink. */
  .nav-toggle + .site-nav a[aria-current='page'] {
    box-shadow: inset 3px 0 0 var(--accent);
    padding-left: var(--space-md);
  }
}

#reading-controls {
  margin-left: auto;
}

/* ---------- Footer -------------------------------------------------------- */

.site-footer {
  border-top: 1px solid var(--border);
  /* Raised, so the settling edge above it has a silhouette to draw. The
     footer's text tokens are contrast-tested on this surface. */
  background: var(--raised);
  padding: var(--space-xl) 0 var(--space-xxl);
  color: var(--ink-muted);
  font-size: calc(0.875rem * var(--scale));
}

.footer-top {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-base) var(--space-xl);
  align-items: center;
  justify-content: space-between;
  padding-bottom: var(--space-base);
  margin-bottom: var(--space-base);
  border-bottom: 1px solid var(--border);
}

.site-footer .brand {
  font-size: calc(1.0625rem * var(--scale));
  color: var(--ink-muted);
}

.footer-nav {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-base) var(--space-lg);
  align-items: center;
}

.footer-nav a {
  color: var(--ink-muted);
  min-height: var(--touch);
  display: inline-flex;
  align-items: center;
  text-decoration: none;
}

.footer-nav a:hover,
.footer-nav a[aria-current='page'] {
  color: var(--ink-soft);
  text-decoration: underline;
  text-decoration-thickness: 0.08em;
  text-underline-offset: 0.18em;
}

.footer-note {
  max-width: 70ch;
  margin-bottom: var(--space-sm);
}

.footer-note:last-child {
  margin-bottom: 0;
}

/* ---------- Pieces -------------------------------------------------------- */

/* Shared by the hero and by every page head, so it lives here rather than in
   either of the page-specific sheets. */
.eyebrow {
  display: inline-block;
  font-size: calc(0.875rem * var(--scale));
  font-weight: 600;
  letter-spacing: calc(var(--tracking) * 1em + 0.06em);
  text-transform: uppercase;
  color: var(--ink-muted);
  margin-bottom: var(--space-md);
}

.lead {
  font-size: calc(1.25rem * var(--scale));
  line-height: calc(1.5 * var(--leading));
  color: var(--ink-muted);
  margin-bottom: var(--space-lg);
}

.card {
  background: var(--raised);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: var(--space-lg);
  margin-bottom: var(--space-base);
}

.card h2,
.card h3 {
  margin-top: 0;
}

.note {
  background: var(--sunken);
  border-left: 4px solid var(--accent);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  padding: var(--space-base) var(--space-lg);
  margin-bottom: var(--space-lg);
}

.note :last-child {
  margin-bottom: 0;
}

.grid {
  display: grid;
  gap: var(--space-base);
  grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
  margin-bottom: var(--space-lg);
}

/**
 * A table on a phone is the most reliable way to break a layout, so every one
 * scrolls inside its own box rather than pushing the page sideways.
 */
.table-scroll {
  overflow-x: auto;
  margin-bottom: var(--space-lg);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}

table {
  border-collapse: collapse;
  width: 100%;
  min-width: 30rem;
}

th,
td {
  text-align: left;
  padding: var(--space-md) var(--space-base);
  border-bottom: 1px solid var(--border);
  vertical-align: top;
}

th {
  font-weight: 600;
  background: var(--sunken);
}

tr:last-child td {
  border-bottom: none;
}

dt {
  font-weight: 600;
  margin-top: var(--space-base);
}

dd {
  margin: var(--space-xs) 0 0;
  color: var(--ink-muted);
}

/**
 * Unfilled copy, marked so it cannot ship by accident.
 *
 * Legal identity and contact details are the operator's to supply, and a
 * plausible-looking invention would be worse than a visible gap.
 */
.todo {
  background: #ffd9a0;
  color: #3a2a10;
  padding: 0 0.3em;
  border-radius: 3px;
  font-weight: 700;
}

.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: var(--touch);
  padding: var(--space-md) var(--space-lg);
  border-radius: var(--radius-sm);
  border: 1px solid var(--accent-ink);
  background: var(--accent-ink);
  color: var(--ground);
  font-family: inherit;
  font-size: inherit;
  font-weight: 600;
  text-decoration: none;
  cursor: pointer;
}

.button.secondary {
  background: transparent;
  color: var(--ink-soft);
  border-color: var(--border);
}

/* ---------- Screenshots and figures ---------------------------------------- */

/**
 * A phone-shaped frame around a real capture.
 *
 * The frame is drawn from the tokens rather than being part of the image, so it
 * sits correctly on every ground and a replacement screenshot needs no matching
 * bezel baked into it.
 *
 * Here rather than in `home.css` because the homepage is no longer the only
 * place that shows the app. A rule used by two page types that lives in one
 * page's sheet is how `.eyebrow` came to render unstyled on four pages.
 */
.shot {
  display: block;
  width: 100%;
  max-width: 17rem;
  margin: 0 auto;
  border-radius: calc(var(--radius-lg) + 12px);
  border: 10px solid color-mix(in oklab, var(--ground), var(--ink) 32%);
  background: color-mix(in oklab, var(--ground), var(--ink) 32%);
  overflow: hidden;
}

.shot img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: var(--radius-md);
}

.shot-pair {
  display: grid;
  gap: var(--space-lg);
  grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
  align-items: start;
}

.shot-pair .shot {
  max-width: 100%;
}

figure {
  margin: 0 0 var(--space-lg);
}

figcaption {
  margin-top: var(--space-md);
  font-size: calc(0.8125rem * var(--scale));
  color: var(--ink-muted);
  text-align: center;
}

/** A paragraph held to a shorter measure than the column it sits in. */
.tight {
  max-width: 54ch;
}

/**
 * The hero's underline grown into a real section edge: the surface that
 * follows starts as a wave on the left and settles flat before mid-screen.
 * Not a line near the boundary — the filled shape IS the next section's
 * silhouette, in its own background colour, with the ordinary hairline
 * running along the curve. Used sparingly: an entrance and an exit, not
 * every boundary. It must sit directly above a raised surface (a tinted
 * band, or the footer) for the colours to join.
 */
.settling-divider {
  line-height: 0;
  /* Overlaps the surface below by a pixel, so no seam of ground shows. */
  margin-bottom: -1px;
}

.settling-divider svg {
  display: block;
  width: 100%;
  height: 30px;
}

.settling-fill {
  fill: var(--raised);
}

/* When the edge caps the sunken strip instead of a raised surface. */
.settling-divider-sunken .settling-fill {
  fill: var(--sunken);
}

/**
 * The exit variant: caps the bottom of a tinted band. The div carries the
 * band's own colour, and the fill is the page ground rising through it —
 * the homepage's entrance, mirrored.
 */
.settling-divider-out {
  background: var(--raised);
  margin: -1px 0 var(--space-xl);
}

.settling-divider-out .settling-fill {
  fill: var(--ground);
}

.settling-edge {
  stroke: var(--border);
}

.settling-divider + .band,
.settling-divider + .site-footer {
  border-top: 0;
}

/**
 * The quiet register: smaller, and muted through the asserted token rather
 * than opacity. Dimming ink with opacity lands below 4.5:1 on the darker
 * tints — the exact failure the retired third text tier shipped — and does it
 * where the contrast test cannot see. `--ink-muted` is recomputed against
 * every tint by `site.test.ts`, so quiet stays readable.
 */
.small {
  font-size: calc(0.9rem * var(--scale));
  color: var(--ink-muted);
}

/**
 * A sample of the reading surface, used to show a pairing rather than describe
 * one. The colours are set inline per element because they are the content.
 */
.sample {
  border-radius: var(--radius-md);
  padding: var(--space-lg);
  border: 1px solid var(--border);
}

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

.sample.plain {
  background: #ffffff;
  color: #000000;
}

/* ---------- Marks: the test's own pictures --------------------------------- */

/**
 * Ported from the app's calibration cards (`calibrationMarks.tsx`), in the same
 * language: flat rounded shapes, no more than about five per mark, drawn in the
 * ink at opacities — so one mark works on every tint with no image files. The
 * absolute greys are absolute on purpose, exactly as in the app: drawn from the
 * ink, their meaning would invert on dark grounds.
 *
 * Every mark is `aria-hidden` and sits beside words that say the same thing,
 * which is the app's own rule for these.
 */
.mark {
  display: flex;
  align-items: center;
  gap: 6px;
  margin: 0 0 var(--space-md);
}

.mark span {
  display: block;
  border-radius: 6px;
}

/* Two kinds of page: absolute light against absolute dark. */
.mark-island span {
  width: 30px;
  height: 42px;
  border: 1px solid var(--border);
}

.mark-island span:nth-child(1) {
  background: #e2e2e2;
}

.mark-island span:nth-child(2) {
  background: #2b2b2b;
}

/* One page, and the four strengths underneath it. */
.mark-colour {
  flex-wrap: wrap;
  width: 58px;
  gap: 4px;
}

.mark-colour span {
  background: var(--ink);
}

.mark-colour span:nth-child(1) {
  width: 58px;
  height: 30px;
  opacity: 0.22;
}

.mark-colour span:nth-child(n + 2) {
  width: 10px;
  height: 5px;
  border-radius: 3px;
}

.mark-colour span:nth-child(2) {
  opacity: 0.8;
}

.mark-colour span:nth-child(3) {
  opacity: 0.6;
}

.mark-colour span:nth-child(4) {
  opacity: 0.4;
}

.mark-colour span:nth-child(5) {
  opacity: 0.24;
}

/* Two almost touching, and almost the same. */
.mark-narrow {
  gap: 2px;
}

.mark-narrow span {
  width: 28px;
  height: 42px;
  background: var(--ink);
  opacity: 0.26;
}

.mark-narrow span:nth-child(2) {
  opacity: 0.2;
}

/* A tonal ladder: the same page, lighter or darker. */
.mark-bright span {
  width: 15px;
  height: 38px;
  border: 1px solid var(--border);
}

.mark-bright span:nth-child(1) {
  background: #e2e2e2;
}

.mark-bright span:nth-child(2) {
  background: #969696;
}

.mark-bright span:nth-child(3) {
  background: #5a5a5a;
}

.mark-bright span:nth-child(4) {
  background: #2b2b2b;
}

/* The page holds still; the writing inside it changes. */
.mark-words {
  flex-direction: column;
  align-items: flex-start;
  gap: 5px;
  border: 1.5px solid var(--border);
  border-radius: 8px;
  padding: 8px 10px;
}

.mark-words span {
  width: 38px;
  height: 4px;
  border-radius: 2px;
  background: var(--ink);
  opacity: 0.55;
}

.mark-words span:nth-child(2) {
  opacity: 0.24;
}

.mark-words span:nth-child(3) {
  width: 24px;
}

/* The same thing three times. Looking again, which is all checking is. */
.mark-check span {
  width: 22px;
  height: 38px;
  background: var(--ink);
  opacity: 0.24;
}

/**
 * Two plainly different pages, and an equals — the app's own picture for
 * "no difference is a real answer". Neither panel is marked, because marking
 * one is the thing this card exists to deny.
 */
.mark-equal {
  gap: 8px;
}

.mark-equal > span:first-child,
.mark-equal > span:last-child {
  width: 30px;
  height: 42px;
  background: var(--ink);
}

.mark-equal > span:first-child {
  opacity: 0.3;
}

.mark-equal > span:last-child {
  opacity: 0.16;
}

/**
 * A mark beside its heading rather than on its own row. Beside, never
 * wrapped: text keeps a single straight edge, which this audience's return
 * sweep depends on. Below 40rem it stacks rather than squeezing the measure.
 */
.mark-head {
  margin-bottom: var(--space-base);
}

.mark-head .mark {
  margin: 0 0 var(--space-md);
}

.mark-head h2 {
  margin: 0;
}

@media (min-width: 40rem) {
  .mark-head {
    display: flex;
    align-items: center;
    gap: var(--space-base);
  }

  .mark-head .mark {
    margin: 0;
  }
}

/**
 * Text is a striped pattern. A page of prose, an equals, a grating — the whole
 * mechanism in three shapes, replacing a paragraph that said the same thing.
 */
.mark-stripes {
  gap: 10px;
}

.mk-lines,
.mk-grating {
  display: flex;
  flex-direction: column;
  gap: 4px;
  border: 1.5px solid var(--border);
  border-radius: 8px;
  padding: 8px 10px;
}

.mk-lines span,
.mk-grating span {
  height: 4px;
  border-radius: 2px;
  background: var(--ink);
  opacity: 0.55;
}

.mk-lines span:nth-child(1) {
  width: 44px;
}

.mk-lines span:nth-child(2) {
  width: 34px;
}

.mk-lines span:nth-child(3) {
  width: 42px;
}

.mk-lines span:nth-child(4) {
  width: 26px;
}

.mk-grating span {
  width: 44px;
}

.mk-equals {
  display: flex;
  flex-direction: column;
  gap: 5px;
}

.mk-equals span {
  width: 16px;
  height: 3px;
  border-radius: 2px;
  background: var(--ink);
  opacity: 0.45;
}

/**
 * The symptom itself: a page whose lines carry a ghost double. Each bar's
 * pseudo-element repeats it, nudged down and right at a fraction of the
 * opacity — text that will not resolve into one image.
 */
.mark-double {
  flex-direction: column;
  align-items: flex-start;
  gap: 7px;
  border: 1.5px solid var(--border);
  border-radius: 8px;
  padding: 10px 12px;
}

.mark-double span {
  position: relative;
  width: 40px;
  height: 4px;
  border-radius: 2px;
  background: var(--ink);
  opacity: 0.55;
}

.mark-double span::after {
  content: '';
  position: absolute;
  left: 3px;
  top: 3px;
  width: 100%;
  height: 100%;
  border-radius: 2px;
  background: var(--ink);
  opacity: 0.45;
}

.mark-double span:nth-child(3) {
  width: 26px;
}

/* Small print, then the same words at the reader's size and spacing. */
.mark-bigger {
  gap: 8px;
}

.mk-cramped {
  display: flex;
  flex-direction: column;
  gap: 2px;
  border: 1.5px solid var(--border);
  border-radius: 8px;
  padding: 6px 8px;
}

.mk-cramped span {
  width: 26px;
  height: 2px;
  border-radius: 1px;
  background: var(--ink);
  opacity: 0.55;
}

.mk-cramped span:nth-child(4) {
  width: 16px;
}

.mk-roomy {
  display: flex;
  flex-direction: column;
  gap: 8px;
  border: 1.5px solid var(--border);
  border-radius: 8px;
  padding: 10px 12px;
}

.mk-roomy span {
  width: 40px;
  height: 5px;
  border-radius: 3px;
  background: var(--ink);
  opacity: 0.55;
}

.mk-roomy span:nth-child(3) {
  width: 26px;
}

/**
 * Sound: level bars with the logo's fully rounded caps. The one mark whose
 * subject is not a page.
 */
.mark-sound {
  gap: 4px;
  min-height: 40px;
}

.mark-sound span {
  width: 6px;
  border-radius: 999px;
  background: var(--ink);
}

.mark-sound span:nth-child(1) {
  height: 14px;
  opacity: 0.35;
}

.mark-sound span:nth-child(2) {
  height: 26px;
  opacity: 0.5;
}

.mark-sound span:nth-child(3) {
  height: 38px;
  opacity: 0.7;
}

.mark-sound span:nth-child(4) {
  height: 20px;
  opacity: 0.5;
}

.mark-sound span:nth-child(5) {
  height: 30px;
  opacity: 0.35;
}

/* A page handed on: the copy behind, yours in front. */
.mark-pass {
  align-items: flex-start;
  gap: 0;
}

.mk-page-back {
  width: 34px;
  height: 46px;
  border-radius: 8px;
  background: var(--ink);
  opacity: 0.16;
}

.mk-page-front {
  display: flex;
  flex-direction: column;
  gap: 5px;
  margin: 12px 0 0 -16px;
  border: 1.5px solid var(--border);
  border-radius: 8px;
  padding: 8px 9px;
  background: var(--ground);
}

.mk-page-front span {
  width: 22px;
  height: 4px;
  border-radius: 2px;
  background: var(--ink);
  opacity: 0.55;
}

.mk-page-front span:nth-child(3) {
  width: 14px;
}

/* ---------- The six stretches of the test ---------------------------------- */

/**
 * The test shown rather than described: the same six sections, labels and
 * pictures the app's own cards use. A test asserts the words match
 * `calibrationCards.tsx`, so the strip cannot quietly describe a test the app
 * no longer has.
 */
.test-strip {
  display: grid;
  gap: var(--space-md);
  grid-template-columns: repeat(auto-fit, minmax(8.5rem, 1fr));
  list-style: none;
  padding: 0;
  margin: var(--space-lg) 0;
}

.test-strip li {
  margin: 0;
  background: var(--ground);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: var(--space-md);
}

.test-card-label {
  font-size: calc(0.8125rem * var(--scale));
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: calc(var(--tracking) * 1em + 0.06em);
  color: var(--ink-muted);
  margin: 0;
}

.test-card-line {
  font-size: calc(0.9375rem * var(--scale));
  line-height: calc(1.4 * var(--leading));
  color: var(--ink-soft);
  margin: var(--space-xs) 0 0;
}

/**
 * Each card can open its stage's own app card as a popover — tap anywhere on
 * the card, hover where hovering exists, Escape or tap-away to close. The
 * popover floats above the card, so nothing on the page moves when it opens:
 * the same no-shift rule that keeps the FAQ un-collapsed.
 *
 * `home.js` builds the toggle button, so without JavaScript there is no dead
 * control and no dotted affordance — just the labels.
 */
.test-strip li {
  position: relative;
}

.test-card-toggle {
  display: block;
  width: 100%;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
}

/**
 * `.test-strip-live` is added by the script, so the pointer cursor and hover
 * reaction only exist where the cards actually respond.
 */
.test-strip-live li {
  cursor: pointer;
  transition: border-color 150ms ease;
}

.test-strip-live li:hover,
.test-strip-live li:has(.test-card-toggle[aria-expanded='true']) {
  border-color: var(--accent);
}

/**
 * The popover floats over neighbouring cards, so it must read as a layer, not
 * another card: the accent border the site's important panels already use,
 * and a soft shadow — the one place on the site depth genuinely means
 * something, because the box really is on top of other content.
 */
.test-card-info {
  position: absolute;
  inset-inline: 0;
  top: calc(100% + var(--space-xs));
  z-index: 5;
  margin: 0;
  padding: var(--space-md);
  background: var(--ground);
  border: 2px solid var(--accent);
  border-radius: var(--radius-md);
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.18);
  font-size: calc(0.875rem * var(--scale));
  line-height: calc(1.45 * var(--leading));
  color: var(--ink-soft);

  /**
   * Open and close animate through the `hidden` toggle itself:
   * `allow-discrete` holds `display` until the fade finishes on the way out,
   * and `@starting-style` below gives the way in somewhere to fade from.
   * Browsers without either simply show and hide instantly, and the global
   * reduced-motion rule collapses the whole thing.
   */
  display: block;
  opacity: 1;
  translate: 0 0;
  transition-property: opacity, translate, display;
  transition-duration: 180ms;
  transition-timing-function: ease;
  transition-behavior: allow-discrete;
}

.test-card-info[hidden] {
  display: none;
  opacity: 0;
  translate: 0 -6px;
}

@starting-style {
  .test-card-info:not([hidden]) {
    opacity: 0;
    translate: 0 -6px;
  }
}

/**
 * On wide screens the popover opens to the top-right, its bottom-left corner
 * overlapping the card's top-right — a callout growing out of its own card —
 * and slides diagonally from that corner. The script flips it to top-left
 * (`.info-left`) when the box would leave the viewport. Below the breakpoint
 * it stays underneath, where a phone has room.
 */
@media (min-width: 60rem) {
  .test-card-info {
    top: auto;
    right: auto;
    bottom: calc(100% - var(--space-md));
    left: calc(100% - var(--space-md));
    width: calc(13rem * var(--scale));
  }

  .test-card-info.info-left {
    left: auto;
    right: calc(100% - var(--space-md));
  }

  .test-card-info[hidden] {
    translate: -8px 8px;
  }

  .test-card-info.info-left[hidden] {
    translate: 8px 8px;
  }

  @starting-style {
    .test-card-info:not([hidden]) {
      translate: -8px 8px;
    }

    .test-card-info.info-left:not([hidden]) {
      translate: 8px 8px;
    }
  }
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

/**
 * Paper.
 *
 * `/professionals` exists to be handed to somebody, and SITE.md asked for
 * "something printable". A PDF would be a second copy of the same words that
 * goes stale the moment the page changes, so the page is the handout.
 *
 * The one place this site sets dark ink on an unpainted background. Browsers do
 * not print background colours by default and the paper is already white, so
 * insisting on parchment would either be ignored or waste a cartridge to
 * reproduce a screen comfort measure on a medium that does not emit light. The
 * warm charcoal stays, because grey-on-white prints better than black-on-white
 * and the reason this product exists does not stop at the printer.
 */
@media print {
  .skip,
  .site-nav,
  #reading-controls,
  .panel,
  .panel-toggle,
  .toc,
  .settling-divider,
  .footer-nav {
    display: none !important;
  }

  body {
    background: none;
    color: #2e2a26;
    font-size: 11pt;
    line-height: 1.5;
  }

  .wrap,
  .prose {
    max-width: none;
    padding: 0;
  }

  /* The title band is a screen device. On paper a heading is already a
     heading, and a tinted panel is a cartridge spent on nothing. */
  .page-hero {
    background: none;
    margin: 0;
    padding: 0;
  }

  .page-head {
    background: none;
    border: 0;
    border-bottom: 1px solid #9a8b78;
    border-radius: 0;
    padding: 0 0 var(--space-md);
  }

  .page-head::before,
  .page-head h1::after {
    display: none;
  }

  h1,
  h2,
  h3 {
    color: #2e2a26;
    /* A heading stranded at the foot of a page is the commonest print defect. */
    break-after: avoid;
  }

  .section,
  .two-up > div,
  .steps li,
  figure,
  tr {
    break-inside: avoid;
  }

  a {
    color: inherit;
    text-decoration: underline;
  }

  /* On paper a link is only useful if you can read where it goes. Internal ones
     are left alone — the whole URL beside every cross-reference is noise. */
  a[href^='http']::after {
    content: ' (' attr(href) ')';
    font-size: 0.85em;
    word-break: break-all;
  }

  .note,
  .contact,
  .card {
    border: 1px solid #9a8b78;
  }
}

/**
 * Windows High Contrast, and anything else that forces its own palette.
 *
 * Worth being honest about what this mode does to this particular site: the
 * operating system replaces every colour, so the tint picker has nothing left to
 * demonstrate — nine swatches all render identically. That is the correct
 * outcome, because a reader who has told their OS to force colours has already
 * answered the question the picker asks, and more emphatically.
 *
 * So the goal here is not to defend the colours. It is to make sure the parts
 * that still matter survive: the structure stays legible, the focus ring stays
 * visible, and the controls that have nothing to do with hue — text size, line
 * spacing, letter spacing, typeface — go on working.
 */
@media (forced-colors: active) {
  /* Surfaces drawn only by a background mix collapse into the system canvas, so
     anything that was a card needs a real border to still read as one. */
  .card,
  .demo,
  .demo-page,
  .honest,
  .contact,
  .two-up > div,
  .panel,
  .shot {
    border: 1px solid CanvasText;
  }

  /* `outline-color` is one of the few properties the mode leaves alone. */
  :focus-visible,
  .swatches input:focus-visible + .swatch,
  .demo-strip input:focus-visible + .chip {
    outline: 3px solid Highlight;
  }

  /* Selection is carried by the tick rather than the border, which is why there
     has always been a tick. */
  .swatches input:checked + .swatch,
  .demo-strip input:checked + .chip {
    border: 3px solid Highlight;
  }

  .button {
    border: 1px solid ButtonText;
  }
}
